RabbitMQ消息积压的几种解决思路
在日常工作中使用RabbitMQ偶尔会遇不可预料的情况导致的消息积压,一般出现消息积压基本上分为几种情况:
消费者消费消息的速度赶不上生产速度,这总问题主要是业务逻辑没设计好消费者和生产者之间的平衡,需要改业务流程或逻辑已保证消费度跟上生产消息的速,譬如增加消费者的数量等。
消费者出现异常,导致一直无法接收新的消息,这种问题需要排查消费的逻辑是不是又问题,需要优化程序。
除了上面的者两种问题,还有一些其他情况会导致消息积压,譬如一些系统是无法预计成产消息的速度和频率,又或者消费者的速度已经被限制,不能通过加新的消费者来解决,譬如不同的系统间的API对接,对接那一方就做了请求频率的限制,或者对方系统承受不了太大的并发,还有一些系统如果是面对企业客户,譬如电商,物流,仓储等类似平台系统的客户的下单是没有规律的或者集中某一个时间段下单的,这种就不能简单的通过加消费者来解决,就需要分析具体业务来避免消息积压。
针对这种情况,我想到了4中解决思路:
拆分MQ,生产者一个MQ,消费者一个MQ,写一个程序监听生产者的MQ模拟消费速度(譬如线程休眠),然后发送到消费者的MQ,如果消息积压则只需要处理生产者的MQ的积压消息,不影响消费者MQ
拆分MQ,生产者一个MQ,消费者一个MQ,写一个程序监听生产者的MQ,定义一个全局静态变量记录上一次消费的时间,如果上一次时间和当前时间只差小于消费者的处理时间,则发送到一个延迟队列(可以使用死信队列实现)发送到消费者的MQ,如果消息积压则只需要处理生产者的MQ的积压消息,不影响消费者MQ
使用Redis的List或ZSET做接收消息缓存,写一个程序按照消费者处理时间定时从Redis取消息发送到MQ
设置消息过期时间,过期后转入死信队列,写一个程序处理死信消息(重新如队列或者即使处理或记录到数据库延后处理)
其中使用延时队列会相对来说逻辑简单,业务逻辑变更也不大,在RabbitMQ中,可使用死信来及延时队列插件rabbitmq_delayed_message_exchange两种方式实现延时队列。
使用插件可以在官网找到:https://www.rabbitmq.com/community-plugins.html
插件的安装及使用方式就不做介绍了,主要介绍下使用死信来实现延时队列,原理就是将消息发送到一个死信队列,并设置过期时间,过期后将死信转发到要处理的消息队列。
生产者相关代码:
/// <summary>
/// 发送延时队列消息
/// </summary>
/// <param name="message"></param>
/// <param name="queueName"></param>
/// <param name="prefetchCount">默认20</param>
public void SendDelayQueues(string message, string queueName,double delayMilliseconds,string beDeadLetterPrefix="beDeadLetter_")
{
#region 死信到期后转入的交换机及队列
//死信转入新的队列的路由键(消费者使用的路由键)
var routingKey = queueName;
var exchangeName = queueName;
//定义队列
Channel.QueueDeclare(queue: queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
//定义交换机
Channel.ExchangeDeclare(exchange: exchangeName,
type: "direct");
//队列绑定到交换机
Channel.QueueBind(queue: queueName,
exchange: exchangeName,
routingKey: routingKey);
#endregion
//将变成死信的队列名
var beDeadLetterQueueName = beDeadLetterPrefix + queueName;
//将变成死信的交换机名
var beDeadLetterExchangeName = beDeadLetterPrefix + queueName;
//定义一个有延迟的交换机来做死信(该消息不能有消费者,不然无法变成死信)
Channel.ExchangeDeclare(exchange:beDeadLetterExchangeName ,
type: "direct");
//定义该延迟消息过期变成死信后转入的交换机(消费者需要绑定的交换机)
//Channel.ExchangeDeclare(exchange: queueName,type: "direct");
var dic = new Dictionary<string, object>();
//dic.Add("x-expires", 30000);
//dic.Add("x-message-ttl", 12000);//队列上消息过期时间,应小于队列过期时间
dic.Add("x-dead-letter-exchange", queueName);//变成死信后转向的交换机
dic.Add("x-dead-letter-routing-key",routingKey);//变成死信后转向的路由键
//定义将变成死信的队列
Channel.QueueDeclare(queue: beDeadLetterQueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: dic);
//队列绑定到交换机
Channel.QueueBind(queue: beDeadLetterQueueName,
exchange: beDeadLetterExchangeName,
routingKey: routingKey);
//不要同时给一个消费者推送多于prefetchCount个消息, ushort prefetchCount = 20
//Channel.BasicQos(prefetchSize: 0, prefetchCount: prefetchCount, global: false);
var body = Encoding.UTF8.GetBytes(message);
var properties = Channel.CreateBasicProperties();
properties.Persistent = true;
properties.DeliveryMode = 2;//持久化消息
//过期时间
properties.Expiration = delayMilliseconds.ToString();
Channel.BasicPublish(exchange: beDeadLetterExchangeName,
routingKey: routingKey,
basicProperties: properties,
body: body);
}
消费者相关代码:
/// <summary>
/// 设置延迟队列接收的事件
/// </summary>
/// <param name="action"></param>
/// <param name="queueName"></param>
/// <param name="prefetchCount">默认1</param>
/// <param name="autoAck"></param>
/// <param name="consumerCount"></param>
public void SetDelayQueuesReceivedAction(Action<string> action, string queueName, ushort prefetchCount = 1,
bool autoAck = false, int consumerCount = 1)
{
if (prefetchCount < 1)
{
throw new Exception("consumerCount must be greater than 1 !");
}
var exchangeName = queueName;
var routingKey = queueName;
for (int i = 0; i < consumerCount; i++)
{
var Channel = Connection.CreateModel();
//定义队列
Channel.QueueDeclare(queue: queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
//定义交换机
Channel.ExchangeDeclare(exchange: exchangeName,
type: "direct");
//队列绑定到交换机
Channel.QueueBind(queue: queueName,
exchange: exchangeName,
routingKey: routingKey);
//不要同时给一个消费者推送多于prefetchCount个消息
Channel.BasicQos(prefetchSize: 0, prefetchCount: prefetchCount, global: false);
ChannelList.Add(Channel);
var consumer = new EventingBasicConsumer(Channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
//Console.WriteLine("处理消费者ConsumerTag:" + ea.ConsumerTag);
action(message);
//手动确认消息应答
Channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};
//autoACK自动消息应答设置为false
Channel.BasicConsume(queue: queueName, autoAck: autoAck, consumer: consumer);
}
}
完整代码实现放到了Github:https://github.com/tanyongzheng/TZ.RabbitMQ
RabbitMQ消息积压的几种解决思路的更多相关文章
- mycat登录报错Host 'XXX' is blocked because of many connection errors的另一种解决思路
报错时机 使用了mycat,而不是单纯使用了mysql. 报错信息 ERROR 1129 (HY000): Host '1.23.22.18' is blocked because of many c ...
- Top K问题的两种解决思路
Top K问题在数据分析中非常普遍的一个问题(在面试中也经常被问到),比如: 从20亿个数字的文本中,找出最大的前100个. 解决Top K问题有两种思路, 最直观:小顶堆(大顶堆 -> 最小1 ...
- Rabbitmq消息积压清理
#!/bin/bash QUE=`rabbitmqctl list_queues messages_ready name durable|grep -v "^Listing" |g ...
- ADO.NET 使用DELETE语句批量删除操作,提示超时,删除失败,几种优化解决思路
起因是如此简单的一句sql 提示:Timeout 时间已到.在操作完成之前超时时间已过或服务器未响应. 提供几种解决思路: 1.检查WHERE条件中字段是否已建索引 2.检查是否被其他表引用,引用表外 ...
- Highcharts在IE中不能一次性正常显示的一种解决办法
由于客户要求必须在IE浏览器下兼容图表,故选用了兼容性较好的Highcharts.另外说一句,博主尝试过ichartjs.ECharts.YUI,兼容性都没有Highcharts给力(所有的兼容性问题 ...
- 数据权限设计——基于EntityFramework的数据权限设计方案:一种设计思路
前言:“我们有一个订单列表,希望能够根据当前登陆的不同用户看到不同类型的订单数据”.“我们希望不同的用户能看到不同时间段的扫描报表数据”.“我们系统需要不同用户查看不同的生产报表列”.诸如此类,最近经 ...
- 改变input的值不会触发change事件的解决思路
通常来说,如果我们自己通过 value 改变了 input 元素的值,我们肯定是知道的,但是在某些场景下,页面上有别的逻辑在改变 input 的 value 值,我们可能希望能在这个值发生变化的时候收 ...
- Highcharts在IE8中不能一次性正常显示的一种解决办法
由于客户要求必须在IE浏览器下兼容图表,故选用了兼容性较好的Highcharts.另外说一句,博主尝试过ichartjs.ECharts.YUI,兼容性都没有Highcharts给力(所有的兼容性问题 ...
- Angular 2的HTML5 pushState在ASP.NET Core上的解决思路
Angular 2的HTML5 pushState在ASP.NET Core上的解决思路 正如Angular 2在Routing & Navigation中所提及的那样,Angular 2是推 ...
随机推荐
- 【HNOI2010】弹飞绵羊 - LCT
题目描述 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装置设定初始弹力系 ...
- 使用css设置边框背景图片
使用css的特有属性,给不同的盒子添加边框图片. 为什么会有这一场景呢.因为,UI给我们前端的边框图片可能未必适合我们当前的内容. 这里我们主要使用到的属性有: border-image-source ...
- JavaScript学习系列博客_33_JavaScript String对象
String对象 在底层,字符串是以数组的形式保存的.比如说一个字符串"String"以["H","t","r",&qu ...
- 随机陷阱和P值
如果让大家写一个50次的抛硬币实验的可能结果(头像H或字T),多半人在连续三个一样的后,会换一下.因为大家都知道,连续一样的越多,概率越小,越不可能发生.大部分人不会去想,其实HHHTT和HHHHH发 ...
- Java中实现对集合中对象按中文首字母排序
有一个person对象如下: public class Person { private String id;private String nam; } 一个list集合如下: List<Emp ...
- [转]camera教程
camera教程 Lens一般由几片透镜组成透镜结构,按材质可分为塑胶透镜(plastic)或玻璃透镜(glass),玻璃镜片比树脂镜片贵.塑胶透镜其实是树脂镜片,透光率和感光性等光学指标比不上镀膜镜 ...
- codeforce Round #605(Div.3)
A. Three Friends 题目链接 题目意思:有三个好朋友a,b,c.他们在一个坐标轴上,他们的位置分别是xa 和xb ,xc,他们三个人都可以往前或者往后走一步,只能走一步.问你他们走了(也 ...
- .Net MongoDB批量修改集合中子集合的字段
环境:.Net Core 3.1 (需要导入.Net MongoDB的驱动) 模型 /// <summary> /// 收藏 /// </summary> public cla ...
- 【Android】安卓开发之activity如何传值到fragment,activity与fragment传值
作者:程序员小冰,GitHub主页:https://github.com/QQ986945193 新浪微博:http://weibo.com/mcxiaobing 大家知道,我们利用activity使 ...
- Android中的Activity四种启动模式(launchMode)
转载:http://blog.csdn.net/cjjky/article/details/7533110 我们在开发项目的过程中,会涉及到该应用中多个Activity组件之间的跳转 ...