.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 ...
随机推荐
- 学习HTML之后的感受
自从学习了HTML之后,感觉自己每天面对密密麻麻的代码,都有了一种密集恐惧症的感觉,作为一个计算机行业的小白,我十分渴望在计算机行业有所建树,以前计算机对我来说是一个神秘的领域.现在我正在努力进入这个 ...
- SPSS数据分析方法不知道如何选择
一提到数学,高等数学,线性代数,概率论与数理统计,数值分析,空间解析几何这些数学课程,头疼呀.作为文科生,遇见这些课程时,通常都是各种寻求帮助,班上有位宅男数学很厉害,各种被女生‘围观’,这数学为 ...
- Android Studio安卓学习笔记(一)安卓与Android Studio运行第一个项目
一:什么是安卓 1.Android是一种基于Linux的自由及开放源代码的操作系统. 2.Android操作系统最初由AndyRubin开发,主要支持手机. 3.Android一词的本义指“机器人”, ...
- 洛谷P1273 有线电视网 树上分组背包DP
P1273 有线电视网 )逼着自己写DP 题意:在一棵树上选出最多的叶子节点,使得叶子节点的值 减去 各个叶子节点到根节点的消耗 >= 0: 思路: 树上分组背包DP,设dp[u][k] 表示 ...
- 2017福建省赛 L Tic-Tac-Toe 模拟
Kim likes to play Tic-Tac-Toe. Given a current state, and now Kim is going to take his next move. Pl ...
- 如何使用React搭建项目
1.首先说明node.js.npm.cnpm分别是做什么的? node.js简单的说 Node.js 就是运行在服务端的 JavaScript,安装了node.js默认安装了npm,可以使用npm - ...
- HTTPS加密协议
使用JDK自带的keytool工具生成一个证书(keystore文件),其中包含了密钥. a.在命令行输入以下命令:keytool -genkey -alias tbb -keyalg RSA -ke ...
- android.intent.category.BROWSABLE
参考: http://blog.csdn.net/annkie/article/details/8349626 http://xiechengfa.iteye.com/blog/1004991 BRO ...
- 第五场周赛(字符串卡常个人Rank赛)——题解
本次题目因为比较简单,除了个别题目,其余题目我只写一个思路不再贴代码. 先是Div.2的题解 A题奇怪的优化,把递归函数改成2*fun(...)即可,其实看懂程序也不难,就是求a*2b: B题你会st ...
- 32 (OC)* keyChain的本质
1:它是一个sqlite数据库,其保存的所有数据都是加密过的. 2:Keychain是加密规则(key)的集合.每个规则必须含有以下三个要素:认证算法.认证密钥(加密字符串).规则的时间. 3:key ...