.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和接口配置,调用指定接口,当然,这里只是简单的代码列子,想用在生产中必须要做好完善的日志调用记录、性能监控、健康检查以及服务器层面的集群防止单点故障。
.Net Core 商城微服务项目系列(十一):MQ消费端独立为Window服务+消息处理服务的更多相关文章
- .Net Core 商城微服务项目系列(七):使用消息队列(RabbitMQ)实现服务异步通信
RabbitMQ是什么,怎么使用我就不介绍了,大家可以到园子里搜一下教程.本篇的重点在于实现服务与服务之间的异步通信. 首先说一下为什么要使用消息队列来实现服务通信:1.提高接口并发能力. 2.保证 ...
- .Net Core 商城微服务项目系列(二):使用Ocelot + Consul构建具备服务注册和发现功能的网关
1.服务注册 在上一篇的鉴权和登录服务中分别通过NuGet引用Consul这个包,同时新增AppBuilderExtensions类: public static class AppBuilderEx ...
- .Net Core 商城微服务项目系列(八):购物车
最近加班有点多,一周五天,四天加班到11点+,心很累.原因是我当前在的这个组比较特殊,相当于业务的架构组,要为其它的开发组提供服务和监控.所以最近更新的也少,不过这个元旦三天假应该会更新三篇. 这篇是 ...
- .Net Core 商城微服务项目系列(一):使用IdentityServer4构建基础登录验证
这里第一次搭建,所以IdentityServer端比较简单,后期再进行完善. 1.新建API项目MI.Service.Identity,NuGet引用IdentityServer4,添加类InMemo ...
- .Net Core 商城微服务项目系列(十三):搭建Log4net+ELK+Kafka日志框架
之前是使用NLog直接将日志发送到了ELK,本篇将会使用Docker搭建ELK和kafka,同时替换NLog为Log4net. 一.搭建kafka 1.拉取镜像 //下载zookeeper docke ...
- .Net Core 商城微服务项目系列(六):搭建自己的Nuget包服务器
当我们使用微服务架构之后,紧接而来的问题便是服务之间的程序集引用问题,可能没接触过的同学不太理解这句话,都已经微服务化了为什么还要互相引用程序集,当然可以不引用.但是我们会有这样一种情况,我们的每个接 ...
- .Net Core 商城微服务项目系列(十四):分布式部署携程Apollo构建配置中心
一.开场白 在系统设计里我们有很多配置希望独立于系统之外,而又能够被系统实时读取.但是在传统的系统设计里,配置信息通常是耦合在系统内的,比如.net里通常会放在App.config或者web.conf ...
- .Net Core 商城微服务项目系列(五):使用Polly处理服务错误
项目进行微服务化之后,随之而来的问题就是服务调用过程中发生错误.超时等问题的时候我们该怎么处理,比如因为网络的瞬时问题导致服务超时,这在我本人所在公司的项目里是很常见的问题,当发生请求超时问题的时候, ...
- .Net Core 商城微服务项目系列(十):使用SkyWalking构建调用链监控(2019-02-13 13:25)
SkyWalking的安装和简单使用已经在前面一篇介绍过了,本篇我们将在商城中添加SkyWalking构建调用链监控. 顺带一下怎么把ES设置为Windows服务,cd到ES的bin文件夹,运行ela ...
随机推荐
- C#装箱与拆箱总结
装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作. 1. 装箱在值类型向引用类型转换时发生 2. 拆箱在引用类型向值类型转换时发生 光上述两句话不难理解,但是往深处了解,就需要一些篇幅来解释了 ...
- python中,一个函数想使用另一个函数中的变量
问题: 第一个函数中用到了变量a:第二个函数也想使用变量a. 解决方法: 在第一个函数中将变量a定义为全局变量,然后在第二个函数中,也写上global a即可. 示例: def func1(): gl ...
- 洛谷 P1070 道路游戏 DP
P1070 道路游戏 题意: 有一个环,环上有n个工厂,每个工厂可以生产价格为x的零钱收割机器人,每个机器人在购买后可以沿着环最多走p条边,一秒走一条,每条边不同时间上出现的金币是不同的,问如何安排购 ...
- poj 3468 A Simple Problem with Integers(原来是一道简单的线段树区间修改用来练练splay)
题目链接:http://poj.org/problem?id=3468 题解:splay功能比线段树强大当然代价就是有些操作比线段树慢,这题用splay实现的比线段树慢上一倍.线段树用lazy标记差不 ...
- Gym 101470 题解
A:Banks 代码: #include<bits/stdc++.h> using namespace std; #define Fopen freopen("_in.txt&q ...
- Idea各种快捷生成Live Template的代码整合
Idea各种快捷生成整合 快速生成method方法注释 配置方法 打开Idea ---> Settings , 搜索 live 点击右边的 + 号,创建模板组 Template Group,之后 ...
- js 大量数据优化,通用方法
当页面渲染太多标签时,会出现卡顿的,典型就是类似table数据太多时,非常卡顿.如果选择分页,没必要讨论,这儿只讨论采用滚动的情况.解决思路很简单,就是页面不展示出来的元素,从页面上删除掉,最难点在于 ...
- ie表单提交提示下载文件
使用jquery的ajaxform提交form表单 如果在html中多了 enctype ="multipart/form-data" 属性值 提交时就会在ie8中提示下载 ...
- fastjson对象,JSON,字符串,map之间的互转
1.对象与字符串之间的互转 将对象转换成为字符串 String str = JSON.toJSONString(infoDo); 字符串转换成为对象 InfoDo infoDo = JSON.pars ...
- eclipse中离线安装activit插件
离线安装activiti教程: 1.先下载压缩包和jar包 链接:https://pan.baidu.com/s/1hSToZt_4A262rUxc8KToCw 密码:j5r1 2.将下载好的jars ...