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是推 ...
随机推荐
- 计算机网络要点---TCP
计算机网络要点---TCP 浏览器在通过域名通过dns服务器找到你的服务器外网ip,将http请求发送到你的服务器,在tcp3次握手之后(http下面是tcp/ip),通过tcp协议开始传输数据,你的 ...
- mycat数据库集群系列之mysql主从同步设置
最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据库安装.mycat部署安装.数据库之读写分离 ...
- linux上传下载小工具lrzsz
工具压缩包链接 密码:zbef 1.将压缩包放到linux的任意目录下,执行:tar zxvf lrzsz-0.12.20.tar.gz 解压压缩包 2.cd lrzsz-0.12.20 3../co ...
- 远程控制(远控Bin)
一.概念 控制端:运行在攻击者的电脑中,负责控制其他肉鸡: 被控制端:需要去生成,运行在肉鸡上,木马病毒 二.使用远控Bin工具实现内网远程控制 Step1:打开Kill.exe,点击系统设置,修改监 ...
- 目录扫描、Nmap
一.基本定义 1.目录扫描: 扫描站点的目录,寻找敏感文件(目录名.探针文件.后台.robots.txt.备份文件等). 2.目录:站点结构,权限控制不严格. 3.探针文件:服务器配置信息,例:php ...
- src rpm 下载地址
drbd: http://mirror.rackspace.com/elrepo/elrepo/el7/SRPMS/ rabbitmq: https://dl.bintray.com/rabbitmq ...
- Three.js学习1_快速入门
快速上手, 搭建第一个3D场景 最重要的一步, 先下载three.js, 引入网页中 <script src="./three.js"></script> ...
- Oracle-Over()函数高级用法
- 【免费】windows下如何生成tar.gz,一键生成tar.gz
废话 一.实验背景 tar.gz 是Linux和Unix下面比较常用的格式,一条命令就可以把文件压缩打包成tar.gz格式,然而这种格式在windows并不多见. Linxu服务器上,tar.gz 包 ...
- Selenium执行js脚本
如何使用Selenium来执行Javascript脚本呢 Selenium中提供了一个方法:execute_script 来执行js脚本 return 可以返回js的返回结果 execute_scri ...