假如你走进一家爆火的奶茶店:点单的顾客挤满柜台,收银员飞快地写下小票——“珍珠奶茶,去冰,七分糖”——夹到旋转架上,制茶师不慌不忙地按顺序取票、做茶。你拿到了号牌,不用站在柜台前干等,可以刷手机、聊天,等叫号取杯。高峰期 100 杯订单涌进来,没人慌:小票先收下,茶一杯杯慢慢出。 这张小票就是“消息”,旋转架就是“消息队列”,收银员是“生产者”,制茶师是“消费者”。消息队列(Message Queue,MQ)的本质,就是在“谁产生数据”和“谁处理数据”之间加一个缓冲中转站。它让上游不必等下游,让高峰流量先囤起来再慢慢消化,让各个系统各干各的活、互不拖累。 本文将从奶茶店的故事出发,带你认识消息队列的基本概念、核心价值、可靠性设计、消息语义、顺序保证、高级特性、工程实践与选型面试。你会发现,消息队列没那么神秘——它就是那个替你“先收单、后出杯、不慌乱”的旋转架。
系统设计面试必考高频题,掌握消息队列的原理、权衡与选型,是 AI 从业者参与大型系统架构设计的入场券。
可视化演示
展示消息队列的生产者-队列-消费者架构、三种消息投递语义及流量削峰机制,揭示异步解耦与可靠性保障的核心原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
生产者-队列-消费者架构 生产者:将消息异步发送到队列,无需等待消费者处理 队列:FIFO缓冲区存储消息,实现生产与消费的解耦 消费者:从队列拉取消息并处理,支持多消费者并行 TPS = min(P_rate, C_rate × N_consumers) 消息队列通过缓冲实现生产者与消费者的速率匹配
基本概念
还记得那个旋转架吗?你把“珍珠奶茶、去冰、七分糖”报给收银员,她写下小票夹到架子上,制茶师按顺序取票做茶。你拿着号牌去旁边等,没人催、没人乱。这套“小票+旋转架”的机制,就是消息队列(Message Queue,MQ)的完整蓝图。
在系统世界里,生产者(Producer) 是收银员——只负责产生和发送数据;消费者(Consumer) 是制茶师——只负责取数据和干活;中间的队列(Queue) 是旋转架——负责暂存和转发。那张小票就是消息(Message),它不只是业务数据本身,还包括消息 ID、发送时间戳、主题标签等“骑缝信息”,统称元数据(Metadata)。
消息模型分两种:点对点模型(Point-to-Point) 像一张小票只能做一杯茶,一条消息只被一个消费者取走;发布订阅模型(Pub/Sub) 像新品海报贴给所有会员,一条消息会广播给所有订阅者。生产速率和消费速率往往不一致,积压量随时间的演化可以用积压公式描述:
理解了“转盘+小票”,后面所有高级话题其实都在回答三个问题:消息怎么不丢?怎么不重?怎么不乱?接下来的每一节,都是在给这三个问题找答案。
- $L(t)$积压消息数:在时刻 $t$,队列里还没被消费的消息总量,也就是旋转架上还没被取走的小票数。
- $\lambda(\tau)$生产速率:在 $\tau$ 时刻,生产者每秒投入队列的消息数。高峰期它可能飙到几千。
- $\mu(\tau)$消费速率:在 $\tau$ 时刻,消费者每秒取走并处理完的消息数,通常相对平稳。
- 积分 $\int_0^t$ 累加把每秒“多收进来但没做掉”的小票差全部累加起来,就是当前架子上的积压。
核心价值
假如店里没有旋转架会怎样?收银员必须等上一杯做完,才能接下一单。高峰期 100 杯订单同时涌来,队伍排到门口,顾客骂骂咧咧,店员手忙脚乱——这就是没有消息队列的系统:上游和下游死死绑在一起,谁慢都拖垮谁。
消息队列的三大价值,全在解决这种“绑死”问题。解耦:点单和制茶互不依赖,收银员不用知道制茶师怎么做茶。异步:顾客下单后拿号就走,不用等茶做完才确认。削峰填谷:高峰期 100 杯先收下小票,制茶师按自己的节奏慢慢做。后端数据库就像制茶师,峰值写入可能高达每秒 5000 条,而数据库只能扛 500 条/秒。有了 MQ,请求先落到队列里,后端按 500 条/秒的匀速消费,系统就不会被打垮。削峰幅度可以量化:
奶茶店用号牌把 100 杯的高峰摊进了 30 分钟里;系统用 MQ 把每秒 5000 的峰值摊成每秒 500 的匀速。这就是“削峰填谷”——不消灭流量,只改变流量的节奏。
- $\eta$削峰率:衡量消息队列把高峰“削平”了多少。0 表示没削,越接近 1 表示削得越狠。
- $\bar{\lambda}$平均速率:一段时间内生产者的平均吞吐,比如日均每秒 300 条。
- $\lambda_{\max}$峰值速率:瞬时最高吞吐,比如大促瞬间每秒 5000 条。
- $1 - \frac{\bar{\lambda}}{\lambda_{\max}}$削峰比例:比例越小说明峰越尖,队列缓冲的价值越大。
可靠性设计
小票会被风吹走吗?会。收银员刚写下小票,一阵风刮来,小票被吹到水沟里——消息丢了。顾客干等,制茶师不知情。怎么保证小票不丢?奶茶店有三招:小票抄进防水账本、出杯后划掉一笔、划不掉的单子扔进“疑难杂货箱”。
对应到 MQ 里,第一招是持久化(Persistence):消息入队时同步刷到磁盘,即使宕机重启,小票还在账本上。第二招是确认机制(ACK):消费者处理完一条消息,必须回执“搞定”,MQ 收到 ACK 才删除这条消息;若消费者处理途中崩溃没回 ACK,MQ 会认为这单“没做完”,稍后重试(Retry) 重新投递。第三招是死信队列(Dead Letter Queue, DLQ):重试超过上限还是失败的消息,丢进专门“疑难杂货箱”,人工排查处理,绝不无声消失。
这三招合起来的目标,是把系统的可用性拉高。可用性用“平均无故障时间”和“平均修复时间”来刻画:
账本、划单、杂货箱——持久化让消息“记得住”,确认让消息“靠得住”,死信队列让消息“死得明白”。有了这三件套,顾客(上游系统)才能放心地把小票交给旋转架。
- $A$可用性:系统正常服务的时间占比。99.9% 意味着一年只允许宕机约 8.7 小时。
- $\mathrm{MTTF}$平均无故障时间:系统从开始运行到发生故障的平均时长,越长越好。
- $\mathrm{MTTR}$平均修复时间:从故障发生到恢复服务的平均时长,越短越好。
- 比值计算$A = \frac{\text{健康时间}}{\text{健康时间} + \text{生病时间}}$,分子分母同时体现“少生病、好得快”。
消息语义
还是那杯珍珠奶茶,如果制茶师做完了茶,但回执“已出杯”的小纸条在半路被风吹丢了,MQ 等不到 ACK,就会把这条消息再投递一次。于是顾客面前出现了两杯一模一样的奶茶——重复消费。反过来,如果你追求“绝不重复”,可能在网络抖动时直接放弃投递,顾客一杯都喝不到。这里就引出了三种投递语义。
至多一次(At-Most-Once):发一次就完事,丢了不补。像小票不抄账本,风一吹就没了,简单但会丢。至少一次(At-Least-Once):不收到确认就重发,保证不丢,但可能重复——这是大多数 MQ 的默认选择,因为重试必然带来重复。精确一次(Exactly-Once):不丢也不重,代价最高。好消息是:“至少一次 + 消费者幂等”就能达到精确一次的效果。幂等(Idempotent) 的意思是:同一个操作做一次和做一百次,结果都一样。做法是给每条消息带唯一 ID,消费者处理前先查去重表,处理过就跳过。重试 k 次后总成功率的累积效应可以这样算:
“至少一次”是 MQ 的诚实告白:我会努力送到,但可能多送。聪明的消费者不会抱怨,而是把处理逻辑设计成幂等的——来两杯也只收一杯的钱,多出来的那杯当赠品跳过。理解了这个,你才真正理解了“精确一次”的本质。
- $P_{\text{成功}}$最终成功概率:经过最多 $k$ 次投递,消息至少被成功处理一次的概率。
- $p$单次成功概率:一次投递中,网络正常、消费者正常处理并回 ACK 的概率。
- $(1 - p)^{k}$全部失败概率:连续 $k$ 次都失败的“倒霉概率”,每次失败互相独立相乘。
- $1 - (\cdots)$至少一次成功:用 1 减去“全部失败”,就是至少成功一次的概率。
顺序保证
做一杯合格的珍珠奶茶是有工序的:先加珍珠、再倒茶、后加奶盖、最后封口。如果顺序乱了——先封口再加珍珠——这杯茶就砸了。消息处理同样有顺序要求:比如“先创建订单、再支付订单”两条消息,如果支付先被处理了,系统就会报“订单不存在”。
消息队列如何保证顺序?靠分区(Partition)。Kafka 这类 MQ 把一个主题的队列拆成多个分区,可以想象成把旋转架拆成好几排。分区内有序:同一分区内部严格先进先出,像同一排小票必须按顺序取。全局有序:整个队列的消息严格排序,这要求只有一个分区、一个消费者,吞吐量直接被打回原形,代价极高。工程上常用局部有序:按业务 key(比如“同一笔订单”)做哈希,让同一 key 的消息永远进入同一个分区,这样每个 key 内部的顺序就保证了。多个分区并行消费,吞吐量是各分区之和:
分区就是奶茶店的“多排制作台”:同一杯茶的工序不能拆开,但不同杯茶可以同时在各个台子上做。要吞吐就多开分区,要全局有序就只留一个分区——顺序和并行,从来都是鱼与熊掌。
- $T_{\text{总}}$总吞吐:整个主题每秒能处理的消息总数。
- $P$分区数:队列被拆成的分区数量。每排旋转架就是一个分区。
- $T_{i}$单分区吞吐:第 $i$ 个分区每秒能处理的消息数,受单线程消费能力限制。
- 求和 $\sum$分区并行,总吞吐近似等于各分区吞吐之和。
高级特性
奶茶店的生意越做越大,老板开始加服务:顾客说“我 30 分钟后来取”,店员就把小票放到“定时提醒盒”里,时间到了再叫号——这是延迟队列(Delayed Queue)。钻石会员来了可以“插队”——这是优先级队列(Priority Queue)。顾客买奶茶又换积分,两件事必须同时生效,不能奶茶出了、积分漏了——这是事务消息(Transactional Message)。还有顾客只关注珍珠奶茶的新品通知,不想收果茶广告——这是消息过滤(Message Filtering),按标签(Tag)订阅。
延迟队列的底层实现,是给每条消息打上“最早可消费时间”,用一个时间轮或定时扫描器,到了时间才把消息投给消费者。事务消息则采用两阶段确认:先发“半消息”告诉 MQ“我准备发了”,本地数据库事务提交成功后,再发送“确认提交”;如果事务回滚,MQ 就把半消息丢弃。这样保证了“本地事务”和“消息发送”同生共死。延迟出队的时间,就是最朴素的一个加法:
延迟队列是“到点再喊你取餐”的定时盒,事务消息是“奶茶与积分同生共死”的双保险,优先级队列是“至尊会员先做”的插队权。这些高级特性,都是奶茶店为了服务更多刁钻顾客而生的精细化管理工具。
- $t_{\text{出队}}$出队时刻:消息真正被投递给消费者的时间点,也就是闹钟响起的时刻。
- $t_{\text{入队}}$入队时刻:生产者把消息放进队列的时间点,相当于顾客下单的时间。
- $\Delta t$延迟时长:业务指定的等待时间,比如 30 分钟的取餐提醒。
- 加法关系到期时间等于入队时间加上延迟,时间一到立即投递。
工程实践
双 11 奶茶节,0 点瞬间涌进 10 万杯订单。如果每单都立刻去查数据库库存、扣减、写订单表,数据库当场崩溃。聪明的做法是:所有请求先写进消息队列,立刻给用户返回“已受理”,后端按每秒 500 单的匀速慢慢落库。等用户刷出“制作中”时,他的奶茶可能还在排队——但没人崩溃,这就是秒杀系统的标准姿势。
消息队列在真实工程里还有三个高频战场。日志采集:App 端百万级埋点日志,不可能逐条直连分析平台,而是先吐进 MQ,由日志平台按自己的节奏拉取,写 HDFS 或 Elasticsearch。数据同步:订单系统支付成功后发布一条“订单已支付”事件,库存、积分、搜索、报表各系统各自订阅,互不干扰——这就是事件驱动架构。积压排查:当队列积压上万条,三板斧是:看消费者是否宕机、增加消费者实例、检查是否单分区消费成为瓶颈。落库 QPS 的估算也很简单:
秒杀、日志、同步,本质上都是同一个小票故事:先收单,别让柜台炸掉;后出杯,按自己的节奏干活。流量再大,只要有旋转架,柜台就永远从容。
- $QPS_{\text{DB}}$数据库写入压力:每秒真正落到数据库的订单数,目标是不超过数据库上限。
- $N_{\text{订单}}$总订单量:秒杀期间产生的全部订单数,比如 10 万。
- $T_{\text{活动}}$活动时长:秒杀持续的总秒数,比如 1200 秒。
- 均摊计算数据库压力等于总量除以时长,MQ 把峰值摊开后才有了这个公式。
选型与面试
选消息队列,就像选奶茶设备。街边小店需要一台灵活便携的封口机——RabbitMQ 功能全面、路由灵活、社区成熟,适合中小系统,但吞吐量中等。大型工厂需要一条自动化流水线——Kafka 基于分区日志,吞吐量百万级,适合日志、大数据流,但不擅长复杂路由和事务。连锁餐饮要搞优惠券对账——RocketMQ 是阿里开源,事务消息和延迟队列是一绝,适合电商核心链路。还有新兴的 Pulsar,存算分离、多租户,适合云原生环境,但生态相对年轻。
面试官最爱问的不是“哪个最好”,而是“你如何权衡”。核心权衡有四个维度:吞吐量、可靠性、顺序性、运维复杂度。Kafka 吞吐最高,但可能丢消息(不开启强刷盘时);RabbitMQ 最灵活,但吞吐上不去;RocketMQ 事务强,但组件更重。这背后还有一个绕不开的理论——CAP:
面试时别背参数,要讲故事:你说“这个场景像奶茶店高峰,库存扣减必须准确(要 C),但用户下单不能失败(要 A ),所以用事务消息保证最终一致”——面试官眼里,你就已经是懂权衡的架构师了。
- $C$一致性:每次读都能读到最新写入的数据,所有节点看法一致。
- $A$可用性:任何时刻请求都有响应,即使部分节点故障。
- $P$分区容错性:网络发生分区(节点失联)时系统仍能继续工作。
- $= 2$三选二:分区必选,因此实际是在“强一致”和“高可用”之间二选一。
AI 应用场景
电商秒杀系统的流量削峰
阶段:系统架构设计
解决的问题:秒杀瞬间 10 万请求直冲数据库,传统同步处理会导致数据库连接池耗尽、服务雪崩。AI 电商平台用消息队列接住全部请求,返回“排队中”,后端按固定速率消费并扣库存,削峰率可达 90% 以上。
具体案例:某电商平台双 11 秒杀,预估峰值 QPS 5 万,数据库上限 500。架构上所有下单请求先写入 RocketMQ,用户秒回“已受理”,随后每秒 500 的速率消费落库。活动结束统计:队列最高积压 8 万条,但没有一条请求丢失,数据库全程无告警。
海量日志与埋点异步采集
阶段:数据采集与预处理
解决的问题:推荐系统需要用户行为日志做训练,但日志量每秒百万级,直连存储会拖垮业务。用 Kafka 做采集缓冲层,业务只发不管,数据平台按批拉取入库。
具体案例:某 AI 推荐团队在客户端埋点后,将点击、曝光等事件统一写入 Kafka 主题,流处理作业(Flink)订阅消费,实时产出特征,同时离线任务每晚全量回放日志训练模型。队列解耦了“产生日志”和“消费日志”,两边各自扩容互不影响。
订单事件驱动的跨系统数据同步
阶段:系统集成与事件驱动
解决的问题:订单、库存、积分、搜索是多个独立系统,传统同步调用链路长、耦合深。通过事务消息发布“订单已支付”事件,各订阅方异步更新,实现最终一致。
具体案例:AI 客服系统需要“用户是否刚下单”来决定推荐话术,下单系统发布事件到 MQ,客服系统订阅后秒级感知。即使客服系统短暂宕机,消息仍保留在队列中,恢复后自动补消费,不漏一条。
Python 演示
import threadingimport timeimport randomfrom collections import deque class MilkTeaQueue: """一个极简的内存消息队列,模拟奶茶店小票架""" def __init__(self, capacity=10): self.queue = deque() # 旋转架:排队中的小票 self.capacity = capacity # 架子最大容量 self.total_in = 0 # 累计收单量 self.total_out = 0 # 累计出杯量 self.lock = threading.Lock() def put(self, msg): """生产者:收银员把订单放入队列""" with self.lock: if len(self.queue) < self.capacity: self.queue.append(msg) self.total_in += 1 return True return False # 队列已满,触发背压 def get(self): """消费者:制茶师从队头取一张小票""" with self.lock: if self.queue: self.total_out += 1 return self.queue.popleft() return None # 架子空了,休息 def producer(q, orders): for order in orders: if q.put(order): print("[收单]", order) else: print("[爆单丢弃]", order) time.sleep(random.uniform(0.1, 0.25)) def consumer(q, total): made = 0 while made < total: msg = q.get() if msg is not None: made += 1 time.sleep(random.uniform(0.3, 0.5)) # 制茶比收单慢 print("[出杯]", msg) if __name__ == "__main__": q = MilkTeaQueue(capacity=6) orders = ["珍珠奶茶-{}".format(i) for i in range(20)] t1 = threading.Thread(target=producer, args=(q, orders)) t2 = threading.Thread(target=consumer, args=(q, len(orders))) t1.start() t2.start() t1.join() t2.join() print("收单 {} 杯, 出杯 {} 杯, 队列已空: {}".format( q.total_in, q.total_out, len(q.queue) == 0))
self.queue = deque()用双端队列当旋转架,先收的小票先被取走。if len(self.queue) < self.capacity:队列满时拒绝新单,这就是背压控制,防止内存被撑爆。self.queue.append(msg)收银员把小票夹到架子队尾。return self.queue.popleft()制茶师从队头取小票,天然先进先出。with self.lock:多线程同时操作队列,加锁保证安全。time.sleep(random.uniform(0.3, 0.5))消费端故意比生产端慢,模拟“收单快、出杯慢”的高峰场景。threading.Thread(target=producer, args=(q, orders))生产者和消费者各跑一个线程,互不阻塞。t1.join(); t2.join()等待两个线程跑完,再统计最终账目。
常见误区
误区我听说 MQ 是“高可靠”的,所以我把消息一放进去就放心了,应该一条都不会丢吧?我们线上好像也没出过问题。
正解MQ 只是“提供了不丢的工具”,不等于“默认不丢”。必须同时开启持久化落盘、消费者正确处理 ACK、配置合理重试,消息才基本不丢。而且消费者代码有 bug、手动删除、磁盘损坏,照样丢。就像奶茶店有了账本,但如果店员不抄账、出杯不划单,风一吹小票照样没。
为何常见:厂商宣传“高可靠”三个字太响,初学者容易把“能力”误解为“默认行为”,忽略了配置和使用姿势。
误区只要代码写对了,网络一般不会抖,重复消费一年也遇不到一次,等真出了再处理也不迟。
正解“至少一次”语义下,任何一次 ACK 超时都会触发重投,重复不是异常而是设计的一部分。尤其是消费者处理耗时较长时,超时重投会频繁发生。必须把消费逻辑做成幂等:唯一消息 ID 去重、状态机校验、数据库唯一索引。这就像制茶师做了两杯一样的奶茶,但只要顾客只付一杯钱,多送的当赠品就行。
为何常见:本地开发环境网络稳定,几乎观察不到重投,让人误以为重复是极端情况。
误区队列不就是先进先出吗?我在同一个队列里发的消息,消费端一定是按发送顺序收到的吧?
正解只有“单分区 + 单消费者”时才严格全局有序。一旦引入多分区并行消费,不同分区的消息会同时被不同线程处理,顺序就乱了。所以 MQ 只承诺“分区内有序”。要保证某个业务 key 的顺序,就用 key 哈希让消息进同一分区。全局有序不是做不到,是吞吐代价大到多数场景不值得。
为何常见:“队列”这个词自带先进先出的直觉,但分布式队列的并行模型打破了这种直觉。
误区生产监控一看积压了几万条,第一反应是 MQ 太慢了,赶紧换更快的中间件。
正解积压恰恰是削峰填谷的正常表现——高峰期流入大于流出,队列本来就要“囤货”。真正要查的是消费端:消费者是否宕机、消费逻辑是否太慢、分区数是否不足。就像高峰时柜台前排 50 张小票很正常,问题不怪旋转架,要怪制茶师是不是只请了一个。
为何常见:把 MQ 当成了“零延迟管道”,忘了它本质是“缓冲水库”,把缓冲当故障。
误区既然叫“事务消息”,那它一定能像数据库事务一样,要么全部成功要么全部回滚,绝对一致。
正解事务消息只保证“本地事务”和“消息发送”原子生效,各订阅方的数据更新是异步的,最终达到的是最终一致性,不是强一致。比如订单已支付的消息发出后,积分系统可能 1 秒后才加上积分,这中间查询积分是旧值。真要强一致,得用分布式事务框架,代价高得多。
为何常见:“事务”二字自带数据库 ACID 的联想,名字的诱惑让人忽略了它只解决“发消息”这一环。
面试题
Q1. 以下哪项不属于消息队列的核心作用?
- A.系统之间解耦
- B.处理流程异步
- C.请求削峰填谷
- D.提升 CPU 主频← 正确答案
答案:D。消息队列的核心价值是解耦、异步、削峰填谷,本质是改变系统间的协作节奏,而不是提升单机硬件性能。提升 CPU 主频属于硬件优化,与 MQ 无关。
Q2. 消息队列已满时生产者继续发送,常见的处理方式是?
- A.丢弃所有新消息
- B.阻塞或背压控制← 正确答案
- C.随机覆盖旧消息
- D.无限扩容到内存
答案:B。队列满时,成熟 MQ 会让生产者阻塞等待或触发背压,而不是粗暴丢消息或覆盖。无限扩容内存会拖垮机器,随机覆盖会破坏业务数据。就像奶茶店柜台满了,礼貌地让顾客稍等,而不是把单子扔了。
Q3. “至少一次”投递语义的正确含义是?
- A.消息可能被丢失
- B.消息可能被重复← 正确答案
- C.消息绝对不重复
- D.消息必定不丢失
答案:B。“至少一次”指 ACK 超时会重投,保证不丢但允许重复。选 A 混淆了“至多一次”,选 C、D 则是“精确一次”的理想。记住:不丢靠重试,重试必然带来重复。
Q4. 要真正实现“精确一次”处理,除了不丢消息,还必须做到?
- A.消费者处理幂等← 正确答案
- B.消息体尽量压缩
- C.使用更多分区
- D.调高消费并发度
答案:A。“至少一次 + 幂等”才能等效实现精确一次。幂等让重复投递的消息处理一次和多次结果一致。压缩、分区、并发提升的是吞吐而非语义正确性,无法消除重复造成的副作用。
Q5. Kafka 中消息的有序性是按什么维度保证的?
- A.整个集群全局有序
- B.单个分区内有序← 正确答案
- C.整个主题内有序
- D.所有队列合并有序
答案:B。Kafka 只在分区(Partition)内部保证严格有序,不同分区之间并行无序。全局有序需要单分区,吞吐代价极大。就像奶茶店每排旋转架内部按序取票,排与排之间各自独立。
Q6. 死信队列(DLQ)的主要用途是?
- A.提升消费端吞吐
- B.存放无法处理的消息← 正确答案
- C.增加消息副本数量
- D.备份全量历史消息
答案:B。消息重试超过上限仍失败时,会被送入死信队列隔离保存,方便人工排查或后续补偿,避免消息无限重试卡死主流程。它不是用来加速、扩容或备份的,而是“疑难杂货箱”。
Q7. 关于“削峰填谷”的本质,正确的说法是?
- A.把峰值请求直接丢弃
- B.将瞬时压力延后平摊← 正确答案
- C.用缓存加速所有请求
- D.提前扩容全部机器
答案:B。削峰填谷是把瞬时的流量高峰先存入队列,让后端按自身能力匀速消费,压力延后但不消失。丢弃请求会损失业务,缓存加速不改变节奏,扩容是硬件手段且成本高。核心是“改变流量节奏”。
Q8. 保证消息可靠投递通常需要哪些机制配合?
- A.消息持久化落盘← 正确答案
- B.消费成功确认机制← 正确答案
- C.超时未确认重试← 正确答案
- D.消息内容压缩编码
答案:ABC。可靠投递三件套是持久化(宕机不丢)、ACK 确认(处理完成才删)、重试(没确认就补投)。消息压缩只减少网络传输体积,与投递可靠性无关。就像账本、划单、补做三个动作,缺一个都不可靠。
Q9. 线上消息积压严重时,合理的排查与优化手段有?
- A.增加消费者实例数← 正确答案
- B.消费逻辑异步精简← 正确答案
- C.降低生产者发送速度
- D.合理增加分区数量← 正确答案
答案:ABD。积压的根因通常是消费能力不足,解决方向是增加消费者、精简消费逻辑、并通过增加分区提升并行度。降低生产者速度只是掩盖问题,会让上游业务堆积甚至超时,属于下策。
Q10. 关于消息队列与 CAP 理论的正确理解是?
- A.网络分区无法完全避免← 正确答案
- B.分区时可用性与一致性只能取一← 正确答案
- C.牺牲部分一致性换取可用性← 正确答案
- D.强一致和可用性一定能兼得
答案:ABC。分布式系统中网络分区(P)不可避免,所以必须在 C(强一致)和 A(可用性)之间取舍:分区时若选一致性,则部分请求不可用;若选可用性,则暂时放宽一致性,用最终一致兜底。D 是违背 CAP 的幻想。很多 MQ 正是通过最终一致性换取高可用。