消息顺序:保证消息有序,一个topic只能有一个partition吗?
消息在分区上的组织方式
- kafka中,消息以分区为单位存储。分区是逻辑上的概念,用于消息水平划分和并行处理。每个topic可以划分至少一个分区,每个分区都是一个有序、不可变的消息日志
- kafka用WAL日志存储消息。每个分区对应一个日志文件,新的消息被追加到文件末尾,已经计入日志的消息就不会再被修改
- 消息在分区日志都有唯一偏移量,标识消息在分区的位置
kafka保证同一分区的消息顺序,但不保证不同分区之间的顺序
什么是有序消息?
- 有序消息:消费者消费topic消息的顺序和生产者生产消息的顺序一样
- kafka不能保证不同分区之间的顺序。业务有先后顺序的消息被发送到不同分区上,无法确定那个消息会被先消费
案例
单分区解决方案
- 要保证消息有序,最简单的做法就是让特点的topic只有1个分区。所有消息发送到一个分区自然就是有序的
- 缺点:性能差。不能支撑高并发。对生产端,消息都在一个分区意味着都发送到同一个broker,服务器可能扛不住压力。对消费端,只有一个分区就只有一个消费者,容易出现消息积压
- 方案:如果要求全局有序只能用更好的服务器。如果只追求业务内有序可以用异步消费或多分区方案
异步消费
- 这个方案和解决消息积压的异步消费方案差不多。只需要做一点改进,消费者线程从kafka中获取消息再转发到内存队列中。转发时,把同一个业务的消息转发到同一个队列中
- 可以根据业务特征字段计算一个哈希值,利用这个哈希值划分对于的内存队列
- 缺陷:可能存在消息未消费的问题
亮点方案:多分区
- 其实就是把异步消费的内存队列换成多个分区,同一个业务的消息发送到同一个分区
- 只需要确保同一个业务消息发送到同一个分区就可以保证同一个业务的消息有序
怎么保证同一个业务消息必然发送到同一个分区?
- 比如用业务ID计算目标分区,发送时显示的指定分区
- 怎么计算分区:最简单的方案就是取余,余数就是目标分区
缺点1:数据不均匀
-
数据不均匀:热点消息可能在同一个分区中,这个分区QPS会很高,消费者可能来不及消费造成消息积压
-
解决方案1:参照Redis的槽和槽分配机制
- 借鉴Redis的槽与槽分配方案。一般划分1024个槽就够,更具业务特征计算哈希值,再除以1024取余得到所在槽。再根据不同槽的数据量把槽分配到不同分区。可以借助于配置中心把槽和分区的绑定关系做成动态的,保证所有分区负载都均匀
-
解决方案2:一致性哈希
- 使用一致性哈希算法筛选分区。根据数据分布情况,把分区分布在哈希环上,确保每一个分区的数据分布大体是均匀的。如果一部分哈希值数据较多,就多插入几个分区节点,然后根据业务特征计算哈希值,从哈希环上找到对应分区
缺点2:增加分区引起消息失序
- 增加分区会引起消息失序。msg1发送到分区0上,但是分区0积攒了很多消息,msg1不被消费。这个时候增加一个新的分区3。如果这个时候来了一个msg2,被转发到了分区3,分区3是空的就会直接消费msg2。本来msg1应该先于msg2被消费,但是现在msg2先消费了。这就是消息失序
- 解决方案:新增的分区可以先等待一段时间,确保同一个业务在其他分区的消息已经被消费了