顺序性
MortarMQ 保证什么顺序、在哪儿不再保证,以及怎么绕着这条边界设计。
保证:单分区、追加顺序
一个分区同一时刻只有一个写入者(该分区的 broker leader),一个组里也只有一个消费者拥有它。这个「单写者 / 单 owner」配对让保证变得很便宜:
一个分区内的消息,按追加顺序投递。
不需要对序号、不需要乱序缓冲 —— 日志本身就是顺序。
你用什么 key 决定了什么被排序
分区选择是 fnv1a32(key) % partition_count。所以:
| 如果你的 key 是… | 那么有序的是 |
|---|---|
同一流里每个消息都用同一个业务 id(如 order_id) | 该实体的全部消息,全局有序 —— 因为它们都落进同一个分区 |
| 空 / 缺省 | 轮询散开;任意两条消息之间没有顺序关系 |
| 随机或基于时间 | 等同于空,而且还会偏斜 —— 比空更糟 |
规矩: 顺序是按 key 的,而按 key 的顺序之所以存在,只因为 key 把消息钉死在了一个分区上。
text
order_id = "A-1001" ─┐
order_id = "A-1001" ─┼─▶ 分区 2 ──▶ 严格有序
order_id = "A-1001" ─┘
order_id = "A-1002" ───▶ 分区 0 ──▶ 仅对 A-1002 有序明确不保证的
| 不保证 | 为什么 |
|---|---|
| 跨分区 | 两个分区是两条独立日志、两个独立写入者 |
| 跨 topic | 比跨分区离得更远 |
| 生产者用多个 key 时的先后 | 不同 key 可能落进不同分区 |
| 消费者看到的全局顺序 | 一个组把分区摊给多个消费者;每个消费者只对自己那几个分区有序,组整体无序 |
| 某个消费者跨 rebalance 的连续性 | 消费者可能持有分区 0、失去它、之后又拿回来 —— 分区的顺序完好,但这个消费者的视角中间缺了一段 |
最后那行最让人意外:日志从不重排,但你的处理序列可能在「不拥有该分区」的那段出现空洞。
绕着边界设计
- 先定排序范围。 「按订单号有序」→ 把
order_id放进 key,接受不同订单之间交错。「全局有序」→ 只用一个分区,接受你已经把并行度扔掉了。 - 绝不在一个分区内部并发处理。 归属保证一个分区一个消费者;如果这个消费者自己往外并行,是它破坏了日志保下来的顺序。按分区串行。
- 别指望 rebalance 对你的处理逻辑不可见。 收到
NOTICE ASSIGN时消费者可能从日志中段开始 —— 从ack_floor恢复,而不是从「我最后碰巧看到的那条」。 - 分区数在 DECLARE 时定死。 之后改分区数会让每个 key 重映射到不同分区,毁掉所有按 key 的顺序 —— 这正是 v0 禁止改它的原因。
怎么验证你的假设
bash
# 1) 确认 topic 的分区数
mortarmq topics list
# name partitions state created_epoch
# orders 4 active 1
# 2) 确认单个 key 没有被摊开:
# 用同一个 key 发 100 条,然后看各分区的 depth ——
# 应该只有一个分区承载了这批负载带 topic,partition label 的 mortarmq_partition_depth 就是这件事的可观测形式:如果一个 key 的流量出现在多个分区里,说明你的 key 没有起到你以为的作用。