消息队列|Kafka消息顺序

2025/4/14 后端消息队列

# 有序消息

有序消息、消息丢失和消息重复消费三个并列面试热点。

# 消息在分区上的组织方式

在Kafka中,消息是以分区为单位进行存储的。分区是逻辑上的概念,用于对消息进行水平划分和并行处理。

每个topic都可以被划分为一个或多个分区,每个分区都是一个有序、不可变的消息日志。

Kafka 使用WAL(write-ahead-log)日志来存储消息。每个分区都有一个对应的日志文件,新的消息会被追加到文件的末尾,而已经加入日志里的消息,就不会再被修改了。

每个消息在分区日志里都有一个唯一的偏移量(offset),用来标识消息在分区里的位置。

Kafka 保证同一分区内的消息顺序,但不保证不同分区之间的顺序。

而 Kafka 本身暴露了对应的接口,也就是说你可以显式地指定消息要发送到哪个分区,也可以显式地指定消费哪个分区的数据。

# 什么是有序消息

在消息队列里面,有序消息是指消费者消费某个topic消息的顺序,和生产者生产消息的顺序一模一样,它也叫做顺序消息。

前面你应该注意到了,Kafka并不能保证不同分区之间的顺序。也就是说,如果业务上有先后顺序的消息被发送到不同的分区上,那么你难以确定哪一个消息会先被消费。

需要注意一点语义上的差别,这里说生产者生产消息的顺序,不是指你创建出来消息那个实例的先后顺序,而是指broker收到的顺序。

比如说你有两个生产者,一个生产者先生产了msg1,另外一个生产者生产了msg2,但是msg2先发到了broker上。那么实际上我们认为msg2是先于msg1的。

如果你要求的是两个生产者,一个生产者一定要先于另外一个生产者发送一条消息,那么这实际上已经超出了消息队列要解决的问题的范畴了,它属于在分布式环境下如何协调不同节点按照先后顺序执行一定的步骤这个问题范畴。

有序消息强调的是某个topic内,而不是跨topic的有序消息。

# 跨topic的有序消息

想支持这种跨topic的有序消息,一定要引入一个协调者,这个协调者负责把消息重组为有序消息。

比如说,如果msg2先到了,但是msg1还没出来,那么这个协调者要有办法让msg2的消费者B停下来,暂时不消费msg2。而在msg1来了之后,唤醒消费者A消费 msg1,并且在消费完 msg1之后要再唤醒消费者B处理msg2。

# 面试准备

  • 在什么业务场景下,你需要用到有序消息?
  • 你是如何解决有序消息这个问题的?用的是哪种方案?
  • 如果你用的是单分区解决方案,那么有没有消息积压问题?如果有,你是怎么解决的?
  • 如果你用的是多分区解决方案,那么有没有分区负载不均衡的问题?如果有,你是怎么解决的?
  1. 增加分区的问题,后面的多分区方案专门讨论了增加分区可能带来的消息失序的问题
  2. Redis的槽和槽分配
  3. 负载均衡,你记得回答一致性哈希,然后把话题引导到利用一致性哈希来解决多分区数据分布不均匀的问题
  4. 消息积压的问题,你可以把话题引导到单分区方案和多分区方案上

# 基本思路

# 单分区

每一个topic只使用一个分区。方案简单且全局有序,但性能太差。一个topic只有一个分区,没有办法支撑高并发。

消息保持有序,但不是全局有序,保证同一个业务内有序。

# 异步消费

第一个方案是异步消费,这个方案和解决消息积压的异步消费方案差不多,但是要做一点改进。

消费者线程从 Kafka 里获取消息,然后转发到内存队列里面。

在转发的时候,要把同一个业务的消息转发到同一个队列里面。一般来说 可以根据业务特征字段计算一个哈希值,比如说直接使用业务 id 作为哈希值。利用这个哈希值除以工作线程数量,然后取余数,得到对应的内存队列。

这种做法的缺陷就是存在消息未消费的问题。也就是消费线程取出来了,转发到队列之后,工作线程还没来得及处理,消费者整体就宕机了,那么这些消息就存在丢失的可能。

# 多分区

怎么保证同一个业务的消息必然发送到同一个分区呢?

做法也很简单,只需要生产者在发送消息的时候,根据业务特征,比如说业务 ID 计算出目标分区,在发送的时候显式地指定分区就可以了。

# 优缺点分析

# 数据不均匀

分区数据不均匀

# 槽与槽分配
# 一致性哈希

# 增加分区引起消息失序

# 基于优化的面试思路

Last Updated: 2025/4/14 15:27:58