略难的题,高频题目。
大部分人就只会回答异步消费什么的,但是这个方案属实有点烂大街了,毫无竞争力。你需要的做的是先区分是临时积压还是永久性积压。
回答这个问题的关键有两个,一个是要把你能记得的各种方案都讲一下;另外一个关键点是你要把话题引导过去顺序消息的消息积压解决方案上。
消息积压的话,首先你要区分是临时性积压还是永久性积压。
所谓的临时性积压是指你的消费者处理能力是足够的,但是因为突发流量的问题,导致消息积压了很多。但是从长期来看,这些消息会在接下来一段时间内被消费掉。这种情况下,你可能需要处理,也可能不需要处理,就看你对这种情况的容忍度。比如说我之前有一个消息积压问题,因为预期会在 24 小时内消费完,所以我就懒得处理。但是后面又遇到过一个类似的场景,这一次预期要用一个星期才能消费完,而业务上并不能等那么久,所以只能着手处理。
而永久性积压就是你的消费者消费能力不足,这种情况下积压的消息只会越来越多,所以你是必须要处理的。
接下来再来看怎么处理。为了方便你理解,我假设现在引起积压的生产-消费模型如下图:
第一种思路就是增加消费者。也就是说,如果要是消费者数量没有达到分区数量的上限,就可以简单通过增加消费者的数量来解决问题。例如说现在 Topic 有十个分区,但是消费者组里面只有七个消费者,那么你就可以额外增加三个消费者,也许加完之后消费者就有足够的消费能力了。
但是很显然,这种场景是很少出现的,因为如果这么简单面试官就不会问你了。
第二种解决思路就是增加分区。也就是原本十个分区只能有十个消费者,我增加分区到二十个分区,不就是可以有二十个消费者了?这个问题有一个难点,就是有些运维不允许你增加分区。所以这个时候你可以曲线救国,创建一个类似的 Topic,但是有更加多的分区。然后让生产者切换到新的 Topic,并且部署新的消费者消费这个新的 Topic。
就好比,你有一个 Topic 叫做 create_user,有十个分区。那么我搞个 create_user_new,搞个二十个分区,就解决了问题。
第三种思路就是优化消费者的性能。至于怎么优化就看消费者的代码是怎么写的了,跟业务细节强相关。你在平时注意整理一个自己的案例;
第四种思路就是消费者降级。也就是原本你正常处理一条消息的时候,可能处理逻辑非常复杂,那么你现在就直接让消费者降级,走简单的逻辑。举个例子,假设说消费者的完整处理路径有十次数据查询,降级之后只有一次数据库查询,那么消费者消费的速度自然是大大提高了。
除了这种降级,还有一些的比较奇诡的降级。例如说很多公司的消费者不仅仅是消费者,还承担着别的功能,比如说服务业务请求,执行的定时任务。那么在这里你有两种做法:
第五种解决思路就是批量消费。也就是消费者一次拉一批消息,而后使用批量接口来一次性处理掉这一批消息。举个例子来说,假设原本你原本消费消息是一条就调用一次 RPC 接口,比如说 handle(id),那么你就改成批量接口 batchHandle(10, 11, 12)。它本质上是利用批量接口的性能优势来提高消费能力;
第六种解决思路就是异步消费,批量提交。它和第二种思路很像,都是拉取一批消息,但是这个时候没有批量接口可以用,所以只能开多个线程同时调用下游接口,最后一起提交。
第七种解决思路就是聚合发送,批量消费。这个方案改动比较大,需要修改消息格式。也就是说假如说原本一条消息的核心内容是 id = 123,那么现在我们把 id 改成数组,一条消息就变成了 id = 123, 234, 345。举个例子,原本是一个 id 就有一条消息,那么现在可以说 10 个 id 凑够一条消息,那么消息量就会减少为 1/10。而对应的消费者端,就可以调用批量接口。
第八种解决思路就是聚合发送,异步消费。它是上一种解决思路的变种,消费者这端没有批量接口,就改为异步消费,每一个 id 开一个线程调用服务处理掉。
其中第五到第八种你可能会觉得很难记忆,我教你一个简单的方法:这几种就是生产者是否发批量消息,消费者是否异步/批量消费的排列组合(或者说笛卡尔积)。
因此总结起来就是:
注意,我这里说的发聚合消息,是指发 id = 1,2 ,3 这种,而不是批量消息,也就是 id=1, id =2, id =3 这种批量。上面我标粗的就是属于比较高级的点,大部分情况下你的竞争对手是答不出来的。
尤其要注意的是,消费者降级这个一定不要忘了,因为它可以和服务治理关联在一起。