源码解析-Abp vNext丨分布式事件总线DistributedEventBus
前言
上一节咱们讲了LocalEventBus,本节来讲本地事件总线(DistributedEventBus),采用的RabbitMQ进行实现。
Volo.Abp.EventBus.RabbitMQ模块内部代码并不多,RabbitMQ的操作都集中在Volo.Abp.RabbitMQ这个包中。
正文
我们从模块定义开始看,项目启动的时候分别读取了appsetting.json的配置参数和调用了RabbitMqDistributedEventBus的Initialize函数。
public class AbpEventBusRabbitMqModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpRabbitMqEventBusOptions>(configuration.GetSection("RabbitMQ:EventBus"));
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
context
.ServiceProvider
.GetRequiredService<RabbitMqDistributedEventBus>()
.Initialize();
}
}
在Initialize函数中我们根据 MessageConsumerFactory.Create向内部进行查阅可以看到最终调用方法为RabbitMqMessageConsumer.TryCreateChannelAsync并且在其内部我们可以看到下面代码,这里定义了消费的回调函数。反推Initialize方法其实是在启动一个消费者。
public void Initialize()
{
Consumer = MessageConsumerFactory.Create(
new ExchangeDeclareConfiguration(
AbpRabbitMqEventBusOptions.ExchangeName,
type: "direct",
durable: true
),
new QueueDeclareConfiguration(
AbpRabbitMqEventBusOptions.ClientName,
durable: true,
exclusive: false,
autoDelete: false
),
AbpRabbitMqEventBusOptions.ConnectionName
);
Consumer.OnMessageReceived(ProcessEventAsync);
SubscribeHandlers(AbpDistributedEventBusOptions.Handlers);
}
var consumer = new AsyncEventingBasicConsumer(Channel);
consumer.Received += HandleIncomingMessageAsync;
继续向下看Consumer.OnMessageReceived(ProcessEventAsync);该方法向一个并发安全集合输入一个委托事件,并该事件会在上面的HandleIncomingMessageAsync会调中触发故确定为消费者的执行逻辑,而ProcessEventAsync其实还是走了我们在讲LocalEventBus哪一套,寻找Handler执行函数。
SubscribeHandlers还是上节讲的基类的函数,这里要注意内部调用的Subscribe该方法中的 Consumer.BindAsync会根据为消费者Bind路由,这样才能触发事件处理函数。
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
var handlerFactories = GetOrCreateHandlerFactories(eventType);
if (factory.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(factory);
if (handlerFactories.Count == 1) //TODO: Multi-threading!
{
Consumer.BindAsync(EventNameAttribute.GetNameOrDefault(eventType));
}
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
看完了事件消费者我们来看看事件发布,直接看PublishAsync函数就完事了,整个函数非常简单,都是RabbitMQ的操作语法,这里的路由Key是在EventNameAttribute.GetNameOrDefault(eventType);函数中通过读取ETO上指定注解Name来指定的。
protected Task PublishAsync(
string eventName,
byte[] body,
IBasicProperties properties,
Dictionary<string, object> headersArguments = null,
Guid? eventId = null)
{
using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
{
channel.ExchangeDeclare(
AbpRabbitMqEventBusOptions.ExchangeName,
"direct",
durable: true
);
if (properties == null)
{
properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
}
if (properties.MessageId.IsNullOrEmpty())
{
properties.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N");
}
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish(
exchange: AbpRabbitMqEventBusOptions.ExchangeName,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body
);
}
return Task.CompletedTask;
}
解析
整个分布式事件的实现其实非常简单,在事件发生时发布者只需要定义好路由名称和消息内容发送RabbitMQ中,而消费者则是在项目运行的时候的通过调用Initialize就启动起来了。
这里我们也同样根据整个原理自己实现一下这个流程。
在Dppt.EventBus分别定义IDistributedEventBus、DistributedEventBusOptions、IDistributedEventHandler分别用于采用分布式事件总线调用、配置选项用于存储处理程序Handler、定义分布式处理程序抽象。
新建Dppt.EventBus.RabbitMQ类库先简单对RabbitMQ进行一个简单的封装
public class RabbitMqConnections : IRabbitMqConnections
{
private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<RabbitMqConnections> _logger;
IConnection _connection;
bool _disposed;
public RabbitMqConnections(IConnectionFactory connectionFactory, ILogger<RabbitMqConnections> logger)
{
_connectionFactory = connectionFactory;
_logger = logger;
}
public bool IsConnected
{
get
{
return _connection != null && _connection.IsOpen && !_disposed;
}
}
public void TryConnect() {
_connection = _connectionFactory.CreateConnection();
}
public IModel CreateModel()
{
if (!IsConnected)
{
throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
}
return _connection.CreateModel();
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
_connection.Dispose();
}
catch (IOException ex)
{
_logger.LogCritical(ex.ToString());
}
}
}
然后我们分别定义ExchangeDeclareConfiguration、QueueDeclareConfiguration用于记录配置信息。
开始处理RabbitMqEventBus处理程序首先是发布事件,大体代码如下就是往RabbitMQ里面丢消息。
/// <summary>
/// rabbmitmq 连接服务
/// </summary>
public readonly IRabbitMqConnections _rabbitMqConnections;
public Task PublishAsync<TEvent>(TEvent eventData)
{
var eventName = EventNameAttribute.GetNameOrDefault(typeof(TEvent));
var body = JsonSerializer.Serialize(eventData);
return PublishAsync(eventName, body, null, null);
}
public Task PublishAsync(string eventName, string body, IBasicProperties properties, Dictionary<string, object> headersArguments = null, Guid? eventId = null)
{
if (!_rabbitMqConnections.IsConnected)
{
_rabbitMqConnections.TryConnect();
}
using (var channel = _rabbitMqConnections.CreateModel())
{
// durable 设置队列持久化
channel.ExchangeDeclare(RabbitMqEventBusOptions.ExchangeName, "direct", durable: true);
if (properties == null)
{
properties = channel.CreateBasicProperties();
// 设置消息持久化
properties.DeliveryMode = 2;
}
if (properties.MessageId.IsNullOrEmpty())
{
// 消息的唯一性标识
properties.MessageId = (eventId ?? Guid.NewGuid()).ToString("N");
}
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish(
exchange: RabbitMqEventBusOptions.ExchangeName,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: Encoding.UTF8.GetBytes(body)
);
}
return Task.CompletedTask;
}
private void SetEventMessageHeaders(IBasicProperties properties, Dictionary<string, object> headersArguments)
{
if (headersArguments == null)
{
return;
}
properties.Headers ??= new Dictionary<string, object>();
foreach (var header in headersArguments)
{
properties.Headers[header.Key] = header.Value;
}
}
然后就是消费者的处理,我们同样定义Initialize函数,并简化部分封装代码,完成消费者启动。
public void Initialize()
{
Exchange = new ExchangeDeclareConfiguration(RabbitMqEventBusOptions.ExchangeName,"direct",true);
Queue = new QueueDeclareConfiguration(RabbitMqEventBusOptions.ClientName, true, false, false);
// 启动一个消费者
if (!_rabbitMqConnections.IsConnected)
{
_rabbitMqConnections.TryConnect();
}
try
{
Channel = _rabbitMqConnections.CreateModel();
Channel.ExchangeDeclare(
exchange: Exchange.ExchangeName,
type: Exchange.Type,
durable: Exchange.Durable,
autoDelete: Exchange.AutoDelete,
arguments: Exchange.Arguments
);
Channel.QueueDeclare(
queue: Queue.QueueName,
durable: Queue.Durable,
exclusive: Queue.Exclusive,
autoDelete: Queue.AutoDelete,
arguments: Queue.Arguments
);
var consumer = new AsyncEventingBasicConsumer(Channel);
consumer.Received += HandleIncomingMessageAsync;
Channel.BasicConsume(
queue: Queue.QueueName,
autoAck: false,
consumer: consumer
);
SubscribeHandlers(DistributedEventBusOptions.Handlers);
}
catch (Exception ex)
{
Console.WriteLine("Error:" + ex.Message);
}
}
参数配置这边主要是读取AppSetting信息和索要Handler
public static class DpptEventBusRabbitMqRegistrar
{
public static void AddDpptEventBusRabbitMq(this IServiceCollection services, IConfiguration configuration, List<Type> types)
{
services.AddSingleton<IRabbitMqConnections>(sp =>
{
var logger = sp.GetRequiredService<ILogger<RabbitMqConnections>>();
var factory = new ConnectionFactory()
{
HostName = configuration["RabbitMQ:EventBusConnection"],
VirtualHost = configuration["RabbitMQ:EventBusVirtualHost"],
DispatchConsumersAsync = true,
AutomaticRecoveryEnabled = true
};
if (!string.IsNullOrEmpty(configuration["RabbitMQ:EventBusUserName"]))
{
factory.UserName = configuration["RabbitMQ:EventBusUserName"];
}
if (!string.IsNullOrEmpty(configuration["RabbitMQ:EventBusPassword"]))
{
factory.Password = configuration["RabbitMQ:EventBusPassword"];
}
return new RabbitMqConnections(factory, logger);
});
var distributedHandlers = types;
foreach (var item in distributedHandlers)
{
services.AddSingleton(item);
}
services.Configure<DistributedEventBusOptions>(options =>
{
options.Handlers.AddIfNotContains(distributedHandlers);
});
services.Configure<DpptRabbitMqEventBusOptions>(options => {
options.ExchangeName = configuration["RabbitMQ:EventBus:ExchangeName"];
options.ClientName = configuration["RabbitMQ:EventBus:ClientName"];
});
services.AddSingleton<IDistributedEventBus, RabbitMqDistributedEventBus>();
}
}
测试
新建一个空项目,进行插件注册,然后创建ETO和Handler进行测试。

测试结果放在下面了。


结语
本次挑选了一个比较简单的示例来讲,整个EventBus我应该分成3篇 下一篇我来讲分布式事务。
最后欢迎各位读者关注我的博客, https://github.com/MrChuJiu/Dppt/tree/master/src 欢迎大家Star
另外这里有个社区地址(https://github.com/MrChuJiu/Dppt/discussions),如果大家有技术点希望我提前档期可以写在这里,希望本项目助力我们一起成长
源码解析-Abp vNext丨分布式事件总线DistributedEventBus的更多相关文章
- 源码解析-Abp vNext丨LocalEventBus
前言 基础篇已经更新完了,从本篇开始我们进入,中级篇(学习部分源代码)我会挑一些我个人认为比较重要的知识点结合部分开源项目进行源码讲解,咱们废话不说直接上车. Abp vNext的事件总线分2种,一种 ...
- [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)
一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...
- [源码解析] TensorFlow 分布式之 ClusterCoordinator
[源码解析] TensorFlow 分布式之 ClusterCoordinator 目录 [源码解析] TensorFlow 分布式之 ClusterCoordinator 1. 思路 1.1 使用 ...
- [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇
[源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 目录 [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 1. ...
- [源码解析] TensorFlow 之 分布式变量
[源码解析] TensorFlow 之 分布式变量 目录 [源码解析] TensorFlow 之 分布式变量 1. MirroredVariable 1.1 定义 1.2 相关类 1.2.1 类体系 ...
- [源码解析] TensorFlow 分布式之 MirroredStrategy
[源码解析] TensorFlow 分布式之 MirroredStrategy 目录 [源码解析] TensorFlow 分布式之 MirroredStrategy 1. 设计&思路 1.1 ...
- [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算
[源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算 目录 [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算 0x1. 运行 ...
- Android 开源项目源码解析(第二期)
Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations ...
- Abp Vnext源码解析系列文章01---EventBus
一.简介 BP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装了一个 ...
随机推荐
- linux关于profile 、bashrc 、.bash_profile、.bashrc的区别
linux关于profile .bashrc ..bash_profile..bashrc的区别 - /etc/profile /etc/bashrc ~/.bash_profile ~/.bashr ...
- 【转】Linux 查看端口占用情况
Linux 查看端口占用情况可以使用 lsof 和 netstat 命令. lsof lsof(list open files)是一个列出当前系统打开文件的工具. lsof 查看端口占用语法格式: l ...
- Dart简易教程 (1)---数据类型 运算符,类转换换
从下面开始学习DART编程 以下是一个简单的示例: main(){ var number = 42; print(number);}程序说明,dart是一个强大的脚本类语言,可以不预先定义变量类型 , ...
- git实战-linux定时监控github更新状态(二)
系列文章 git介绍-常用操作(一)✓ git实战-linux定时监控github更新状态(二)✓ 本文主要内容 如何查看github的本地仓库和远程仓库的同步情况 linux服务器定时监控githu ...
- 深入浅出 BPF TCP 拥塞算法实现原理
本文地址:https://www.ebpf.top/post/ebpf_struct_ops 1. 前言 eBPF 的飞轮仍然在快速转动,自从 Linux 内核 5.6 版本支持 eBPF 程序修改 ...
- Docker DevOps实战:Docker+Jenkins+Python+Pytest+Allure(1)- 创建Jenkins容器、安装Python环境、安装项目依赖类库、安装Allure报告插件
前言: 本文实操笔记参照菠萝笔记,安装过程中的坑大家可以参考下 创建Jenkins容器 # 下载Jenkins镜像 [root@localhost ~]# docker pull jenkins/je ...
- Docker系列(22)- DockerFile指令说明并构建自己的centos
DockerFile常用指令 实战测试 DockerHub中99%镜像都是从这个基础镜像过来的FROM scratch,然后配置需要的软件和配置来进行构建 #创建一个自己的centos # 1.编写D ...
- Jmeter系列(12)- 上传接口压测
step-1上传接口分析 上传接口源码分析:分析上传文件类型.有无大小限制.存放上传文件服务器 没有源码通过抓包工具,或者Chrome查看框架源代码 接口路径/uploadfile,接口请求POST, ...
- 测试工程需要明白的Monkey测试
App稳定性测试 稳定性测试就是指软件长时间的持续运行,系统版本是否稳定,是否能否持续的为用户提供服务. 指标: 异常的次数 异常的频率 App的稳定性测试如何实施? 首选Monkey Monkey是 ...
- Jmeter压测学习1—第一个项目:登录
我用的是我们公司的测试环境练习的 网址:http://****:9000/login.html 账号是admin 密码:123456 一.打开Jmeter 找到安装目录:bin->Jmeter ...