小明是一家大型电商平台的架构师,最近遇到一个棘手的麻烦:用户的每一笔订单,系统都要依次执行“创建订单 → 扣减库存 → 支付确认”三个步骤。如果这三步的消息被并发乱序处理,就可能出现“支付成功了,但订单还没创建”的荒谬现象。他找到一位深耕消息中间件多年的老师傅请教,老师傅指着窗外快递站里的分拣传送带说:“你看,邮件要保证收件人按顺序收到,靠的就是分拣台上那个邮政编码——消息要保证顺序,靠的就是分区键。” 这个比喻一下子点亮了小明。如果把消息队列想象成一个巨大的邮局,每个分区(Partition)就是一条分拣传送带,而分区键(Partition Key)就是贴在信封上的邮政编码。送到同一邮编的邮件会落进同一条传送带,由同一位操作员按到达先后依次处理。只要“同一收件人的信”永远进同一条带子、永远由一个操作员处理,顺序就牢牢锁住了。本文将带你走进这个“邮局”,从分区键设计、单分区消费模型到全局顺序保障,完整掌握消息顺序性的核心策略。

岗位关联

消息顺序性是分布式系统中“有序”的基石,掌握分区键与单分区消费策略,是每个云架构师与后端工程师设计可靠消息系统的必备能力。

目录
  1. 可视化演示
  2. 顺序性本质
  3. 分区键设计
  4. 单分区消费
  5. 全局顺序保障
  6. 乱序异常处理
  7. 应用场景实践
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

顺序性本质

快递站里,一位老奶奶每天都能准时收到孙子寄来的两封信:第一封说“我下周三回家”,第二封说“我改到下周五”。如果第二封先送到,老奶奶就会白跑一趟火车站。消息顺序性,就是指消息从生产者到消费者的流转过程中,保持发送顺序与处理顺序一致的能力。

在分布式消息系统中,顺序性分为局部顺序和全局顺序。局部顺序指同一个分区内的消息按序处理;全局顺序指整个队列所有消息严格排序。Kafka、RocketMQ 等主流消息队列,天然保证单分区内有序,但跨分区无法保证全局有序。这就像邮局可以保证一条传送带上的邮件有序,但没法保证两条传送带的邮件合并后依然有序。

评价消息顺序性需要关注三个维度:发送顺序(生产者发送消息的先后)、存储顺序(消息落盘到分区中的排列)、消费顺序(消费者取出的先后)。三者环环相扣,任何一环断裂都会导致乱序。

回到老奶奶的比喻,传送带上的两封信必须保持“周三信在前、周五信在后”的排列,老奶奶才不会白跑。消息队列的设计目标,就是让每一条“传送带”(分区)上的信都严格有序。

顺序一致性=发送顺序∩存储顺序∩消费顺序\text{顺序一致性} = \text{发送顺序} \cap \text{存储顺序} \cap \text{消费顺序}
公式说明:只有当发送、存储、消费三个环节的顺序完全一致时,消息系统才达到真正的顺序一致性。任一环节失序,整体即失序。
  1. 发送顺序生产者调用发送 API 的先后次序,是顺序性的起点。
    Psend(t1)<Psend(t2)P_{\text{send}}(t_1) < P_{\text{send}}(t_2)
  2. 存储顺序消息在分区日志中落盘的物理排列,由分区追加写保证。
    Sstore(t1)<Sstore(t2)S_{\text{store}}(t_1) < S_{\text{store}}(t_2)
  3. 消费顺序消费者从分区中读取消息的先后次序,依赖位点顺序推进。
    Cconsume(t1)<Cconsume(t2)C_{\text{consume}}(t_1) < C_{\text{consume}}(t_2)
  4. 整体一致性三者在时间轴上完全对齐,才构成顺序保证。
    Psend≡Sstore≡CconsumeP_{\text{send}} \equiv S_{\text{store}} \equiv C_{\text{consume}}

分区键设计

邮局分拣员面对堆积如山的信件,如何决定哪封信上哪条传送带?答案就是看信封上的邮政编码——在消息队列中,这个邮编就是分区键(Partition Key)。生产者发送消息时附带一个键,Broker 根据这个键计算哈希值,再对分区数取模,决定消息落入哪个分区。

其中 $N$ 为分区总数。这个公式的关键在于:相同的分区键永远计算出相同的分区号。这就像同一个收件人的信件永远印着同一个邮编,永远被分到同一条分拣带。

设计分区键的核心原则是业务相关性分组:需要保持顺序的消息,必须使用相同的业务标识作为分区键。比如订单场景中,同一个订单 ID 的所有状态变更消息(创建、支付、发货)都使用 orderId 作为分区键,它们就会进入同一分区,按序处理。如果错误地使用用户 ID 作为分区键,同一订单的不同消息可能被分到不同分区,顺序瞬间瓦解。

分区键设计就像为每个“业务家庭”分配专属邮编。只要邮编不变,这个家庭的所有信件就会永远走同一条传送带,顺序自然有保障。

分区号=hash(PartitionKey) mod N\text{分区号} = \text{hash}(\text{PartitionKey}) \bmod N
业务有序性=同一业务ID→同一分区\text{业务有序性} = \text{同一业务ID} \rightarrow \text{同一分区}
公式说明:业务相关性分组的本质,是将“需要有序的消息”映射到“同一个分区”,将乱序风险隔离在分区之外。
  1. 计算哈希对分区键字符串执行哈希算法,得到散列值。
    h=hash(key)h = \text{hash}(\text{key})
  2. 对分区数取模将哈希值对分区总数取模,得到分区索引。
    p=h mod Np = h \bmod N
  3. 路由到分区生产者将消息投递到索引为 $p$ 的分区。
    Message→Partition[p]\text{Message} \rightarrow \text{Partition}[p]

单分区消费

分拣传送带的尽头,站着一位资深分拣员。他目不转睛地盯着传送带,把信件一封封按顺序装进邮包。在消息队列中,这个“一位分拣员”就是单分区消费模型:每个分区在同一时刻只允许一个消费者实例读取消息。

Apache Kafka 的消费组机制规定:一个分区最多被消费组内的一个消费者消费。如果消费组有 3 个消费者、某主题有 3 个分区,理想情况下每个消费者各处理一个分区,实现负载均衡;如果消费者多于分区,多出的消费者会空闲。这个模型保证了分区内的消息被唯一消费者按序读取,不会出现多个消费者争抢导致乱序。

位点(Offset)顺序提交是单分区消费的另一根支柱。消费者每处理完一条消息,就提交一次位点,表示“这条已经处理完,下次从这里继续”。如果消费者先处理了第 5 条、提交了位点 5,再处理第 4 条,就会造成“前面的没处理,后面的已记录”,崩溃恢复后第 4 条被跳过,顺序被打破。

传送带的一位分拣员看似孤独,却最可靠。他一个人按顺序处理所有信件,虽然慢一点,但绝不会乱。多几个分拣员虽然快,但就可能在接力时把信的顺序搞乱。

消费位点:Offsetn+1=Offsetn+1\text{消费位点}: \quad \text{Offset}_{n+1} = \text{Offset}_n + 1
公式说明:位点必须严格递增推进,每消费一条消息,位点加一,确保每条消息都被恰好处理一次且顺序不跳变。
  1. 分区分配消费组协调者将分区分配给消费者实例。
    Partitioni→assignConsumerj\text{Partition}_i \xrightarrow{\text{assign}} \text{Consumer}_j
  2. 顺序拉取消费者从分区头部按位点递增拉取消息。
    Message(Offsetk)→Consumer\text{Message}(\text{Offset}_k) \rightarrow \text{Consumer}
  3. 位点提交处理完成后提交新位点,记录消费进度。
    commit(Offsetk+1)\text{commit}(\text{Offset}_{k+1})

全局顺序保障

如果老奶奶的孙子把所有信都装进同一个信封寄出,那无论邮局怎么分拣,老奶奶收到的一定是一整包完整的内容——这就是全局顺序的朴素实现:只有一个分区。在消息队列中,如果主题只创建 1 个分区,那么所有消息都进入这条唯一的传送带,天然全局有序。

但全局顺序的代价是吞吐量断崖式下降。单分区意味着所有消息串行处理,无法并行消费,系统吞吐能力被限制在单个消费者的处理速度之内。这就是分布式系统中最经典的顺序性与吞吐量的取舍。

如何在保证业务顺序的同时提升吞吐?答案是细粒度业务键:不要用“所有订单”这一个粗键,而要用“单个订单 ID”作为分区键。这样每个订单内部严格有序,而不同订单可以分布在不同的分区并行处理——在局部有序的基础上实现了整体并行。这就像邮局按城市分拣:同一城市的所有信走一条带子保持有序,不同城市的传送带可以同时运转。

孙子给老奶奶写信,其实可以每次只寄一张明信片,但每张明信片上都写着“第几封”。这样邮局可以并行送递,老奶奶也能按编号拼出完整信息。细粒度分区键就是那张明信片上的编号。

总吞吐=∑i=1NTi(分区内串行,分区间并行)\text{总吞吐} = \sum_{i=1}^{N} T_i \quad (\text{分区内串行,分区间并行})
公式说明:系统总吞吐是各分区吞吐之和。分区内串行保证顺序,分区间并行提升吞吐,这是分区键设计的核心思想。
  1. 单分区方案所有消息进入一个分区,全局有序但吞吐受限。
    Tglobal=TsingleT_{\text{global}} = T_{\text{single}}
  2. 多分区并行用业务 ID 拆分到多个分区,每分区内部有序。
    Ttotal=∑i=1NTpartitioniT_{\text{total}} = \sum_{i=1}^{N} T_{\text{partition}_i}
  3. 取舍本质业务维度的顺序隔离,换取系统维度的并行能力。
    Orderbusiness×Parallelsystem\text{Order}_{\text{business}} \times \text{Parallel}_{\text{system}}

乱序异常处理

传送带偶尔会出故障:一封邮件在中途掉落,分拣员重新捡起来放进传送带,但这时它已经被后面几封信超过去了。在消息系统中,这种“重新放入”就是重试机制。网络抖动、消费者处理超时、Broker 短暂不可用,都会触发重试,而重试极大概率导致消息乱序。

更隐蔽的乱序来自重复投递。消费者处理完一条消息后,在提交位点之前崩溃了,消息就会被再次投递。这就是“至多一次”(可能丢)和“至少一次”(可能重)两种投递语义中的后者。Kafka 默认使用“至少一次”,重复不可避免。

处理乱序的最终防线是幂等性:设计消费者使得“处理两次同一消息”与“处理一次”的结果完全一致。实现幂等的常用手段包括:在消息中携带全局唯一 ID,消费者记录已处理 ID,重复消息直接丢弃;或利用数据库唯一约束,重复插入报错但业务状态不变。

传送带掉落邮件后,分拣员的补救方式是:只要看到“这封已经拆过”的标记,就直接跳过不再处理。幂等设计就是给每封邮件盖上的“已拆”章。

幂等条件:f(f(x))=f(x)⇔重复消费无副作用\text{幂等条件}: \quad f(f(x)) = f(x) \quad \Leftrightarrow \quad \text{重复消费无副作用}
公式说明:幂等操作是“重复执行结果不变”的操作。消费者代码具备幂等性后,即使消息被重复投递或重试乱序,最终状态依然正确。
  1. 重试乱序单条消息发送失败后重发,可能排到后续消息之后。
    Retry⇒Reorder\text{Retry} \Rightarrow \text{Reorder}
  2. 重复投递位点未提交前消费者崩溃,消息被再次消费。
    Crash⇒Duplicate\text{Crash} \Rightarrow \text{Duplicate}
  3. 幂等处理利用唯一业务 ID 去重,使重复消息被安全跳过。
    IDunique⇒Idempotent\text{ID}_{\text{unique}} \Rightarrow \text{Idempotent}

应用场景实践

邮局的智慧最终要服务于千家万户。消息顺序性在实际业务中最典型的应用有三个领域。

金融交易流转是最苛刻的场景。一笔转账涉及“发起 → 风控审核 → 资金划拨 → 通知”多个步骤,任何一步乱序都可能导致资金错误。以银行卡号作为分区键,就能保证同一张卡的所有交易事件严格有序。某个支付平台的交易系统,正是利用订单号作为分区键,将每笔交易的完整生命周期消息路由到同一分区,配合单分区顺序消费,实现了日均数亿笔交易的有序处理。

订单状态机更新是电商系统的命脉。订单状态从“待支付”到“已支付”再到“已发货”,状态变迁必须严格有序。以订单 ID 为分区键,将订单的每一次状态变更事件送入同一分区,消费者按序推进状态机,彻底避免“已发货但未支付”的逻辑错误。

日志顺序追踪是云原生可观测性基础设施的关键。分布式系统的日志以 traceId 作为分区键,同一请求链路的所有日志集中在同一分区,排查问题时可以按序回放完整请求路径,快速定位故障点。

邮局的故事在金融系统、电商平台和云原生基础设施中生生不息。分区键就像那个神奇的邮编,让每一类业务信件都能按正确的顺序抵达目的地。

有序场景∈{交易流转, 状态机, 日志追踪}\text{有序场景} \in \{\text{交易流转},\ \text{状态机},\ \text{日志追踪}\}
公式说明:所有需要严格顺序的场景,共同模式都是“以业务聚合根 ID 作为分区键”。
  1. 交易流转交易 ID 作为分区键,资金事件严格排序。
    TradeID→Partitionordered\text{TradeID} \rightarrow \text{Partition}_{\text{ordered}}
  2. 状态机更新订单 ID 作为分区键,状态变迁按序执行。
    OrderID→Partitionordered\text{OrderID} \rightarrow \text{Partition}_{\text{ordered}}
  3. 日志追踪TraceID 作为分区键,链路日志顺序回放。
    TraceID→Partitionordered\text{TraceID} \rightarrow \text{Partition}_{\text{ordered}}

AI 应用场景

金融交易实时风控

阶段:数据管道与实时特征工程

解决的问题:传统风控系统需要先汇总交易流水再做分析,耗时数十分钟;且同一账户的连续交易若乱序到达,会导致风控模型计算出错误的风险评分。以账户 ID 作为分区键将交易消息按序送入同一分区,AI 风控模型可以实时按序消费,对每笔交易毫秒级评分,同时对用户进行实时行为建模。

具体案例:某支付公司在交易消息中设置 accountId 为分区键,AI 风控模型按序消费每笔交易,实时更新该账户的行为特征向量。当检测到“先小额试探、后大额转账”连续模式时,系统在第二笔交易到达的 30 毫秒内发出拦截告警,因为顺序特征被完整保留,模型识别准确率提升了 37%。

智能推荐系统状态同步

阶段:特征存储与在线推理

解决的问题:推荐系统中用户行为事件(浏览、点击、加购)必须按时间顺序进入特征库,否则模型会看到“先加购后浏览”的颠倒行为,造成错误推荐。以 userId 为分区键,将同一用户的所有行为事件路由到同一分区,AI 特征平台按序更新用户实时特征。

具体案例:某短视频平台将用户行为事件以 userId 为分区键写入 Kafka,推荐系统的特征服务按序消费并增量更新用户 Embedding。由于同一用户的行为严格有序,模型捕捉到“先看完整个视频再点赞”的顺序信号,推荐点击率提升了 12%。

Python 演示

python68 行
import hashlibfrom collections import defaultdict, deque  class MessageRouter:    """模拟消息队列的分区路由与顺序消费"""     def __init__(self, num_partitions):        self.num_partitions = num_partitions        self.partitions = defaultdict(deque)        self._consume_count = 0     def _hash_partition(self, key):        """计算分区键的哈希值并取模,得到目标分区号"""        hash_val = int(hashlib.md5(key.encode()).hexdigest(), 16)        return hash_val % self.num_partitions     def send(self, partition_key, message):        """将消息路由到固定分区,相同 key 永远进同一分区"""        partition_id = self._hash_partition(partition_key)        self.partitions[partition_id].append((partition_key, message))        return partition_id     def consume_partition(self, partition_id):        """单分区顺序消费:从队首取出消息,确保 FIFO 有序"""        if not self.partitions[partition_id]:            return None        item = self.partitions[partition_id].popleft()        self._consume_count += 1        return item     def consume_count(self):        return self._consume_count  def main():    router = MessageRouter(num_partitions=3)     order_id = "A1001"    events = [        (order_id, "创建订单"),        (order_id, "扣减库存"),        (order_id, "支付成功"),    ]     print("=== 发送消息:同一订单使用相同分区键 ===")    sent_pids = []    for key, msg in events:        pid = router.send(key, msg)        sent_pids.append(pid)        print(f"  消息[{msg}] -> 分区 {pid}")     assert len(set(sent_pids)) == 1, "同一订单必须路由到同一分区!"     print("\n=== 按分区顺序消费 ===")    pid = sent_pids[0]    while True:        item = router.consume_partition(pid)        if item is None:            break        key, msg = item        print(f"  消费: [{msg}] (key={key})")     print(f"\n共消费 {router.consume_count()} 条消息,全部有序处理完成。")  if __name__ == "__main__":    main()
目的:演示分区键哈希取模路由和单分区 FIFO 顺序消费的完整流程,直观展示“相同 key 进同一分区、分区内严格有序”的核心原理。
  1. hashlib.md5(key.encode())对分区键字符串做哈希,生成稳定散列值
  2. hash_val % self.num_partitions对分区数取模,得到目标分区号
  3. defaultdict(deque)用双端队列模拟分区,天然支持先进先出
  4. popleft()从队首取消息,保证单分区内消费顺序严格 FIFO

常见误区

误区很多初学者认为分区数越多,吞吐量越高,于是把分区数设置得非常大,认为这样可以最大化并行度。

正解分区数增加确实能提升并行消费能力,但并非越多越好。每个分区在 Broker 上对应一组文件句柄和内存缓冲,分区过多会导致文件碎片化、zookeeper 元数据膨胀、消费者重平衡时间变长。就像邮局如果开 1000 条传送带,每条带子上只有几封信,分拣员来回跑动的开销反而超过了处理信件的收益。分区数应根据目标吞吐量、消费者数量和 Broker 磁盘性能综合评估。

为何常见:人们直观地认为“更多并行 = 更高性能”,忽略了系统资源开销与管理复杂度是随分区数线性增长的。

误区以为设置了分区键,所有消息就天然有序,不需要关心消费者端的处理方式。

正解分区键只保证消息被路由到同一分区,但消费端的并发模型同样会影响顺序。如果消费者内部用多线程并行处理消息,或使用异步回调,即使消息从分区中按序取出,处理结果也可能乱序落库。必须确保消费端采用单线程处理或按分区键加锁,才能真正锁住顺序。这就像传送带按序送来了信,但负责盖章的职员如果把信交给两个实习生同时盖,盖章的先后顺序就乱了。

为何常见:人们只关注消息队列的存储顺序,忽略了消费端的处理顺序是最终可见顺序的最后一公里。

误区有同学认为只要消费端实现了幂等,消息乱序也无所谓,反正重复处理结果一样。

正解幂等只能解决“重复”问题,不能解决“顺序”问题。比如订单状态机中,“创建”和“支付”两条消息乱序到达,幂等并不能阻止系统先进入“已支付”再尝试“创建”的逻辑错误。幂等是乱序场景的兜底方案,但不能替代分区键和单分区消费提供的顺序保障。就像老奶奶收到两封信,即使内容相同可以重复看,但如果“改时间”的信先到,她依然会白跑一趟。

为何常见:幂等和顺序是两个维度的可靠性语义,初学者容易混淆“不重复”和“不乱序”这两个目标。

面试题

Q1. 在 Kafka 中,保证同一分区内消息顺序的关键机制是什么?

单选题 · easy

  • A.消息随机分发到多个分区
  • B.分区追加写入与单消费者顺序读取← 正确答案
  • C.消费者多线程并发处理
  • D.消息压缩存储

答案:B。Kafka 的每个分区是一个追加写日志,消息按写入顺序存储;同一分区在同一时刻只被消费组内一个消费者读取,二者结合保证分区内有序。选项 A 随机分发会打乱顺序,C 多线程并发处理会破坏消费顺序,D 压缩只影响存储不影响顺序。这就像一条传送带既按序送入信件,又只有一个分拣员取件,顺序自然保持。

Q2. 分区键设计为 hash(partitionKey) % N 时,若 N 从 8 扩展到 16,会发生什么?

单选题 · mid

  • A.所有消息顺序不变
  • B.原有分区键映射到新分区的路由结果可能变化← 正确答案
  • C.消息自动复制到新分区
  • D.消费者无需重新分配分区

答案:B。取模运算的除数变化后,同一个 key 的哈希值对 16 取模的结果大概率不同于对 8 取模的结果,导致消息被路由到新分区。这就像邮局把邮编从 3 位升级到 4 位,原来的老邮编对应到全新的分拣线路。分区扩容后需要通过迁移或双写策略保证数据不丢失,A、C、D 均不符合 Kafka 的实际行为。

Q3. 消费组内有 4 个消费者,主题有 3 个分区,以下说法正确的是?

单选题 · mid

  • A.每个消费者各消费一个分区
  • B.3 个消费者各消费一个分区,第 4 个消费者空闲← 正确答案
  • C.4 个消费者轮询消费所有分区消息
  • D.分区会被平均切分给 4 个消费者

答案:B。Kafka 的分配策略保证一个分区同一时刻只被一个消费者消费,消费者数多于分区数时,多余消费者空闲。这就像有 3 条传送带、4 个分拣员,只能有 3 个人干活,第 4 个人待命。A 错误因为无法满足“每个消费者分到一个分区”,C 轮询会导致分区内消息被并发读取破坏顺序,D 分区不会切分。

Q4. 某订单要保证“创建 → 支付 → 发货”状态严格有序,最合适的分区键是?

单选题 · hard

  • A.用户 ID
  • B.订单 ID← 正确答案
  • C.商品 ID
  • D.卖家 ID

答案:B。订单状态流转的所有事件都围绕同一个订单实体发生,以订单 ID 作为分区键,同一订单的事件必然路由到同一分区按序消费。用户 ID 会导致该用户的不同订单混在同一分区,无法区分订单内顺序;商品 ID、卖家 ID 更无法关联到具体订单的状态流。这就像给每个家庭一个专属邮编,这个家庭内部的所有信件都走一条传送带。

Q5. 以下哪些措施可以缓解消息重试导致的乱序问题?

多选题 · hard

  • A.设置消息超时时间为无穷大
  • B.消费端实现幂等处理← 正确答案
  • C.将同一业务的消息指定相同分区键← 正确答案
  • D.增加分区数

答案:BC。B 幂等处理保证重复投递无副作用,C 相同分区键保证需要有序的消息进入同一分区。A 超时无穷大会导致消息积压且失败消息永远阻塞队列,不是缓解乱序的方案;D 增加分区数反而会让原本可能在同一分区的消息被分散到不同分区,加剧乱序风险。这就像传送带同时做了“防重复盖章”和“固定邮编”双重保险。

Q6. 消息队列中“局部顺序”与“全局顺序”的区别是?

单选题 · easy

  • A.局部顺序指单分区内有序,全局顺序指所有分区整体有序← 正确答案
  • B.局部顺序指内存有序,全局顺序指磁盘有序
  • C.二者没有区别
  • D.局部顺序靠消费者保证,全局顺序靠生产者保证

答案:A。局部顺序限定在单个分区内,Kafka 天然保证;全局顺序要求所有分区消息合并后依然有序,只有单分区主题才能实现。B 混淆了存储层级与顺序范围,C 显然错误,D 颠倒了责任方——顺序性主要由分区机制和消费模型共同保证。邮局每条传送带内部有序是局部顺序,所有传送带的信合并后仍有序才是全局顺序。

Q7. 消费者提交位点前崩溃,消息恢复后会被如何处理?

单选题 · mid

  • A.消息永久丢失
  • B.消息被重新投递,可能导致重复消费← 正确答案
  • C.消息自动跳过
  • D.消息顺序自动修正

答案:B。位点未提交意味着消费进度没有被记录,消费者重启后会从上次提交的位点重新拉取,导致已处理但未提交的消息被再次消费,这就是“至少一次”语义的重复投递。解决方案是在消费端实现幂等。这就像分拣员盖了章但没在台账上登记,重新上班后只能把已经盖章的信再盖一遍。

Q8. 关于幂等消费的正确理解包括哪些?

多选题 · mid

  • A.相同消息重复处理结果一致← 正确答案
  • B.可以完全替代分区键的顺序保障
  • C.通常借助唯一业务 ID 去重实现← 正确答案
  • D.只在消息丢失时有意义

答案:AC。幂等指重复执行同一操作结果不变(A),常用全局唯一 ID + 去重表实现(C)。但幂等只能解决重复问题,不能解决先后顺序颠倒的逻辑错误,故 B 错误;幂等主要体现在重复投递场景,而非消息丢失场景,故 D 也不正确。就像“已拆章”只能防止重复拆信,不能阻止“改时间的信先到”。

Q9. 一个主题有 6 个分区,每秒产生 6 万条消息,要求同一用户的消息严格有序,此时最佳方案是?

单选题 · hard

  • A.创建一个单分区主题牺牲吞吐
  • B.以用户 ID 为分区键,将同一用户路由到固定分区← 正确答案
  • C.将消息随机发送到 6 个分区
  • D.使用数据库代替消息队列

答案:B。以用户 ID 为分区键,同一用户的全部消息进入同一分区,在保证用户维度顺序的同时,6 个分区可并行处理不同用户的消息,兼顾吞吐。A 单分区吞吐不够;C 随机路由破坏用户顺序;D 数据库不具备消息队列的削峰填谷和异步解耦能力。邮局按用户家庭分配邮编,每个邮编固定走一条传送带,不同家庭的传送带并行运转。

Q10. 某系统要求所有交易消息全局有序,同时又需要较高的吞吐量,以下可行方案包括哪些?

多选题 · extreme

  • A.单分区 + 批量聚合消费← 正确答案
  • B.单分区 + 消费者内部并行批处理
  • C.多分区 + 全局序号排序后合并← 正确答案
  • D.放弃消息队列改用数据库表

答案:AC。A 单分区保证全局有序,批量聚合可提升消费效率;C 多分区写入时附带全局递增序号,消费端按序号排序后合并,在并行写入的同时还原全局顺序。B 消费者内部并行批处理会破坏处理顺序,除非按序号再排序;D 数据库表做消息队列既不可扩展也无法应对高吞吐。这相当于邮局要么用一条高速传送带,要么用多条带子但给每封信编总序号,最后按序号拼装。