05 | 消息积压了该如何处理?

1.应用场景

https://blog.csdn.net/william_n/article/details/104025408

2.学习/操作

2.1 阅读文档

这节课我们来聊一聊关于消息积压的问题。

 

据我了解,在使用消息队列遇到的问题中,消息积压这个问题,应该是最常遇到的问题了,并且,这个问题还不太好解决。

我们都知道,消息积压的直接原因,一定是系统中的某个部分出现了性能问题,来不及处理上游发送的消息,才会导致消息积压。

所以,我们先来分析下,在使用消息队列时,如何来优化代码的性能,避免出现消息积压。

然后再来看看,如果你的线上系统出现了消息积压,该如何进行紧急处理,最大程度地避免消息积压对业务的影响

 

优化性能来避免消息积压

在使用消息队列的系统中,对于性能的优化,主要体现在生产者和消费者这一收一发两部分的业务逻辑中。对于消息队列本身的性能,你作为使用者,不需要太关注。为什么这么说呢?

 

主要原因是,对于绝大多数使用消息队列的业务来说,消息队列本身的处理能力要远大于业务系统的处理能力。主流消息队列的单个节点,消息收发的性能可以达到每秒钟处理几万至几十万条消息的水平,还可以通过水平扩展 Broker 的实例数成倍地提升处理能力。

而一般的业务系统需要处理的业务逻辑远比消息队列要复杂,单个节点每秒钟可以处理几百到几千次请求,已经可以算是性能非常好的了。所以,对于消息队列的性能优化,我们更关注的是,在消息的收发两端,我们的业务代码怎么和消息队列配合,达到一个最佳的性能。

 

1. 发送端性能优化

发送端业务代码的处理性能,实际上和消息队列的关系不大,因为一般发送端都是先执行自己的业务逻辑,最后再发送消息。如果说,你的代码发送消息的性能上不去,你需要优先检查一下,是不是发消息之前的业务逻辑耗时太多导致的。

 

对于发送消息的业务逻辑,只需要注意设置合适的并发和批量大小,就可以达到很好的发送性能。为什么这么说呢?

我们之前的课程中讲过 Producer 发送消息的过程,Producer 发消息给 Broker,Broker 收到消息后返回确认响应,这是一次完整的交互。

 

假设这一次交互的平均时延是 1ms,我们把这 1ms 的时间分解开,它包括了下面这些步骤的耗时:

发送端准备数据、序列化消息、构造请求等逻辑的时间,也就是发送端在发送网络请求之前的耗时;

发送消息和返回响应在网络传输中的耗时;

 

Broker 处理消息的时延。

如果是单线程发送,每次只发送 1 条消息,那么每秒只能发送 1000ms / 1ms * 1 条 /ms = 1000 条 消息,这种情况下并不能发挥出消息队列的全部实力。

 

无论是增加每次发送消息的批量大小,还是增加并发,都能成倍地提升发送性能。至于到底是选择批量发送还是增加并发,主要取决于发送端程序的业务性质。简单来说,只要能够满足你的性能要求,怎么实现方便就怎么实现。

 

比如说,你的消息发送端是一个微服务,主要接受 RPC 请求处理在线业务。很自然的,微服务在处理每次请求的时候,就在当前线程直接发送消息就可以了,因为所有 RPC 框架都是多线程支持多并发的,自然也就实现了并行发送消息。并且在线业务比较在意的是请求响应时延,选择批量发送必然会影响 RPC 服务的时延。这种情况,比较明智的方式就是通过并发来提升发送性能。

 

如果你的系统是一个离线分析系统,离线系统在性能上的需求是什么呢?它不关心时延,更注重整个系统的吞吐量。发送端的数据都是来自于数据库,这种情况就更适合批量发送,你可以批量从数据库读取数据,然后批量来发送消息,同样用少量的并发就可以获得非常高的吞吐量。

 

2. 消费端性能优化

使用消息队列的时候,大部分的性能问题都出现在消费端,如果消费的速度跟不上发送端生产消息的速度,就会造成消息积压。如果这种性能倒挂的问题只是暂时的,那问题不大,只要消费端的性能恢复之后,超过发送端的性能,那积压的消息是可以逐渐被消化掉的。

要是消费速度一直比生产速度慢,时间长了,整个系统就会出现问题,要么,消息队列的存储被填满无法提供服务,要么消息丢失,这对于整个系统来说都是严重故障。

 

所以,我们在设计系统的时候,一定要保证消费端的消费性能要高于生产端的发送性能,这样的系统才能健康的持续运行。

消费端的性能优化除了优化消费业务逻辑以外,也可以通过水平扩容,增加消费端的并发数来提升总体的消费性能。特别需要注意的一点是,在扩容 Consumer 的实例数量的同时,必须同步扩容主题中的分区(也叫队列)数量,确保 Consumer 的实例数和分区数量是相等的。如果 Consumer 的实例数量超过分区数量,这样的扩容实际上是没有效果的。原因我们之前讲过,因为对于消费者来说,在每个分区上实际上只能支持单线程消费。

 

我见到过很多消费程序,他们是这样来解决消费慢的问题的:

它收消息处理的业务逻辑可能比较慢,也很难再优化了,为了避免消息积压,在收到消息的 OnMessage 方法中,不处理任何业务逻辑,把这个消息放到一个内存队列里面就返回了。然后它可以启动很多的业务线程,这些业务线程里面是真正处理消息的业务逻辑,这些线程从内存队列里取消息处理,这样它就解决了单个 Consumer 不能并行消费的问题。

 

这个方法是不是很完美地实现了并发消费?请注意,这是一个非常常见的错误方法! 为什么错误?因为会丢消息。

如果收消息的节点发生宕机,在内存队列中还没来及处理的这些消息就会丢失。

 

消息积压了该如何处理?

还有一种消息积压的情况是,日常系统正常运转的时候,没有积压或者只有少量积压很快就消费掉了,但是某一个时刻,突然就开始积压消息并且积压持续上涨。这种情况下需要你在短时间内找到消息积压的原因,迅速解决问题才不至于影响业务。

导致突然积压的原因肯定是多种多样的,不同的系统、不同的情况有不同的原因,不能一概而论。但是,我们排查消息积压原因,是有一些相对固定而且比较有效的方法的。

 

能导致积压突然增加,最粗粒度的原因,只有两种:要么是发送变快了,要么是消费变慢了。

大部分消息队列都内置了监控的功能,只要通过监控数据,很容易确定是哪种原因。如果是单位时间发送的消息增多,比如说是赶上大促或者抢购,短时间内不太可能优化消费端的代码来提升消费性能,唯一的方法是通过扩容消费端的实例数来提升总体的消费能力。

如果短时间内没有足够的服务器资源进行扩容,没办法的办法是,将系统降级,通过关闭一些不重要的业务,减少发送方发送的数据量,最低限度让系统还能正常运转,服务一些重要业务。

 

还有一种不太常见的情况,你通过监控发现,无论是发送消息的速度还是消费消息的速度和原来都没什么变化,这时候你需要检查一下你的消费端,是不是消费失败导致的一条消息反复消费这种情况比较多,这种情况也会拖慢整个系统的消费速度。

如果监控到消费变慢了,你需要检查你的消费实例,分析一下是什么原因导致消费变慢。优先检查一下日志是否有大量的消费错误,如果没有错误的话,可以通过打印堆栈信息,看一下你的消费线程是不是卡在什么地方不动了,比如触发了死锁或者卡在等待某些资源上了。

 

小结

这节课我们主要讨论了 2 个问题,一个是如何在消息队列的收发两端优化系统性能,提前预防消息积压。另外一个问题是,当系统发生消息积压了之后,该如何处理。

 

优化消息收发性能,预防消息积压的方法有两种,增加批量或者是增加并发,在发送端这两种方法都可以使用,在消费端需要注意的是,增加并发需要同步扩容分区数量,否则是起不到效果的。

 

对于系统发生消息积压的情况,需要先解决积压,再分析原因,毕竟保证系统的可用性是首先要解决的问题。快速解决积压的方法就是通过水平扩容增加 Consumer 的实例数量。

 

思考题

课后请你思考一下,在消费端是否可以通过批量消费的方式来提升消费性能?在什么样场景下,适合使用这种方法?或者说,这种方法有什么局限性?欢迎在留言区与我分享讨论。

感谢阅读,如果你觉得这篇文章对你有一些启发,也欢迎把它分享给你的朋友。

 

2.2 实践

TBD

 

 

 

 

后续补充

...

3.问题

TBD

4.参考

https://time.geekbang.org/column/article/189284

后续补充

...

热门文章

暂无图片
编程学习 ·

Linux centos7 乱码设置中文字符集

1.locale 查看现在使用的字符集locale -a 查看有哪些字符集utf8的就可以显示中文yum -y install kde-l10n-Chinese 安装后选个uft8的 ,设置一下全局变量vi /etc/profileexport LANG=en_CA.utf8=号后面是字符集,这个大家随意最后让这个配置文件生效就可以了. /etc/profile 可能…
暂无图片
编程学习 ·

Python之list添加新元素

讲解 现在,班里有3名同学: >>> L = [Adam, Lisa, Bart]今天,班里转来一名新同学 Paul,如何把新同学添加到现有的 list 中呢? 第一个办法是用 list 的 append() 方法,把新同学追加到 list 的末尾: >>> L = [Adam, Lisa, Bart] >>> L.append(P…
暂无图片
编程学习 ·

Leetcode 题解 - 字符串

字符串循环移位包含 编程之美 3.1 s1 = AABCD, s2 = CDAA Return : true给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。 s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。 字符串循环移位 编程之美…
暂无图片
编程学习 ·

《剑指 Offer》——调整数组顺序使奇数位于偶数前面

1. 本题知识点 数组 2. 题目描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 例如: Input: [1,2,3,4,5]Output: [1,3,5,2,4]3. 解题思路 …
暂无图片
编程学习 ·

Go map的概念及三种使用方法

map的概念map 的基本介绍map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合,基本语法var map 变量名 map[keytype]valuetypekey 可以是什么类型golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只包…
暂无图片
编程学习 ·

[算法]回文数

题目判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。示例输入: 121 输出: true输入: -121 输出: false 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。输入: 10 输出: false 解释: 从右向左读, 为 01…
暂无图片
编程学习 ·

SQL存储过程

什么是存储过程,如何创建一个存储过程 * Stored Procedure * 存储过程=SQL语句+流控制语句定义存储过程定义 create procedure 存储过程名称(【参数列表】) begin 需要执行的语句 end. 创建CREATE PROCEDURE `get_hero_scores`( OUT max_max_hp FLOAT, OUT min_max_mp FLO…
暂无图片
编程学习 ·

MySQL基础(十二):锁机制

文章目录一、锁的概述1、什么是锁?2、锁的分类二、MySQL中的三种锁1、表锁(偏读)(1)表锁的特点(2)表加读锁、写锁的语法(3)读锁特性实验(4)写锁特性实验(3)结论2、行锁(偏写)(1) 由于行锁支持事务,复习老知识(2)建表sql(3)行锁加读锁、写锁语法(4)读锁…
暂无图片
编程学习 ·

英语四六级必备软件

分享今天的软件的时候,突然想到了十几年前初中高中学英语的时候,那时候为了学会英语,让家人买过mp3,结果里面最后装的全部都是流行歌,还买过一个步步高学习机,结果最后这个学习机变成的游戏机,想想如果当初学习英语的有这个软件会怎么样今天分享的这个英语软件面向所有爱…
暂无图片
编程学习 ·

老鸟带你回顾新人Java不容错过的八本好书

回头看看, 我进入Java 领域已经快15个年头了, 虽然学的也一般, 但是分享下我的心得,估计也能帮大家少走点弯路。 [入门] 我在2001年之前是C/C++阵营, 有C和面向对象的基础, 后来转到Java ,发现没有指针的Java真是好简单, 另外Java 的类库好用的让人哭啊。 后来我就看《…
暂无图片
编程学习 ·

冒泡排序 | 快速排序 | 线性查找 | 二分查找等

目录排序算法:冒泡排序排序算法:快速排序数组的复制、反转、查询(线性查找、二分查找) 排序算法:冒泡排序 public static void main(String[] args){int[] arr = new int[]{43,32,76,-98,0,64,32,15,108,-21,59};//冒泡排序for(int i = 0;i< arr.length - 1;i++){for(i…
暂无图片
编程学习 ·

Dota 四五号位(辅助)理解

两个辅助打团等于3打5?我怕你是没经历东血魔北迪法南幻刺西巨魔中火枪的年代。你看这阵容是不是5大哥,咋不扎实?要输出有输出,要gank有gank,一个个还灵活得一批。dota发展到如今这个程度,辅助真的是越老越吃香,比如国土比如毛毛鸭比如爱沙尼亚拳王等等。他们的年龄很难做…
暂无图片
编程学习 ·

SpringBoot

文章目录springboot的产生背景?spring boot pom依赖jar包的==@SpringBootApplication==Spring Boot 启动注解思维图**@SpringBootConfiguration**:**@EnableAutoConfiguration**:快速创建SpringBoot工程配置文件YML语法配置文件值获取@PropertySource&@ImportResource配置…
暂无图片
编程学习 ·

KMP算法

KMPKMP主要使用场景场景注意事项结构模板结构模板主体初始化建立前缀表移动处理前缀表循环匹配实现cpp经典问题字符串模式匹配问题描述例题演示实现cpp参考文献 KMP 主要使用场景场景字符串模式匹配注意事项KMP需要输入两个字符串,使用string的时候自带长度,使用纯c的时候请自己…
暂无图片
编程学习 ·

mysql的五种约束

mysql的五种约束 什么是约束? 设计一个数据库,需要一种方法来保证只在表中插入合法的数据,管理如何插入或处理数据库数据的规则 就是约束 。 约束规则有不同的种类, 以下是几种规则约束类型 非空约束 唯一约束 默认值约束 检查约束 主键约束 外键约束关键词 NOT NULL UNIQU…
暂无图片
编程学习 ·

学习node.js前,浏览器的一些工作原理知识的补充

浏览器概述 1、人机交互(UI) 2、网络请求部分(Socket) 3、JavaScript引擎(解析执行JavaScript) 4、渲染引擎(渲染HTML,CSS)又叫排版引擎或浏览器内核 5、数据库存储(cookie、HTML5的本地存储Localstorage、SessionStorage)渲染引擎 主流的渲染引擎有 Chrome浏览器:…
暂无图片
编程学习 ·

LCOF16 快速幂

链接 lcof16 快速幂 描述 实现函数double Power(double base, int exponent),求base的exponent次方。 分析十进制正整数n,二进制表示“bm…b3b2b1” 二进制转十进制,n = 1b1 + 2b2 + 4b3 + … + 2(m-1)bm 所以计算每一个二进制位的幂(x1,x2 ,x4,…),将所有位的幂相乘 利…
暂无图片
编程学习 ·

其实AQS并不难

不啰嗦,直接上干货 文章目录上锁解锁总结条件队列 newConditionCLH队列的数据结构扩展 interrupted 上锁ReentrantLock reentrantLock = new ReentrantLock(true);或者ReentrantLock reentrantLock = new ReentrantLock();看构造函数://无参的构造函数,默认为非公平锁public…
暂无图片
编程学习 ·

近三位数增长,苏宁银行金融科技之花结出普惠金融之果

文|曾响铃来源|科技向令说(xiangling0815)美联储无限QE,2020年中国不设GDP目标,2万亿直达基层扶危纾困……国内疫情已经基本控制,经济基本面迎来全面复苏阶段,作为市场中最活跃的存在之一,小微企业在复苏过程中,面临的融资难等问题也被热议。在中国有一群喊着帮助小微企…
暂无图片
编程学习 ·

Linux 防火墙安装与配置

IPTABLES构建防火墙应用iptables 介绍iptables 常用规则命令常见参数说明 iptables 介绍iptables其实不是真正的防火墙,我们可以把它理解成一个客户端代理,用户通过iptables这个代理,将用户的安全设定执行到对应的“安全框架“中,这个“安全框架”才是真正的防火墙,这个框…