之前使用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. python暴力算法快乐数

    编写一个算法来判断一个数是不是"快乐数". 一个"快乐数"定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 ...

  2. 2019DX#6

    Solved Pro.ID Title Ratio(Accepted / Submitted)   1001 Salty Fish 16.28%(7/43)  OK 1002 Nonsense Tim ...

  3. POJ-3169 Layout (差分约束+SPFA)

    POJ-3169 Layout:http://poj.org/problem?id=3169 参考:https://blog.csdn.net/islittlehappy/article/detail ...

  4. 良许 | 听说,有个同事因为关闭服务器被打进 ICU ……

    提问:你是如何关闭电脑的? 普通青年 文艺青年 二逼青年 你是属于哪一种呢? 实话说, 这三种良许都干过~ 还好我没有对服务器这么做, 否则-- 分分钟被打进 ICU -- 1. 关机命令知多少 对于 ...

  5. python自学Day02(自学书籍python编程从入门到实践)

    第三章 列表简介 3.1 列表是什么 按特定顺序排列的元素组成. 元素类型可以是任意数据类型. 元素之间没有任何的关系. 在python中用中括号 [] 括起来并用 ,号隔开 3.1.1 访问列表元素 ...

  6. 分布式之分布式事务、分布式锁、接口幂等性、分布式session

    一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...

  7. 对于synchronized的理解

    一.synchronized 同步关键字,分为同步代码块和同步函数 二.对synchronized的理解(未加static关键字)(以下所说:对同步方法和同步代码块均适用) 对象的创建是以类为模板的 ...

  8. .NET Core 3.0之深入源码理解Host(二)

      写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...

  9. 实验吧CTF练习题---WEB---FALSE解析

    实验吧web之FALSE 地址:http://www.shiyanbar.com/ctf/1787 flag值:CTF{t3st_th3_Sha1}   解题步骤: 1.点开题目链接,观察题意 2.题 ...

  10. 在一个升序数组中添加最少的数字,使得从1--n之间所有的数都能用数组中几个数的和表示

    一个Java的笔试题上面遇到的题,当时没有做出来. 拆分: 序列升序 1--n所有的数都要能表示 用数组中数字的和表示 添加最少的数字 思路:这个要先从小的数开始表示,因为大的数可以用小数表示. 1- ...