C#&.NET 使用RabbitMQ实现一个分布式事件总线(二)

上文我们介绍了本地事件总线的一个简单实现,这次我们通过借助RabbitMQ, 来完成分布式事件总线的设计。
代码仓库:MaH.EventBus
Nuget: Install-Package MaH.EventBus -Version 0.7.0

设计思路

我希望可以通过指定 交换器名称、接收队列名称、连接信息,自动创建MQ连接与基础内容建设,以此来简化使用。
当然,对于单纯的生产者来说,队列名称不是必须的,因此AddRabbitMq()方法提供了重载。

 public void ConfigureServices(IServiceCollection services)
        {
    
    
            services.AddTransient<WeatherForecastHandler>();
            services.AddRabbitMq("MaHExchangeName", "MaHQueueName", cfg =>
            {
    
    
                cfg.Host = "localhost";
                cfg.Port = 5672;
                cfg.UserName = "guest";
                cfg.Password = "guest";
            });
            services.AddRabbitMqEventBus((eventBus, sp) =>
            {
    
    
                eventBus.Subscribe(typeof(WeatherUpdateEvent), new DefaultEventHandlerFactory(typeof(WeatherForecastHandler), sp.GetRequiredService<IServiceScopeFactory>()));
            });
                   }

使用方式

定义Weather Update 处理程序

public class WeatherForecastHandler : IEventHandler<WeatherUpdateEvent>
    {
    
    
        private readonly Guid Id;

        public WeatherForecastHandler()
        {
    
    
            Id = Guid.NewGuid();
        }

        public Task InvokeAsync(WeatherUpdateEvent eventData)
        {
    
    
            Console.WriteLine($"{Id}-{eventData.EventData.Summary}-{eventData.EventData.TemperatureC}");

            return Task.CompletedTask;
        }
    }

发布一个Weather Update 消息

 public class WeatherForecastService
    {
    
    
        private readonly IEventBus _eventBus;
        public WeatherForecastService(IEventBus eventBus)
        {
    
    
            _eventBus = eventBus;
        }
        public Task UpdateWeatherAsync()
        {
    
    
            var item = Datas[new Random().Next(Datas.Count)];
            item.TemperatureC = new Random().Next(-20, 55);

            return _eventBus.PublishAsync(new WeatherUpdateEvent(item));
        }

    }

默认配置

通过从IOC容器中获取ExchangeOption、QueueOption、RabbitMqOption这三个对象,来做RabbitMQ的相关配置。AddRabbitMq()方法为我们默认注册了这三个对象,并提供一些默认配置:

  • 交换器类型type:direct
  • 交换器持久化durable:true
  • 交换器自动删除auto delete:false
  • 队列持久化durable:true
  • 排他队列:false
  • 队列自动删除auto delete:false

我们通过工厂方法来获取MQ连接IConnection, 并且保证连接实例是唯一的。
MQ消费者模式有两种,同步和异步。此处我们通过设置DispatchConsumersAsync = true来使用异步模式,并且默认设置了AutomaticRecoveryEnabled = true

public class RabbitMqConnectionFactory : IRabbitMqConnectionFactory
    {
    
    
        public RabbitMqOption RabbitMqOption {
    
     get; }
        private IConnection _instance;
        private static readonly object Monitor = new object();

        public RabbitMqConnectionFactory(RabbitMqOption rabbitMqOption)
        {
    
    
            RabbitMqOption = rabbitMqOption;
        }

        public IConnection GetConnection()
        {
    
    
            if (_instance != null) return _instance;
            lock (Monitor)
            {
    
    
                if (_instance == null)
                {
    
    
                    _instance = new ConnectionFactory
                    {
    
    
                        HostName = RabbitMqOption.Host,
                        Port = RabbitMqOption.Port,
                        UserName = RabbitMqOption.UserName,
                        Password = RabbitMqOption.Password,
                        AutomaticRecoveryEnabled = true,
                        DispatchConsumersAsync = true
                    }.CreateConnection();
                }
            }

            return _instance;
        }
    }

另外,如何确定消费者队列与交换器之间的绑定关系也是一个问题。这里我采用了direct模式,将事件类型的FullName作为RoutingKey来进行绑定。

 		public void Bind(Type eventType)
        {
    
    
            Bind(eventType.FullName);
        }
        
         private void Bind(string routingKey)
        {
    
    
            Channel.QueueBind(QueueOption.Name, ExchangeOption.Name, routingKey);
        }

其实到这里,我们剩下的主要工作就是根据收到的MQ消息,以及其RoutingKey代表的类型,来反序列化成具体的事件对象,并根据其类型,找出订阅该事件的Handler,执行即可。可以参考我这里RabbitMqMessageConsumer的实现。

总结

到这里,一个简易的分布式事件总线就实现了。当然,一个合格的,大家愿意在生产环境应用的组件并不是那么容易实现,需要作者有极强的编程功底与开发经验,并且能经受住测试与实战考验才行。
我这里仅仅是做了一次尝试与探索,希望这篇博客可以帮助到大家。

猜你喜欢

转载自blog.csdn.net/qq_40404477/article/details/116765483