Kafka 如何保证消息顺序性
文章目录
- 分区策略
- 轮询
- 随机
- 按 Partition 路由
- 按 key 路由
- 顺序性保证
分区策略
Kafka 的消息会被生产者发送到分区 Partition中,然后消费者通过获取分区中的消息来完成消费操作。所以Kafka不仅有主题 Topic
这个概念,也引入分区 Partition
这个概念来实现队列的可用性、伸缩性、吞吐量,不过也有一定的代价,就是需要解决消费无序、丢失等比较关键的问题,当前主要的分区策略有以下4种。
轮询
当生产者向Kafka发送消息时,如果既没有指定key
,又没有指定 Partition
,就会使用默认的分区器,默认按照轮询策略向分区进行数据发送。轮询很好理解,字面意思就是每次消息发送给不同的分区,例如:
- 第一次:发送给分区1
- 第二次:发送给分区2
- 第三次:发送给分区3
- 第四次:发送给分区1
- 不断循环…
轮询策略尽可能保证每个分区中的消息数量均衡,如下图所示:
随机
当生产者向Kafka发送消息时,如果既没有指定key
,又没有指定 Partition
。虽然现在默认策略应该是轮询,但是以前老版本Kafka的分区策略其实采用的是随机,轮询更好理解,每次发送消息时都会发送给不同的分区,没有任何规律,如下图所示:
按 Partition 路由
上面提到的两种方式,都是不指定key和分区,该策略就是在消息发送之前,就指定好该消息要发送到具体哪个分区中,如下图所示:
按 key 路由
指定 key 路由方式是我们开发过程中Kafka主要使用的方式,在向分区发送消息之前,一般都会为该消息指定 key,这样 Kafka 会根据消息的 key 进行哈希运算,然后与分区的数量进行取模操作,这样就能判定当前消息回路由到哪个分区,所以每次相当 key 的消息一定会在同一个分区当中(前提分区没有发生扩缩容), 如下图所示:
顺序性保证
有一个前提需要了解:Kafka保证单个分区内顺序,不保证多个分区间的全局顺序
想要保证消息顺序性,势必要牺牲Kafka吞吐量,保证消费顺序性最简单的方式莫过于为单个主题单个分区,这样就能保证每条消息一定是顺序的,对应的消费者也要使用单线程或者保证消费顺序的线程模型。但是这样反而违背了分区这个概念的初衷,正常生产环境也几乎没有这样做的,这个方式绝大部分场景并不可取。❌
所以想要保证顺序性,通常最佳实践如下:
- 首先,指定消息key会进行哈希 + 取模方式定位具体分区,然后进行消息发送,这样同一分区消息本身就是按顺序排列。
- 其次,在消费者端同样也要控制,不能多线程对消息进行处理,这样会因为并发问题打乱消费顺序,要确保消费者单线程处理一个分区的消息。
- 最后,设置下述 Kafka 相关参数,这样就避免发送重试消息乱序问题:
# 确认应答机制:all表示所有分区副本消息同步完毕
acks=all
# 消息发送失败重试次数,如果发送失败,会重新进行消息发送
retries > 0
# 允许生产者在同一个连接上同时发送、等待确认的最大请求数
max.in.flight.requests.per.connection = 1