之前使用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服务+消息处理服务的更多相关文章

  1. .Net Core 商城微服务项目系列(七):使用消息队列(RabbitMQ)实现服务异步通信

    RabbitMQ是什么,怎么使用我就不介绍了,大家可以到园子里搜一下教程.本篇的重点在于实现服务与服务之间的异步通信. 首先说一下为什么要使用消息队列来实现服务通信:1.提高接口并发能力.  2.保证 ...

  2. .Net Core 商城微服务项目系列(二):使用Ocelot + Consul构建具备服务注册和发现功能的网关

    1.服务注册 在上一篇的鉴权和登录服务中分别通过NuGet引用Consul这个包,同时新增AppBuilderExtensions类: public static class AppBuilderEx ...

  3. .Net Core 商城微服务项目系列(八):购物车

    最近加班有点多,一周五天,四天加班到11点+,心很累.原因是我当前在的这个组比较特殊,相当于业务的架构组,要为其它的开发组提供服务和监控.所以最近更新的也少,不过这个元旦三天假应该会更新三篇. 这篇是 ...

  4. .Net Core 商城微服务项目系列(一):使用IdentityServer4构建基础登录验证

    这里第一次搭建,所以IdentityServer端比较简单,后期再进行完善. 1.新建API项目MI.Service.Identity,NuGet引用IdentityServer4,添加类InMemo ...

  5. .Net Core 商城微服务项目系列(十三):搭建Log4net+ELK+Kafka日志框架

    之前是使用NLog直接将日志发送到了ELK,本篇将会使用Docker搭建ELK和kafka,同时替换NLog为Log4net. 一.搭建kafka 1.拉取镜像 //下载zookeeper docke ...

  6. .Net Core 商城微服务项目系列(六):搭建自己的Nuget包服务器

    当我们使用微服务架构之后,紧接而来的问题便是服务之间的程序集引用问题,可能没接触过的同学不太理解这句话,都已经微服务化了为什么还要互相引用程序集,当然可以不引用.但是我们会有这样一种情况,我们的每个接 ...

  7. .Net Core 商城微服务项目系列(十四):分布式部署携程Apollo构建配置中心

    一.开场白 在系统设计里我们有很多配置希望独立于系统之外,而又能够被系统实时读取.但是在传统的系统设计里,配置信息通常是耦合在系统内的,比如.net里通常会放在App.config或者web.conf ...

  8. .Net Core 商城微服务项目系列(五):使用Polly处理服务错误

    项目进行微服务化之后,随之而来的问题就是服务调用过程中发生错误.超时等问题的时候我们该怎么处理,比如因为网络的瞬时问题导致服务超时,这在我本人所在公司的项目里是很常见的问题,当发生请求超时问题的时候, ...

  9. .Net Core 商城微服务项目系列(十):使用SkyWalking构建调用链监控(2019-02-13 13:25)

    SkyWalking的安装和简单使用已经在前面一篇介绍过了,本篇我们将在商城中添加SkyWalking构建调用链监控. 顺带一下怎么把ES设置为Windows服务,cd到ES的bin文件夹,运行ela ...

随机推荐

  1. HTML(一)简介,元素

    HTML简介 html实例: <!DOCTYPE html> 菜鸟教程 我的第一个标题 我的第一个段落 实例解析: <!DOCTYPE html> 声明为 HTML5 文档,不 ...

  2. B-xor_2019牛客暑期多校训练营(第四场)

    题意 给出n个数组(每组数个数不定),m个询问 l, r, x 序号在区间\([l,r]\)的每个数组是否都可以取出任意个数异或出x 题解 判断一个数组能否异或出x,是简单的线性基问题 判断多个线性基 ...

  3. CodeForces 416 B Appleman and Tree DP

    Appleman and Tree 题解: 定义dp[u][1] 为以u的子树范围内,u这个点已经和某个黑点相连的方案数. dp[u][0] 为在u的子树范围内, u这个点还未和某个黑点相连的方案数. ...

  4. Different Integers 牛客网暑期ACM多校训练营(第一场) J 离线+线状数组或者主席树

    Given a sequence of integers a1, a2, ..., an and q pairs of integers (l 1, r1), (l2, r2), ..., (lq, ...

  5. python数据结构——线性表

    线性表 线性表可以看作是一种线性结构(可以分为顺序线性结构,离散线性结构) 1. 线性表的种类: 顺序表 元素存储在一大块连续存储的地址中,首元素存入存储区的起始位置,其余元素顺序存放. (元素之间的 ...

  6. 2、pytest中文文档--使用和调用

    目录 使用和调用 通过python -m pytest调用pytest *pytest执行结束时返回的状态码 pytest命令执行结束,可能会返回以下六种状态码: *获取帮助信息 最多允许失败的测试用 ...

  7. Xshell配置公钥,免密码登陆

    下面讲解如何通过xshell配置使用PubKey 来登录服务器,可以减少通过password登录的麻烦. 1.先到用户的家目录, cd ~ 2.执行ssh-keygen -t dsa(or rsa) ...

  8. Linux基础提高_系统性能相关命令

    w 看系统的负载信息 用于显示已经登陆系统的用户列表,并显示用户正在执行的指令 uptime [root@localhost]#uptime 17:26:07 up 9:02, 3 users, lo ...

  9. IP地址和int互转

    /** * @author: yqq * @date: 2019/5/8 * @description: ip地址与int之间互换 * https://mp.weixin.qq.com/s?__biz ...

  10. Mybatis系列(四)注解

    Mybatis系列(四)注解 1.pom.xlm: <?xml version="1.0" encoding="UTF-8"?> <proje ...