• .Net Core 商城微服务项目系列(十一):MQ消费端独立为Window服务+消息处理服务


    之前使用MQ的时候是通过封装成dll发布Nuget包来使用,消息的发布和消费都耦合在使用的站点和服务里,这样会造成两个问题:

    1.增加服务和站点的压力,因为每次消息的消费就意味着接口的调用,这部分的压力都加在了使用的站点和服务的机器上。

    2.增加修改的复杂性,如果我们需要加两条消费日志,都需要再发布一个版本重新通过dll引用。

    所以我们需要做以下两方面的工作:

    1.MQ的接收拆分为Windows服务,通过zokeerper实现主从防止单点故障。

    2.MQ的消费这里做成单独的WebApi服务。

    这样做的好处有以下几方面:

    1.解耦。MQ的消费从使用的站点和服务中被拆分出来,减轻服务压力。

    2.增加程序的可维护和可调试性。

    3.单独部署提高吞吐量。

    首先我们先来看下MQ的消费服务端,其实就是把之前调接口的方法单独放到了WebApi中,这样可以单独部署,减轻服务器压力:

            /// <summary>
            /// MQ消费到指定的服务接口
            /// </summary>
            [HttpPost]
            public async Task<ConsumerProcessEventResponse> ConsumerProcessEventAsync([FromBody]ConsumerProcessEventRequest request)
            {
                ConsumerProcessEventResponse response = new ConsumerProcessEventResponse();
                try
                {
                    _logger.LogInformation($"MQ准备执行ConsumerProcessEvent方法,RoutingKey:{request.RoutingKey} Message:{request.MQBodyMessage}");
                    using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
                    {
                        //获取绑定该routingKey的服务地址集合
                        var subscriptions = await StackRedis.Current.GetAllList(request.RoutingKey);
                        if (!subscriptions.Any())
                        {
                            //如果Redis中不存在 则从数据库中查询 加入Redis中
                            var queryRoutingKeyApiUrlResponse = _apiHelperService.PostAsync<QueryRoutingKeyApiUrlResponse>(ServiceAddress.QueryRoutingKeyApiUrlAsync, new QueryRoutingKeyApiUrlRequest { RoutingKey = request.RoutingKey });
                            if (queryRoutingKeyApiUrlResponse.Result != null && queryRoutingKeyApiUrlResponse.Result.ApiUrlList.Any())
                            {
                                subscriptions = queryRoutingKeyApiUrlResponse.Result.ApiUrlList;
                                Task.Run(() =>
                                {
                                    StackRedis.Current.SetLists(request.RoutingKey, queryRoutingKeyApiUrlResponse.Result.ApiUrlList);
                                });
                            }
                        }
                        if(subscriptions!=null && subscriptions.Any())
                        {
                            foreach (var apiUrl in subscriptions)
                            {
                                Task.Run(() =>
                                {
                                    _logger.LogInformation(request.MQBodyMessage);
                                });
    
                                //这里需要做判断 假如MQ要发送到多个服务接口 其中一个消费失败 应该将其单独记录到数据库 而不影响这个消息的确认
    //先做个备注 以后添加这个处理
    await _apiHelperService.PostAsync(apiUrl, request.MQBodyMessage); } _logger.LogInformation($"MQ执行ProcessEvent方法完成,RoutingKey:{request.RoutingKey} Message:{request.MQBodyMessage}"); } } } catch(Exception ex) { response.Successful = false; response.Message = ex.Message; _logger.LogError(ex, $"MQ消费失败 RoutingKey:{request.RoutingKey} Message:{request.MQBodyMessage}"); } return response; }

    这个WebApi只有这一个方法,就是根据RoutingKey查找对应的MQ配置,然后根据配置的接口地址调用指定的接口,比较简单哈,之前也写过,就不细说了。

    我们来看接收MQ消息的Windows服务端,MQ首次使用都需要重新绑定Routingkey、队列和交换器,所以我在Monitor服务里写了一个绑定的方法,在Windows服务端启动的时候调用一次:

    public class MQConsumerService
        {
            private readonly IApiHelperService _apiHelperService;
            private ILog _logger;
    
            public MQConsumerService(IApiHelperService apiHelperService,ILog logger)
            {
                _apiHelperService = apiHelperService;
                _logger = logger;
            }
    
            /// <summary>
            /// 发送MQ到MQ消费服务端
            /// </summary>
            /// <param name="routingKey"></param>
            /// <param name="message"></param>
            public void ProcessEvent(string routingKey, string message)
            {
                try
                {
                    _logger.Info($"MQ准备执行ProcessEvent方法,RoutingKey:{routingKey} Message:{message}");
                    _apiHelperService.PostAsync<ConsumerProcessEventResponse>(ServiceUrls.ConsumerProcessEvent,new ConsumerProcessEventRequest { RoutingKey=routingKey,MQBodyMessage=message});
                }
                catch(Exception ex)
                {
                    _logger.Error($"MQ发送消费服务端失败 RoutingKey:{routingKey} Message:{message}",ex);
                }
            }
    
            /// <summary>
            /// MQ初始化 调用队列交换器绑定接口
            /// </summary>
            /// <returns></returns>
            public async Task MQSubscribeAsync()
            {
                try
                {
                    var response= await _apiHelperService.PostAsync<MQSubscribeResponse>(ServiceUrls.MQSubscribe, new MQSubscribeRequest());
                    if(!response.Successful)
                    {
                        _logger.Error($"MQ绑定RoutingKey队列失败: {response.Message}");
                    }
                }
                catch(Exception ex)
                {
                    _logger.Error($"MQ绑定RoutingKey队列失败",ex);
                }            
            }
        }

    这里为了简单起见,交换器和队列使用的都是同一个,路由方式是“direct”模式,之后会继续修改的,先跑起来再说:

    static void Main(string[] args)
            {
                //交换器(Exchange)
                const string BROKER_NAME = "mi_event_bus";
                //队列(Queue)
                var SubscriptionClientName = "RabbitMQ_Bus_MI";
                //log4net日志加载
                ILoggerRepository repository = LogManager.CreateRepository("MI.WinService.MQConsumer");
                XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));
                ILog log = LogManager.GetLogger(repository.Name, "MI.WinService.MQConsumer");
                //依赖注入加载
                IServiceCollection serviceCollection = new ServiceCollection();
                //WebApi调用类
                serviceCollection.AddTransient<IApiHelperService, ApiHelperService>();
                var serviceProvider = serviceCollection.AddHttpClient().BuildServiceProvider();
                serviceProvider.GetService<ILogger>();
                var apiHelperService = serviceProvider.GetService<IApiHelperService>();
                //MQ消费类(发送MQ消息调用接口、绑定队列交换器)
                MQConsumerService consumerService = new MQConsumerService(apiHelperService,log);
    
                //MQ连接类
                ConnectionFactory factory = new ConnectionFactory
                {
                    UserName = "",
                    Password = "",
                    HostName = ""
                };
    
                var connection = factory.CreateConnection();
                var channel = connection.CreateModel();
    
                channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
    
                channel.QueueDeclare(queue: SubscriptionClientName, durable: true, exclusive: false, autoDelete: false, arguments: null);
    
                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += (ch, ea) =>
                {
                    //发送到MQ消费服务端
                    var message = Encoding.UTF8.GetString(ea.Body);
                    log.Info($"MQ准备消费消息 RoutingKey:{ea.RoutingKey} Message:{message}");
    
                    //发送到MQ消费服务端MQStationServer
                    Task result= Task.Run(() =>
                    {
                        consumerService.ProcessEvent(ea.RoutingKey, message);
                    });
                    if(!result.IsFaulted)
                    {
                        //确认ack
                        channel.BasicAck(ea.DeliveryTag, false);
                    }
                };
                channel.BasicConsume(SubscriptionClientName, false, consumer);
                Console.WriteLine("消费者已启动!");
    
                //绑定队列RoutingKey
                Task taskResult= Task.Run(async() =>
                {
                    await consumerService.MQSubscribeAsync();
                });
    
                taskResult.Wait();
    
    
                Console.WriteLine("队列RoutingKey绑定完成!");
    
                Console.ReadKey();
                channel.Dispose();
                connection.Close();
            }

    最后梳理下消费端消费MQ流程:

    MQ发布后,Windows服务端会受到MQ消息,然后通过调用接口将消息发送到MQ消费服务端,通过RoutingKey从数据库查找对应的MQ和接口配置,调用指定接口,当然,这里只是简单的代码列子,想用在生产中必须要做好完善的日志调用记录、性能监控、健康检查以及服务器层面的集群防止单点故障。

  • 相关阅读:
    四、创建多线程、数据共享
    operator函数操作符
    三、线程传参
    二、线程创建、结束
    一、并发、进程、线程概念
    bagging和boosting的区别
    ID3,C4.5和CART三种决策树的区别
    7创建型模式之建造者模式
    6创建型模式之工厂模式与抽象工厂模式
    5创建型模式之简单工厂模式
  • 原文地址:https://www.cnblogs.com/weiBlog/p/10549248.html
Copyright © 2020-2023  润新知