前言

上一节咱们讲了LocalEventBus,本节来讲本地事件总线(DistributedEventBus),采用的RabbitMQ进行实现。

Volo.Abp.EventBus.RabbitMQ模块内部代码并不多,RabbitMQ的操作都集中在Volo.Abp.RabbitMQ这个包中。

正文

我们从模块定义开始看,项目启动的时候分别读取了appsetting.json的配置参数和调用了RabbitMqDistributedEventBusInitialize函数。

    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的更多相关文章

  1. 源码解析-Abp vNext丨LocalEventBus

    前言 基础篇已经更新完了,从本篇开始我们进入,中级篇(学习部分源代码)我会挑一些我个人认为比较重要的知识点结合部分开源项目进行源码讲解,咱们废话不说直接上车. Abp vNext的事件总线分2种,一种 ...

  2. [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)

    一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...

  3. [源码解析] TensorFlow 分布式之 ClusterCoordinator

    [源码解析] TensorFlow 分布式之 ClusterCoordinator 目录 [源码解析] TensorFlow 分布式之 ClusterCoordinator 1. 思路 1.1 使用 ...

  4. [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇

    [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 目录 [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 1. ...

  5. [源码解析] TensorFlow 之 分布式变量

    [源码解析] TensorFlow 之 分布式变量 目录 [源码解析] TensorFlow 之 分布式变量 1. MirroredVariable 1.1 定义 1.2 相关类 1.2.1 类体系 ...

  6. [源码解析] TensorFlow 分布式之 MirroredStrategy

    [源码解析] TensorFlow 分布式之 MirroredStrategy 目录 [源码解析] TensorFlow 分布式之 MirroredStrategy 1. 设计&思路 1.1 ...

  7. [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算

    [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算 目录 [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算 0x1. 运行 ...

  8. Android 开源项目源码解析(第二期)

    Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations ...

  9. Abp Vnext源码解析系列文章01---EventBus

    一.简介 BP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装了一个 ...

随机推荐

  1. 离散化模板题 II ——重复元素离散化后的数字不相同

    离散化模板题 II --重复元素离散化后的数字不相同 题目描述 现有数列A1, A2, ⋯, An,数列中可能有重复元素. 现在要求输出该数列的离散化数列,重复元素离散化后的数字不相同. 输入 第一行 ...

  2. Android通过WebView实现新闻界面的加载

    原文链接:Android实现WebView加载网页及网页美化(简易新闻 四)_Tobey_r1的博客-CSDN博客 效果展示: 我是按照原文作者的步骤一步步来的,中间没有遇到什么问题.主要是界面中有很 ...

  3. eval(input())

    看到一段代码,判读输入的数字,用的是eval(input()),查了一下,原来input()会把所有输入值,包括数字,视为字符串,而eval()会去掉字符串最外层的引号,然后当做Python语句执行[ ...

  4. 前端规范之Git提交规范(Commitizen)

    代码规范是软件开发领域经久不衰的话题,几乎所有工程师在开发过程中都会遇到或思考过这一问题.而随着前端应用的大型化和复杂化,越来越多的前端团队也开始重视代码规范.同样,前段时间,笔者所在的团队也开展了一 ...

  5. 【Golang】Go 通过结构(struct) 实现接口(interface)

    一.通过结构(struct) 实现 接口(interface) 1.在了解iris框架的时候,经常看到有这样去写的使用一个空结构体作为接收器,来调用方法,有点好奇这样做有什么意义. 解释:在 Go 语 ...

  6. 突破GD渲染的图片马

    <?php /* The algorithm of injecting the payload into the JPG image, which will keep unchanged aft ...

  7. Java网络爬虫技术《一》 HttpClient

    HttpClient HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的.最新的.功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP ...

  8. WPF进阶技巧和实战08-依赖属性与绑定02

    将元素绑定在一起 数据绑定最简单的形式是:源对象是WPF元素而且源属性是依赖项属性.依赖项属性内置了更改通知支持,当源对象中改变依赖项属性时,会立即更新目标对象的绑定属性. 元素绑定到元素也是经常使用 ...

  9. Python简单爬取图书信息及入库

    课堂上老师布置了一个作业,如下图所示: 就是简单写一个借书系统. 大概想了一下流程,登录-->验证登录信息-->登录成功跳转借书界面-->可查看自己的借阅书籍以及数量... 登录可以 ...

  10. 洛谷2619/bzoj2654 Tree(凸优化+MST)

    bzoj的数据是真的水.. qwq 由于本人还有很多东西不是很理解 qwq 所以这里只写一个正确的做法. 首先,我们会发现,对于你选择白色边的数目,随着数目的上涨,斜率是单调升高的. 那么这时候我们就 ...