eShopOnContainers 知多少[5]:EventBus With RabbitMQ

1. 引言

事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉。事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。640?wx_fmt=other从上图可知,核心就4个角色:

  1. 事件(事件源+事件处理)

  2. 事件发布者

  3. 事件订阅者

  4. 事件总线

实现事件总线的关键是:

  1. 事件总线维护一个事件源与事件处理的映射字典;

  2. 通过单例模式,确保事件总线的唯一入口;

  3. 利用反射完成事件源与事件处理的初始化绑定;

  4. 提供统一的事件注册、取消注册和触发接口。

以上源于我在事件总线知多少(1)中对于EventBus的分析和简单总结。基于以上的简单认知,我们来梳理下eShopOnContainers中EventBus的实现机制。

2. 高屋建瓴--看类图

我们直接以上帝视角,来看下其实现机制,上类图。640?wx_fmt=png

我们知道事件的本质是:事件源+事件处理。 针对事件源,其定义了 Handle方法用于响应事件。不同之处在于方法参数的类型: 第一个接受的是一个强类型的 dynamic。 为什么要单独提供一个事件源为 dynamic可以简化事件源的构建,更趋于灵活。

有了事件源和事件处理,接下来就是事件的注册和订阅了。为了方便进行订阅管理,系统提供了额外的一层抽象 InMemoryEventBusSubscriptionsManager就是使用内存进行存储事件源和事件处理的映射字典。 从类图中看 SubscriptionInfo,其主要用于表示事件订阅方的订阅类型和事件处理的类型。

我们来近距离看下

  1. //InMemoryEventBusSubscriptionsManager.cs

  2. //定义的事件名称和事件订阅的字典映射(1:N)

  3. private readonly Dictionary<string, List<SubscriptionInfo>> _handlers;

  4. //保存所有的事件处理类型

  5. private readonly List<Type> _eventTypes;

  6. //定义事件移除后事件

  7. public event EventHandler<string> OnEventRemoved;


  8. //构造函数初始化

  9. public InMemoryEventBusSubscriptionsManager()

  10. {

  11.    _handlers = new Dictionary<string, List<SubscriptionInfo>>();

  12.    _eventTypes = new List<Type>();

  13. }

  14. //添加动态类型事件订阅(需要手动指定事件名称)

  15. public void AddDynamicSubscription<TH>(string eventName)

  16.    where TH : IDynamicIntegrationEventHandler

  17. {

  18.    DoAddSubscription(typeof(TH), eventName, isDynamic: true);

  19. }

  20. //添加强类型事件订阅(事件名称为事件源类型)

  21. public void AddSubscription<T, TH>()

  22.    where T : IntegrationEvent

  23.    where TH : IIntegrationEventHandler<T>

  24. {

  25.    var eventName = GetEventKey<T>();


  26.    DoAddSubscription(typeof(TH), eventName, isDynamic: false);


  27.    if (!_eventTypes.Contains(typeof(T)))

  28.    {

  29.        _eventTypes.Add(typeof(T));

  30.    }

  31. }

  32. //移除动态类型事件订阅

  33. public void RemoveDynamicSubscription<TH>(string eventName)

  34.    where TH : IDynamicIntegrationEventHandler

  35. {

  36.    var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);

  37.    DoRemoveHandler(eventName, handlerToRemove);

  38. }


  39. //移除强类型事件订阅

  40. public void RemoveSubscription<T, TH>()

  41.    where TH : IIntegrationEventHandler<T>

  42.    where T : IntegrationEvent

  43. {

  44.    var handlerToRemove = FindSubscriptionToRemove<T, TH>();

  45.    var eventName = GetEventKey<T>();

  46.    DoRemoveHandler(eventName, handlerToRemove);

  47. }

添加了这么一层抽象,即符合了单一职责原则,又完成了代码重用。 IEventBusSubscriptionsManager的依赖,即可完成订阅管理。 你这里可能会好奇,为什么要暴露一个 EventBusRabbitMQ源码亲密接触。

3.3.1. 构造函数定义

IRabbitMQPersistentConnection

以便连接到对应的Broke。


  • 使用空对象模式注入 OnEventRemoved事件,取消队列的绑定。(这也就回答了上面遗留的问题)

3.3.2. 事件订阅的逻辑:

  1. public void Publish(IntegrationEvent @event)

  2. {

  3.    if (!_persistentConnection.IsConnected)

  4.    {

  5.        _persistentConnection.TryConnect();

  6.    }


  7.    var policy = RetryPolicy.Handle<BrokerUnreachableException>()

  8.        .Or<SocketException>()

  9.        .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>

  10.        {

  11.            _logger.LogWarning(ex.ToString());

  12.        });


  13.    using (var channel = _persistentConnection.CreateModel())

  14.    {

  15.        var eventName = @event.GetType()

  16.            .Name;


  17.        channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");


  18.        var message = JsonConvert.SerializeObject(@event);

  19.        var body = Encoding.UTF8.GetBytes(message);


  20.        policy.Execute(() =>

  21.        {

  22.            var properties = channel.CreateBasicProperties();

  23.            properties.DeliveryMode = 2; // persistent


  24.            channel.BasicPublish(exchange: BROKER_NAME, routingKey: eventName, mandatory:true, basicProperties: properties, body: body);

  25.        });

  26.    }

  27. }

这里面有以下几个知识点:

  1. 使用Polly,以2的阶乘的时间间隔进行重试。(第一次2s后,第二次4s后,第三次8s后...重试)

  2. 使用direct全匹配、单播形式的路由机制进行消息分发

  3. 消息主体是格式化的json字符串

  4. 指定 mandatory:true告知服务器当根据指定的routingKey和消息找不到对应的队列时,直接返回消息给生产者。

3.3.4. 然后看看事件消息的监听

Received事件委托处理消息接收事件

调用 

以上代码主要包括以下知识点:

4. EventBus的集成和使用

以上介绍了EventBus的实现要点,那各个微服务是如何集成呢?

1. 注册

2. 注册单例模式的 services.AddSingleton<IEventBusSubscriptionsManager,InMemoryEventBusSubscriptionsManager>();

3. 注册单例模式的

完成了以上集成,就可以在代码中使用事件总线进行事件的发布和订阅。

4. 发布事件

若要发布事件,需要根据是否需要事件源(参数传递)来决定是否需要申明相应的集成事件,需要则继承自 IEventBus的实例的

IIntegrationEventHandler

IEventBus的实例调用

TestEvent
事件,B服务订阅该事件,同样需要在B服务复制定义一个 <code class="prettyprint code-in-text prettyprinted" style="box-sizing: border-box;background: rgb(243, 241, 241);color: rgb(88, 88, 88);line-height: 18px;font-family: consolas, menlo, courier, monospace, " initial="" microsoft="" !important;"="" 0px="">TestEvent


。 这也是微服务的一个通病,重复代码。


5. 最后

通过一步一步的源码梳理,我们发现eShopOnContainers中事件总线的总体实现思路与引言部分的介绍十分契合。所以对于事件总线,不要觉得高深,明确参与的几个角色以及基本的实现步骤,那么不管是基于RabbitMQ实现也好还是基于Azure Service Bus也好,万变不离其宗!




  1. //定义事件处理

  2. public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>

  3. {

  4.    public async Task Handle(ProductPriceChangedIntegrationEvent @event)

  5.    {

  6.        //do something

  7.    }

  8. }

  9. //事件源的声明

  10. public class ProductPriceChangedIntegrationEvent : IntegrationEvent

  11. {        

  12.    public int ProductId { get; private set; }


  13.    public decimal NewPrice { get; private set; }


  14.    public decimal OldPrice { get; private set; }


  15.    public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)

  16.    {

  17.        ProductId = productId;

  18.        NewPrice = newPrice;

  19.        OldPrice = oldPrice;

  20.    }

  21. }

  22. services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>

  23. {

  24.    var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();

  25.    var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();

  26.    var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();

  27.    var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();


  28.    var retryCount = 5;

  29.    if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))

  30.    {

  31.        retryCount = int.Parse(Configuration["EventBusRetryCount"]);

  32.    }


  33.    return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);

  34. });

  35. services.AddSingleton<IRabbitMQPersistentConnection>(sp =>

  36. {

  37.    var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();

  38.    //...

  39.    return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);

  40. });

  1. Json字符串的反序列化

  2. 利用依赖注入容器解析集成事件(Integration Event)和事件处理(Event Handler)类型

  3. 反射调用具体的事件处理方法

  1. private async Task ProcessEvent(string eventName, string message)

  2. {

  3.    if (_subsManager.HasSubscriptionsForEvent(eventName))

  4.    {

  5.        using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))

  6.        {

  7.            var subscriptions = _subsManager.GetHandlersForEvent(eventName);

  8.            foreach (var subscription in subscriptions)

  9.            {

  10.                if (subscription.IsDynamic)

  11.                {

  12.                    var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;

  13.                    dynamic eventData = JObject.Parse(message);

  14.                    await handler.Handle(eventData);

  15.                }

  16.                else

  17.                {

  18.                    var eventType = _subsManager.GetEventTypeByName(eventName);

  19.                    var integrationEvent = JsonConvert.DeserializeObject(message, eventType);

  20.                    var handler = scope.ResolveOptional(subscription.HandlerType);

  21.                    var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);

  22.                    await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });

  23.                }

  24.            }

  25.        }

  26.    }

  27. }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318373.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

USACO Section 4

前言 好久没更新这个系列了&#xff0c;最近闲的无聊写一下。有两题搜索懒得写了。 P2737 [USACO4.1]麦香牛块Beef McNuggets https://www.luogu.com.cn/problem/P2737 解题思路 先只考虑a1a_1a1​&#xff0c;假设我们拼出了www&#xff0c;那么一定能拼出wka1wka_1wka1​…

YBTOJ:数列方案(组合数学)

文章目录题目描述解析代码题目描述 解析 如果它不取等&#xff0c;那就和方程的解这道题一样了&#xff0c;但有了等号就很头疼 如何把等号去掉呢&#xff1f; 定义BiAiiB~i~A~i~iB i A i i那么我们就可以得到&#xff1a;0<B1<B2<...<Bm<mn0<B~1~<B~2~&…

【COCI 2018/2019 Round #2】Kocka

这道题也是一个ex的模拟题 不过他比Zamjena可爱 作为一个帅气的小哥哥&#xff0c;让我们一起&#xff0c; 开启你的模拟ex大门&#xff0c;C从入门到放弃&#xff01; 题目 题目描述 我又来了&#xff01;我又来了&#xff01; 在清晨来到儿童游乐园的时候&#xff0c;出题…

Matrix Equation

题意&#xff1a; 题目给出两个矩阵X,Y,现在有两种操作 Z X Y D X⊙Y 问是否存在一个矩阵C&#xff0c;使得ACB⊙C式子成立&#xff0c;问矩阵C能有多少个 题解&#xff1a; 这个式子在模2意义下的加法就等于异或 也就相当于 那现在有 将BC移到左边 然后将Ci,j的系数进…

eShopOnContainers 知多少[6]:持久化事件日志

1. 引言事件总线解决了微服务间如何基于集成事件进行异步通信的问题。然而只有事件总线正常运行&#xff0c;微服务之间基于事件的通信才得以运转。 而现实情况是&#xff0c;总有这样或那样的问题&#xff0c;导致事件总线不稳定或不可用&#xff0c;比如&#xff1a;网络中断…

单调队列优化DP

全局最优解必然包含局部最优解&#xff0c;因此每次转移只需考虑局部最优解&#xff01;&#xff01;&#xff01; 主要内容 形如这样 的 \(\operatorname{DP}\) 转移方程&#xff1a; \[dp[i]\max_{L_i\le j\le R_i}{\{dp[i]val(i,j)\}} \]满足&#xff1a; \(\{L_i\}\) , \(\…

CF1322B-Present【双指针】

正题 题目链接:https://www.luogu.com.cn/problem/CF1322B 题目大意 给出nnn个数字aia_iai​求 ⨁i1n⨁ji1n(aiaj)\bigoplus _{i1}^n\bigoplus _{ji1}^n(a_ia_j)i1⨁n​ji1⨁n​(ai​aj​) 1≤n≤4105,1≤ai≤1071\leq n\leq 4\times 10^5,1\leq a_i\leq 10^71≤n≤4105,1≤a…

多体问题

代码&#xff1a; function SunEarthMoon % M函数文件load planets; % 将planets.mat中的变量mass、position、velocity加载过来[sun, earth, moon] deal(18, 3, 25); % sun、earth、moon分别是18、3、25行 list [sun, earth, moon]; % 1行3列矩阵 G 6.67e-11; % gr…

【CF1179 A,B,C】Valeriy and Deque / Tolik and His Uncle / Serge and Dining Room

还好题很温柔&#xff0c;温柔得我差点没做完 文章目录A&#xff1a;Valeriy and Deque题意题解代码实现B&#xff1a;Tolik and His Uncle题目题解代码实现C&#xff1a;Serge and Dining Room题目题解代码实现A&#xff1a;Valeriy and Deque 题意 给定一个双端队列&#…

YBTOJ:比赛得分(期望)

文章目录题目描述解析代码题目描述 解析 不太难的题 显然本题在AB队员大小关系相反时其对答案的贡献互为相反数。 所以想到把B队队员sort一下后就可以二分找到大小关系相反的分界点 然后维护和与平方和两个前缀数组搞一搞即可O1求出贡献 总复杂度&#xff1a;nlognnlognnlogn …

Matlab与高等数学

曲线与曲面画图 平面 对于不同曲线的表达式&#xff0c;Matlab中有不同的绘图命令&#xff0c;主要有 plot, fplot, ezplot&#xff0c;plot3&#xff0c;polar&#xff0c; 曲面 1.2 曲面画图 曲面的一般方程是F(x,y,z)0&#xff0c;一般需要将曲面的点坐标先表示出来&…

[USACO19JAN,Platinum] Redistricting

[USACO19JAN,Platinum] Redistricting 这道题A了才知道。。并不难a&#xff01; orz 题目 内存限制&#xff1a;128 MiB 时间限制&#xff1a;1000 ms 题目描述 奶牛们的最大城市Bovinopolis正在重新划分势力范围—生活在那里的主要是两个品种的奶牛&#xff08;Holsteins和…

.NET Core + JWT令牌认证 + Vue.js 通用动态权限(RBAC)管理系统框架[DncZeus]开源啦!!!...

DncZeus前言关于 DncZeusDncZeus Dnc Zeus"Dnc"--.Net Core 的缩写&#xff1b;"Zeus"--中文译为宙斯&#xff0c;是古希腊神话中的众神之王&#xff0c;奥林匹斯十二主神之首&#xff0c;统治宇宙万物的至高无上的主神&#xff08;在古希腊神话中主神专…

[gdoi2018 day1]小学生图论题【分治NTT】

正题 题目大意 一张随机的nnn个点的竞赛图&#xff0c;给出它的mmm条相互无交简单路径&#xff0c;求这张竞赛图的期望强联通分量个数。 1≤n,m≤1051\leq n,m\leq 10^51≤n,m≤105 解题思路 先考虑m0m0m0的做法&#xff0c;此时我们考虑一个强联通块的贡献&#xff0c;注意到…

背包问题 DP

各种各样的基础背包 0-1 背包 非常朴素&#xff0c;复杂度 \(O(nV)\) void z_o_pack(int c,int v) {for(int iV;i>c;i--)dp[i]max(dp[i],dp[i-c]v); } 完全背包 复杂度 \(O(nV)\) void comp_pack(int c,int v) {for(int ic;i<V;i)dp[i]max(dp[i],dp[i-c]v); } 多重背包 单…

P5081 Tweetuzki爱取球(期望)(线性求逆元)

文章目录题目描述解析代码题目描述 解析 首先有一个很重要的引理&#xff1a; 若一件事做成的概率是p&#xff0c;则其做成需要次数的期望是1/p 为什么呢&#xff1f; 我们设做成这件事的期望次数是x 就可以列出方程&#xff1a; x1p∗0(1−p)∗xx1p*0(1-p)*xx1p∗0(1−p)∗x …

Matlab与线性代数

文章目录多项式求解1.2 多项式四则运算1.3 多项式的分解与合并行列式求解3、矩阵基本运算➢ 3.2 矩阵的取块和变换➢ 3.3 矩阵的基本运算4、求解线性方程组多项式求解 ➢ 1.1 多项式表达式与根 有关多项式函数表达式与根的Matlab命令&#xff1a; poly2sym 返回由多项式系数转…

【 CF1186D,E,F】Vus the Cossack and Numbers/Vus the Cossack and a Field/Vus the Cossack and a Graph

太ex了&#xff0c;哭了哭了orz 后面两道平均一道花了我一天啊&#xff01; 文章目录D&#xff1a;Vus the Cossack and Numbers题意翻译题解代码实现E&#xff1a;Vus the Cossack and a Field题意翻译题解代码实现F:Vus the Cossack and a Graph题目暴力题解代码实现官方题解…

IdentityServer4与ocelot实现认证与客户端统一入口

关于IdentityServer4与ocelot博客园里已经有很多介绍我这里就不再重复了。ocelot与IdentityServer4组合认证博客园里也有很多&#xff0c;但大多使用ocelot内置的认证&#xff0c;而且大多都是用来认证API的&#xff0c;查找了很多资料也没看到如何认证oidc&#xff0c;所以这里…

CF1556D-Take a Guess【交互】

正题 题目链接:https://codeforces.com/contest/1556/problem/D 题目大意 现在有nnn个你不知道的数字&#xff0c;你有两种询问操作 询问两个下标的数字的andandand询问两个下标的数字的ororor 要求在2n2n2n次操作以内求出第kkk小的数字 1≤n≤104,0≤ai≤1091\leq n\leq 1…