.Net Core下使用RabbitMQ比较完备的两种方案(虽然代码有点惨淡,不过我会完善)
一、前言
上篇说给大家来写C#和Java的方案,最近工作也比较忙,迟到了一些,我先给大家补上C#的方案,另外如果没看我上篇博客的人最好看一下,否则你可能看的云里雾里的,这里我就不进行具体的方案画图了;传送门
二、使用的插件
HangFire
一个开源的.NET任务调度框架,最大特点在于内置提供集成化的控制台,方便后台查看及监控,支持多种存储方式;在方案中主要使用定时任务做补偿机制,后期可能会封装一些,能通过页面的形式直接添加任务;
NLog
日志记录框架,方案中使用记录日志,后期可能回集成多个日志框架;
Autofac
依赖注入的框架,应该不用做过多介绍;
SqlSugar
ORM框架,这个从刚开始我就在使用了,在现在公司没有推行起来,不过在上两家公司都留下的遗产,据说还用的可以,当然我还是最佩服作者;
Polly
容错服务框架,类似于Java下的Hystrix,主要是为了解决分布式系统中,系统之间相互依赖,可能会因为多种因素导致服务不可用的而产生的一套框架,支持服务的超时重试、限流、熔断器等等;
RabbitMQ.Client
官方提供的C#连接RabbitMQ的SDK;
三、方案
模拟一个简单订单下单的场景,没有进行具体的实现。同时建议下游服务不要写在web端,最好以服务的形式奔跑,代码中是Web端实现的,大家不要这么搞。整体上还是实现了之前提到的两种方案:一是入库打标,二是延时队列(这块没有进行很好的测试,但是估计也没有很大的问题);当然也是有一些特点:RabbitMQ宕机情况下无需重启服务,网络异常的情况下也可以进行断线重连。接下来聊下代码和各方插件在系统中的具体应用:
项目结构:

RabbitMQExtensions:
采用Autofac按照单例的形式注入,采用Polly进行断线重连,也开启了自身断线重连和心跳检测机制,配置方面采用最简单的URI规范进行配置,有兴趣参考下官方,整体上这块代码还相对比较规范,以后可能也不会有太多调整;
/// <summary>
/// rabbitmq持久化连接
/// </summary>
public interface IRabbitMQPersistentConnection
{
bool IsConnected { get; } bool TryConnect(); IModel CreateModel();
}
/// <summary>
/// rabbitmq持久化连接具体实现
/// </summary>
public class DefaultRabbitMQPersistentConnection : IRabbitMQPersistentConnection
{
private readonly IConnectionFactory connectionFactory;
private readonly ILogger<DefaultRabbitMQPersistentConnection> logger; private IConnection connection; private const int RETTRYCOUNT = ; private static readonly object lockObj = new object();
public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger<DefaultRabbitMQPersistentConnection> logger)
{
this.connectionFactory = connectionFactory;
this.logger = logger;
} public bool IsConnected
{
get
{
return connection != null && connection.IsOpen;
}
} public void Cleanup()
{
try
{
connection.Dispose();
connection.Close();
connection = null; }
catch (IOException ex)
{
logger.LogCritical(ex.ToString());
}
} public IModel CreateModel()
{
if (!IsConnected)
{
connection.Close();
throw new InvalidOperationException("连接不到rabbitmq");
}
return connection.CreateModel();
} public bool TryConnect()
{
logger.LogInformation("RabbitMQ客户端尝试连接"); lock (lockObj)
{
if (connection == null)
{
var policy = RetryPolicy.Handle<SocketException>()
.Or<BrokerUnreachableException>()
.WaitAndRetry(RETTRYCOUNT, retryAttempt => TimeSpan.FromSeconds(Math.Pow(, retryAttempt)), (ex, time) =>
{
logger.LogWarning(ex.ToString());
}); policy.Execute(() =>
{
connection = connectionFactory.CreateConnection();
});
} if (IsConnected)
{
connection.ConnectionShutdown += OnConnectionShutdown;
connection.CallbackException += OnCallbackException;
connection.ConnectionBlocked += OnConnectionBlocked; logger.LogInformation($"RabbitMQ{connection.Endpoint.HostName}获取了连接"); return true;
}
else
{
logger.LogCritical("无法创建和打开RabbitMQ连接"); return false;
}
}
} private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e)
{ logger.LogWarning("RabbitMQ连接异常,尝试重连..."); Cleanup();
TryConnect();
} private void OnCallbackException(object sender, CallbackExceptionEventArgs e)
{ logger.LogWarning("RabbitMQ连接异常,尝试重连..."); Cleanup();
TryConnect();
} private void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
{ logger.LogWarning("RabbitMQ连接异常,尝试重连..."); Cleanup();
TryConnect();
}
}
OrderDal
SqlSugar的一些简单封装,有些小特点:大家可以可以通过配置来实现读写分离,采用仓储设计。如果不太喜欢这么写,也可以参考杰哥的做法;
//仓储设计
public interface IBaseDal<T> where T:class,new()
{
DbSqlSugarClient DbContext { get; } IBaseDal<T> UserDb(string dbName);
IInsertable<T> AsInsertable(T t);
IInsertable<T> AsInsertable(T[] t);
IInsertable<T> AsInsertable(List<T> t);
IUpdateable<T> AsUpdateable(T t);
IUpdateable<T> AsUpdateable(T[] t);
IUpdateable<T> AsUpdateable(List<T> t);
IDeleteable<T> AsDeleteable(); List<T> GetList();
Task<List<T>> GetListAnsync(); List<T> GetList(Expression<Func<T,bool>> whereExpression);
Task<List<T>> GetListAnsync(Expression<Func<T, bool>> whereExpression); List<T> GetList(Expression<Func<T, bool>> whereExpression, Expression<Func<T, object>> orderExpression, OrderByType orderByType = OrderByType.Desc);
Task<List<T>> GetListAnsync(Expression<Func<T, bool>> whereExpression, Expression<Func<T, object>> orderExpression, OrderByType orderByType = OrderByType.Desc); List<T> GetPageList(Expression<Func<T, bool>> whereExpression, PageModel page);
Task<List<T>> GetPageListAsync(Expression<Func<T, bool>> whereExpression, PageModel page); List<T> GetPageList(Expression<Func<T, bool>> whereExpression, PageModel page, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
Task<List<T>> GetPageListAsync(Expression<Func<T, bool>> whereExpression, PageModel page, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); int Count(Expression<Func<T, bool>> whereExpression);
Task<int> CountAsync(Expression<Func<T, bool>> whereExpression);
T GetById(dynamic id);
T GetSingle(Expression<Func<T, bool>> whereExpression);
Task<T> GetSingleAsync(Expression<Func<T, bool>> whereExpression);
T GetFirst(Expression<Func<T, bool>> whereExpression);
Task<T> GetFirstAsync(Expression<Func<T, bool>> whereExpression); bool IsAny(Expression<Func<T, bool>> whereExpression);
Task<bool> IsAnyAsync(Expression<Func<T, bool>> whereExpression); bool Insert(T t);
Task<bool> InsertAsync(T t);
bool InsertRange(List<T> t);
Task<bool> InsertRangeAsync(List<T> t);
bool InsertRange(T[] t);
Task<bool> InsertRangeAsync(T[] t);
int InsertReturnIdentity(T t);
Task<long> InsertReturnIdentityAsync(T t); bool Delete(Expression<Func<T, bool>> whereExpression);
Task<bool> DeleteAsync(Expression<Func<T, bool>> whereExpression);
bool Delete(T t);
Task<bool> DeleteAsync(T t);
bool DeleteById(dynamic id);
Task<bool> DeleteByIdAsync(dynamic id);
bool DeleteByIds(dynamic[] ids);
Task<bool> DeleteByIdsAsync(dynamic[] ids); bool Update(Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression);
Task<bool> UpdateAsync(Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression);
bool Update(T t);
Task<bool> UpdateAsync(T t);
bool UpdateRange(T[] t);
Task<bool> UpdateRangeAsync(T[] t); void BeginTran();
void CommitTran();
void RollbackTran(); }
OrderCommon
定义全局异常的中间件,还有包含一些用到的实体等等,这部分代码还可优化拆分一下;
OrderService
生产者和消费者的具体实现,这块我还想在改造一番,将消费和业务分割开,现在写的很凌乱,不建议这么写,先把代码放出来,看看大家赞同不赞同我的这些用法,可以讨论,也欢迎争论,虽然这块代码写的不好,但是其实里面涉及一些RabbitMQ回调函数的用法,也是比较重要的,没有这些函数也就实现不了我上面说那两个特点;
//RabbitMQ宕机以后回调
//客户端这块大家不要采用递归调用恢复链接
//具体为什么大家可以测试下,这里留点小疑问哈哈
connection.ConnectionShutdown += OnConnectionShutdown; //消费端异常以后回调
consumerchannel.CallbackException += OnOnConsumerMessageAndWriteMessageLogException;
Order
具体的调用者,大家应该根据方法名字就能区分出我上面提到的两种方案的设计,整体的设计思路都是最终一致,延时队列发送消息这块最终也是可以通过定时任务来实现最终一致,实现方式有很多种,简单来说下可以通过入库时生成的缓存机制,通过定时任务来进行补偿实现,这块我没有进行具体实现,有兴趣我们可以探讨下这个方案;
[Route("api/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
{
private readonly IBaseDal<OrderMessageLogEntity> orderBaseDal;
private readonly IMessageService<OrderMessageLogEntity> messageService;
private readonly IConsumerMessageService consumerMessageService;
private const string EXCHANGENAME = "order";
private const string QUEUENAME = "order";
private const string ROUTINGKEY = "order";
public OrderController(IBaseDal<OrderMessageLogEntity> orderBaseDal, IMessageService<OrderMessageLogEntity> messageService,IConsumerMessageService consumerMessageService)
{
this.orderBaseDal = orderBaseDal;
this.messageService = messageService;
this.consumerMessageService = consumerMessageService;
}
/// <summary>
/// 创建订单
/// </summary>
/// <returns></returns>
public ActionResult<bool> CreateOrder(long userId)
{
//创建订单成功
OrderEntity orderEntity = new OrderEntity();
Random random= new Random();
orderEntity.OrderId = random.Next();
orderEntity.OrderNo = random.Next();
orderEntity.UserId = userId;
orderEntity.OrderInfo = random.Next() + "详情";
//bool isCreateOrderSuccress = orderService.CreateOrder(orderId);
//if (!isCreateOrderSuccress)
//{
// throw new Exception("创建订单失败");
//}
//创建订单成功以后开始入消息记录库
//消息建议设计的冗余一些方便以后好查询
//千万级以后连表太困难
//建议冗余的信息有用户信息、订单信息、方便以后按照这个核对信息
//消息表的建议是按照不同的业务进行分表存储
Random messageRandom = new Random();
OrderMessageLogEntity orderMessageLog = new OrderMessageLogEntity();
orderMessageLog.MessageId = messageRandom.Next();
orderMessageLog.MessageInfo = orderEntity.OrderId+"订单信息";
orderMessageLog.Status = (int)MessageStatusEnum.SENDING;
orderMessageLog.OrderId = orderEntity.OrderId;
orderMessageLog.UserId = orderEntity.UserId;
orderMessageLog.CreateTime = DateTime.Now;
orderMessageLog.UpdateTime = DateTime.Now;
orderMessageLog.TryCount = ;
orderMessageLog.NextRetryTime = DateTime.Now.AddMinutes();
//必须保证消息先落库
bool isCreateOrderMessageLosSuccess = orderBaseDal.Insert(orderMessageLog);
if (!isCreateOrderMessageLosSuccess)
throw new Exception("消息入库异常");
Message message = new Message();
message.ExchangeName = EXCHANGENAME;
message.QueueName = QUEUENAME;
message.MessageId = orderMessageLog.MessageId;
message.RoutingKey = ROUTINGKEY;
message.Body = Encoding.UTF8.GetBytes(orderMessageLog.MessageInfo);
//落库成功以后开始发送消息到MQ
//这个地方采用最终一致而不去使用分布式事物最终一致
messageService.SendMessage(message, orderMessageLog);
return true;
}
/// <summary>
/// 消费订单
/// </summary>
/// <returns></returns>
public ActionResult<bool> ConsumerOrder()
{
Message message = new Message();
message.ExchangeName = EXCHANGENAME;
message.QueueName = QUEUENAME;
message.RoutingKey = ROUTINGKEY;
consumerMessageService.ConsumerMessage();
return true;
}
/// <summary>
/// 通过延时队列发送消息
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public ActionResult<bool> CreateDelayCreateOrder(long userId)
{
//创建订单成功
OrderEntity orderEntity = new OrderEntity();
Random random = new Random();
orderEntity.OrderId = random.Next();
orderEntity.OrderNo = random.Next();
orderEntity.UserId = userId;
orderEntity.OrderInfo = random.Next() + "详情";
//bool isCreateOrderSuccress = orderService.CreateOrder(orderId);
//if (!isCreateOrderSuccress)
//{
// throw new Exception("创建订单失败");
//}
//创建订单成功以后开始入消息记录库
//消息建议设计的冗余一些方便以后好查询
//千万级以后连表太困难
//建议冗余的信息有用户信息、订单信息、方便以后按照这个核对信息
//消息表的建议是按照不同的业务进行分表存储
Random messageRandom = new Random();
OrderMessageLogEntity orderMessageLog = new OrderMessageLogEntity();
orderMessageLog.MessageId = messageRandom.Next();
orderMessageLog.MessageInfo = orderEntity.OrderId + "订单信息";
orderMessageLog.Status = (int)MessageStatusEnum.SENDING;
orderMessageLog.OrderId = orderEntity.OrderId;
orderMessageLog.UserId = orderEntity.UserId;
orderMessageLog.CreateTime = DateTime.Now;
orderMessageLog.UpdateTime = DateTime.Now;
orderMessageLog.TryCount = ;
orderMessageLog.NextRetryTime = DateTime.Now.AddMinutes();
////必须保证消息先落库
//bool isCreateOrderMessageLosSuccess = orderBaseDal.Insert(orderMessageLog);
//if (!isCreateOrderMessageLosSuccess)
// throw new Exception("消息入库异常");
Message message = new Message();
message.ExchangeName = EXCHANGENAME;
message.QueueName = QUEUENAME;
message.MessageId = orderMessageLog.MessageId;
message.RoutingKey = ROUTINGKEY;
message.Body = Encoding.UTF8.GetBytes(orderMessageLog.MessageInfo);
//这里的设计是不进行落库
//假如两条消息都失败必须借助定时任务去对比消息库和订单库的消息id然后进行再补发
//剩下的只要有一条发送成功其实就能保证下游必然会消费调这条消息,排除下游消费异常的情况 这个地方我不在进行实现自己可脑补一下
//开始发送消息到MQ
messageService.SendMessage(message, orderMessageLog);
//发送延时消息
messageService.SendDelayMessage(message, orderMessageLog);
return true;
}
/// <summary>
/// 消费消息以后并入库
/// </summary>
/// <returns></returns>
public ActionResult<bool> ConsumerOrderAndWirteMessageLog()
{
consumerMessageService.ConsumerMessageAndWriteMessageLog();
return true;
}
/// <summary>
/// 消费延时消息
/// 进行二次检查核对
/// </summary>
/// <returns></returns>
public ActionResult<bool> ConsumerDelayOrder()
{
consumerMessageService.ConsumerDelayMessage();
return true;
}
}
HangfireExtensions
Hangfire定时框架,采用Mysql作为持久层的存储,写的也比较清晰,后期就是针对这些进行扩展,实现在界面就能添加定时任务;
四、结束
生产端和消费端这段代码写的凌乱,希望大家不要介意这一点,是有原因的,这里我就不说了。希望大家看到闪光点,不要在一点上纠结;下次会加入Elasticsearch和监控部分的时候我会把这块代码改掉,还大家一片整洁的世界;
Github地址:https://github.com/wangtongzhou520/rabbitmq.git 有什么问题大家可以问我;
欢迎大家加群438836709!欢迎大家关注我!

.Net Core下使用RabbitMQ比较完备的两种方案(虽然代码有点惨淡,不过我会完善)的更多相关文章
- .Net Core下发送WebRequest请求的两种方式
1.使用RestSharp.NetCore 2.使用WebApi请求方式
- windows下获取IP地址的两种方法
windows下获取IP地址的两种方法: 一种可以获取IPv4和IPv6,但是需要WSAStartup: 一种只能取到IPv4,但是不需要WSAStartup: 如下: 方法一:(可以获取IPv4和I ...
- Linux下实现秒级定时任务的两种方案
Linux下实现秒级定时任务的两种方案(Crontab 每秒运行): 第一种方案,当然是写一个后台运行的脚本一直循环,然后每次循环sleep一段时间. while true ;do command s ...
- 在ArcEngine下实现图层属性过滤的两种方法
转自chanyinhelv原文 在ArcEngine下实现图层属性过滤的两种方法 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* ...
- 比较好用的移动端适配的两种方案及flexible和px2rem-loader在webpack下的配置
移动端适配,目前自己常用的两种 方案,参考以下两篇好文 方案一:使用lib-flexible包 https://www.w3cplus.com/mobile/lib-flexible-for-html ...
- 详解Grunt插件之LiveReload实现页面自动刷新(两种方案)
http://www.jb51.net/article/70415.htm 含Grunt系列教程 这篇文章主要通过两种方案详解Grunt插件之LiveReload实现页面自动刷新,需要的朋友可以 ...
- [转载]Java操作Excel文件的两种方案
微软在桌面系统上的成功,令我们不得不大量使用它的办公产品,如:Word,Excel.时至今日,它的源代码仍然不公开已封锁了我们的进一步应用和开发.在我们实际开发企业办公系统的过程中,常常有客户这样子要 ...
- Spring Boot 配置文件密码加密两种方案
Spring Boot 配置文件密码加密两种方案 jasypt 加解密 jasypt 是一个简单易用的加解密Java库,可以快速集成到 Spring 项目中.可以快速集成到 Spring Boot 项 ...
- kettle 多表全删全插同步数据 两种方案
背景: 接到上级指示,要从外网某库把数据全部导入到内网,数据每天更新一次即可,大约几百万条数据,两个库结构一样,mysql的,两台数据库所在服务器都是windows server的,写个java接口实 ...
随机推荐
- python爬虫---实现项目(三) Selenium分析美团美食
上一期博客,我本来想爬取美团美食的,但是由于请求头太复杂,没有破解开其中的几个参数,所以放弃,这次我们来用selenium来模式浏览器抓取数据,我们先来简单看一下流程: 1,利用selenium驱动浏 ...
- shell脚本,一个字符一个字符输出。
[root@localhost wyb]# cat file abc def abc 789de f567 [root@localhost wyb]# cat fffile.sh #!/bin/bas ...
- C++_STL基础案例
C++ C++三种容器:list.vector和deque的区别:https://blog.csdn.net/gogokongyin/article/details/51178378 一.容器 小常识 ...
- 【数论】贝壳找房计数比赛&&祭facinv
震惊!阶乘逆元处理背后竟有如此玄机…… 题目描述 贝壳找房举办了一场计数比赛,比赛题目如下. 给一个字符串 s 和字符串 t,求出 s 的所有去重全排列中 t 出现的次数.比如aab的去重全排列为aa ...
- python多进程与多线程编程
进程(process)和线程(thread)是非常抽象的概念.多线程与多进程编程对于代码的并发执行,提升代码运行效率和缩短运行时间至关重要.下面介绍一下python的multiprocess和thre ...
- python中函数定义之实参、形参
一般在函数的定义中,会有一类变量---形参,它是函数完成其工作的一项信息.实参往往是调用函数时传递给函数的信息.我们在调用函数时,将要让函数使用的信息放在括号内.例如定义一个函数def greet_u ...
- perl学习之裸字
use strict包含3个部分.其中之一(use strict "subs")负责禁止乱用的裸字. 这是什么意思呢? 如果没有这个限制,下面的代码也可以打印出"hell ...
- Python把类当做字典来访问
定义一个类将它实例化,我们可以通过obj.属性来访问类的属性,如果想获取类的所有实例变量,我们可以使用obj.__dict__来访问,如下: class A: def __init__(self): ...
- IRQ中断处理流程
基于Linux2.6.30.4分析IRQ中断的处理流程. 1.中断入口 /* arch/arm/kenel/entry-armv.S*/ b vector_irq + stubs_offset 2.v ...
- Python中的序列化以及pickle和json模块介绍
Python中的序列化指的是在程序运行期间,变量都是在内存中保存着的,如果我们想保留一些运行中的变量值,就可以使用序列化操作把变量内容从内存保存到磁盘中,在Python中这个操作叫pickling,等 ...