消息积压的直接原因是系统中,某个部分存在性能问题,来不及处理上游消息。
优化性能来避免消息积压
消息队列本身的处理能力要远大于业务系统的处理能力。主流产品的单节点,消息收发性能在每秒几万到几十万条,还可以通过水平拓展broker实例数量成倍提升。
由于业务代码逻辑远比消息队列复杂,单节点可以达到 百/千 QPS 已经算性能很好了。
因此对于优化性能,关注点在消息收发两端,业务代码如何与消息队列配合去达到最优性能。
1. 发送端性能优化
优先检查发送消息前,业务逻辑代码的耗时。
其次对于发送消息的代码,设置合适的并发,批量大小,可以达到很好的发送性能。
一次发送消息包含:producer端序列化消息,构造请求等逻辑;producer发送请求和broker返回确认响应的网络延时;broker端处理消息耗时;
至于选择提高并发,还是批量大小,取决于发送端的业务性质。即怎么方便怎么来。
以发送端为微服务为例,由于RPC框架都支持多线程处理请求,因此直接在请求中发送消息即可实现并行发送消息。在线业务对时延敏感,批量发送则会影响时延不合适使用。
以发送端为离线分析系统为例,离线系统在性能上不关心时延,更关注整个系统的吞吐。发送端的数据都来自于数据库,这种情况就更适合批量发送,可以批量从数据库读取数据,然后批量地发出消息,同样也可以用少量并发获得非常搞的吞吐量。
2. 消费端性能优化
大部分使用消息队列的性能问题会出现在消费端,若消费的速度跟不上生产消息的速度,就会造成积压。
若倒挂情况暂时,则可以在消费端性能追上之后,慢慢消化掉积压的消息。
若一直是倒挂情况,时间长了,要么消息队列存储被占满,无法提供服务,要么就发生丢消息,对于整个系统来说都是严重故障。
因此设计系统的时候,一定要保证消费速度远高于生产速度,系统才能健康运行。
消费端性能优化方式如下:
1. 优化消费业务逻辑
2. 水平扩容,增加消费端的并发数(增加topic中的queue数量,consumer数量与queue[分区]一一对应),实际上是增加主题中的分区数量(请求-确认)。
很多消费程序会通过下图来解决消费满
OnMessage ——put——> 内存队列 ——take——> 业务线程
——take——> 业务线程
——take——> 业务线程
消息的业务逻辑可能不能再优化了(仍比较慢),为了避免积压,收到消息的OnMessage方法中,不处理任何业务逻辑,把这个消息放到内存队列就离开了。而内存队列可以启动多业务线程增加并发处理速度。
但注意这种方案遇到节点宕机,内存队列中没来得及处理的内容就会丢失。
消息积压了如何处理
固定的线上积压排查方法:
消息积压粗粒度原因分类
1. 发送变快了
2. 消费变慢了
通过大部分消息队列内置的监控功能,确认是上述哪个情况:
1. 如果是单位时间的发送消息变多,如赶上大促或抢购、短时间无法优化代码,可通过立刻水平扩容消费端的实例数来提升消费性能。
如果短时间没有足够服务器资源进行扩容,没办法的办法是对系统进行降级,关闭不重要的业务,减少发送方发送消息的数量,最低限度让系统能正常运转,服务重要业务。
2. 另一种不太常见的情况是,无论发消息和接受消息的速度都没什么变化。此时需要检查消费端是否一直消费失败,导致一条消息重复消费,这种情况会拖慢整个系统的消费速度。
3. 如果消费速度变慢了,需要检查消费实例,分析速度变慢原因,看下日志中是否有大量错误,没有的话,通过打印堆栈信息(golang 中的 pprof)看下消费代码是不是出现了死锁或者卡在了资源等待上。
小结
这节课主要讨论了2个问题,一个是如何在消息队列的接收两方优化性能,提前预防消息积压。另一个问题是,当系统发生消息积压后,该如何处理。
优化消息发性能,可以通过减少前置业务逻辑耗时,或者常用的两种是增加批量或者增加并发,在发送端两种都可以使用。在消费端需注意增加并发需要同步扩容分区数量不然不起作用。
对于系统发生消息积压的情况,需要先解决积压,再分析问题,保证系统可用性是首要解决的问题。快速解决积压的方法时通过水平扩容增加consumer的实例数量。
思考题
消费端是否也可以同样通过批量消费来提升消费性能?什么样的场景下适合使用这种方法?或者这种方法有什么局限性?
1. 消费端对消息的处理支持批量处理,或者开启多线程单处理。
2. 批量消费中一旦一条数据消费失败会导致整批消息重复消费。
3. 对实时性要求不能太高,批量消费需要broker积累到一定消费数据才会发送到consumer。