
Kafka本身不保证整个Topic的全局消息顺序但能保证单个分区(Partition)内的消息是有序的。这就像一个快递站所有包裹消息都先送到总站(Topic)但总站内部会分给不同的快递员分区去送每个快递员手里的包裹顺序是固定的但不同快递员之间的顺序就乱了。如何保证消息顺序?把需要保持顺序的消息都发到同一个分区里。按业务Key分区这是最常用的方法。用一个能代表业务主体的唯一标识作为消息的Key比如订单号、用户ID。Kafka会根据这个Key的哈希值把同一主体的所有消息都路由到同一个分区。这样订单的“创建”、“支付”、“发货”消息就能按顺序被处理了。自定义分区器如果业务逻辑更复杂可以自己写一个分区器决定每条消息该去哪个分区。比如把VIP用户和普通用户的消息分开处理。生产者端的配置有两套配置:# 方案一 ackall retriesx max.in.flight.requests.per.connectio1 # 方案二: ackall retriesx enable.idempotencetrue max.in.flight.requests.per.connection1...5 # 可配小于等于5的一个数解析方案一: 只有ackall时正常发送消息没有发送失败的情况时分区中的消息是有序的发送失败时——为了解决消息发送失败配置了retriesx这个参数用于重试发送重试就会导致分区中消息乱序和重复——为了解决重试发送导致的分区中消息乱序配置了max.in.flight.requests.per.connectio1这个参数。引入这个参可以解决重试分区中消息乱序的原因: 这个参数控制生产者在收到服务器响应前可以发送多少个未确认的请求批次如果设置大于1并且网络延迟或服务器响应慢就可能导致消息乱序为了保证严格顺序这个值最好设置为1。但这个方案还有一个问题没有解决分区中的消息可能会有重复消息。所以一般用方案二方案二只有ackall时正常发送消息没有发送失败的情况时分区中的消息是有序的发送失败时——为了解决消息发送失败配置了retriesx这个参数用于重试发送重试就会导致分区中消息乱序和重复——为了解决这两个问题加入了enable.idempotencetrue收到一个 batch 时检查 batch 中第一条消息的 seq if (seq lastSeqNum 1) { 写入 ✅lastSeqNum 更新为 batch 中最后一条的 seq } else if (seq lastSeqNum) { 丢弃 ✅重复消息重试产生的 # 解决重复问题 } else { # 即seq lastSeqNum 拒绝 ❌OutOfOrderSequenceException → seq 超前了说明前面的 batch 还没到 # 解决乱序问题拒绝后等seqlastSeqNum的batch[可能是生产者已经发送过但由于网络抖动失败后再次发送的]到了以后当前seq lastSeqNum的batch被拒绝后生产者没有收到确认响应也会再次发送这个batch以此保证顺序正常 }但是幂等不允许太多 batch 在途否则导致乱序拒绝概率大增因此限制max.in.flight.requests.per.connection最大为5方案一生成者无幂等配置时是没有消息序号的因此只能配置1物理串行发送保证消息有序。消费者端的处理消息到了消费者端如果处理不当顺序也可能被破坏单线程消费最简单有效的方法是用单线程同步消费一个分区的消息这样就能保证顺序多线程和异步都不能保证顺序消费。多线程消费如果要用多线程提高消费速度必须确保同一个分区的消息由同一个线程处理不同分区的消息可以并行处理。特别提醒扩容问题如果一个Topic的分区数需要从N扩容到M(MN)那么新加入的分区会导致原有的Key哈希计算结果变化部分消息会被分配到新的分区这可能会导致这些消息的顺序性被破坏。全局顺序vs局部顺序绝大多数业务场景只需要“局部顺序”即同一业务主体内的消息有序不同主体之间可以并行处理。追求全局顺序虽然能保证但会牺牲Kafka的高并发性能通常不推荐。