想象你开了一家特别火的餐厅。店里的老规矩是:顾客点菜,服务员必须亲自把厨师拉到桌前,当面说完要求,再站在旁边等菜做好。十桌客人同时点菜时,服务员和厨师全都堵在一起,谁也没法干活。后来你换了套规矩:服务员把顾客的需求写在一张小票上,往出餐口的传递带上一放,就转身去接待下一桌。冷菜厨师、热菜厨师、甜品厨师各看各的票,谁有空谁做,做完打个勾。后厨一下子就顺畅了。这张小票,就是事件;这条传递带,就是消息队列;而各司其职的厨师,就是一个个独立处理事件的服务。事件驱动架构(Event-Driven Architecture,简称 EDA)正是这样一套设计思想:让系统的各个部分像餐厅后厨一样,通过“发布事件”来协作,而不是互相拽着衣角排队等待。这篇文章会带你从点餐小票讲到火锅滑轨,把事件驱动架构的来龙去脉讲明白。
掌握事件驱动架构,意味着你能设计出高并发下依然松耦合、可弹性伸缩、故障被隔离的系统——这是从“能写接口”到“能搭架构”的关键一跃。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
异步发布订阅解耦服务通信 生产者发布事件到消息代理,消费者按订阅主题异步接收消息 Broker解耦生产者与消费者,支持多消费者并行消费同一事件 观察事件从生产者经Broker到消费者的异步流转过程 发布订阅模式实现服务间异步解耦
事件驱动基础
想象你是一家热闹餐厅的老板。传统做法是:顾客点菜时,服务员亲自把厨师拉出后厨,当面告诉他“我要一份宫保鸡丁”,然后站在旁边等菜做好再亲手端回去。如果同时有十桌客人,服务员和厨师就得互相等待、互相阻塞。而事件驱动做法的餐厅是这样的:服务员把需求写在一张订单小票上,往出餐口的传递带上一放,就不再管了。冷菜厨师、热菜厨师、甜品厨师各看各的票,谁有空谁做。整个后厨从“挤在一起”变成“各干各的”。
点餐小票就是事件,传递带就是事件通道,各司其职的厨师就是消费者。事件驱动架构的核心思想是:服务之间不直接点对点调用,而是通过发布“已经发生的事实”来触发别的服务动作。生产者和消费者互不感知对方存在,只依赖事件的格式约定,这就是发布订阅模型。
一个完整的 EDA 有四个基本角色:事件是“已发生之事”的不可变记录,比如“订单已创建”;生产者是事件的源头;通道把事件送给感兴趣的人;消费者订阅并处理事件。在没有事件驱动时,服务两两直连,连接数随组件数爆炸式增长;有了事件中心,每个服务只需接入总线一次。
就像点对点“服务员拉厨师”让双方都动弹不得,而订单传递带让每个人只关心手头的活——事件驱动架构的价值,正是把“谁认识谁”的强绑定,变成“我发布了,谁需要谁拿”的松耦合。
- 组件数量 N系统中的服务或模块个数,比如有 6 个微服务互相直接调用时,N = 6。
- 点对点连接数每个服务要和其它 N-1 个服务分别建立调用关系,所有连线总数等于从 N 个组件里选 2 个的组合数。
- 事件总线连接数所有服务只和事件中心相连,每个服务只需一条连接,总连接数等于组件数 N。
消息队列原理
订单传递带不是一根简单的绳子,它有“排队规则”:先到的小票排前面,后到的排后面;热菜窗口忙时,小票就在传送带上等着,不会把传菜员卡住。这条传递带还会“确认收货”:厨师拿起一张小票,做完菜后在票上打勾,表示这张订单处理完毕。
这条传递带就是消息队列(Message Queue),它是事件驱动架构最常见的载体,把事件的产生和处理在时间上彻底分离。生产者(Producer)把消息放进队列后立刻继续自己的工作,消费者(Consumer)从队列里取消息处理,两边不需要同时在线。
消息队列有两种组织方式:队列模式下,一条消息只能被一个消费者取走,适合“任务分工”,比如一个订单只由一个客服处理;主题模式下,一条消息可以被多个订阅者同时收到,适合“广播通知”,比如订单创建后库存、支付、短信都要知道。为了保证消息不丢,Broker(消息代理)会等消费者返回确认(ACK)后才删除消息;如果处理失败,消息会被重新投递,甚至进入死信队列等待人工排查。
传送带把“谁来取”变成自由竞争,把“什么时候取”变成各自安排。订单多时队列自然积压,订单少时消费者闲着——这种弹性缓冲,正是队列为系统带来的核心价值。
- 积压量 L队列中等待处理的消息条数,可以理解为传送带上待做的订单小票厚度。
- 到达速率 λ单位时间内生产者放入队列的消息数,比如每秒新到 100 张订单。
- 处理速率 μ单位时间内消费者取走并处理完的消息数,比如后厨每秒出 80 道菜。
- 积压变化率队列长度的时间变化率等于到达速率减处理速率;λ 大于 μ 时队列持续增长,λ 小于 μ 时积压逐渐消化。
事件溯源模式
有些餐厅会保留每位顾客的完整“用餐流水账”:几点进门、点了什么、加了几次菜、最后付了多少钱,全部按时间顺序记在账本上,从不撕页、从不涂改。月底对账时,会计把账本从头翻到尾,就能算出现在的结余;想查上周三的状态,翻到那一页即可。
这就是事件溯源(Event Sourcing,ES)的思想:系统不保存“当前状态”这个覆盖式结果,而是把所有领域事件作为唯一真相源,一条一条追加进事件日志。传统 CRUD 是“改数据库里的当前值”,事件溯源是“只追加、不更新、不删除”。每条事件记录的是“发生了什么”,而不是“现在是什么”。
因为事件不可变,审计、回放、时点查询(比如“上周三的账户余额”)都变得非常自然:当前状态只是把事件流折叠出来的一个投影,可以随时从零重建,也可以回到任意历史时刻。代价是每次读取状态都要做一次聚合计算,所以工程上常配合快照(Snapshot)加速,并和 CQRS 一起使用。
账本只增不改,回放即是状态。事件溯源让系统拥有“记忆”,可以随时回到过去,而不是像传统数据库那样只记得“现在长什么样”。
- 初始状态 S(t₀)系统在零时刻的起点,比如账户初始余额 0 元,账本翻开第一页。
- 事件增量 Δᵢ第 i 条事件带来的状态变化,比如“存款 100 元”就是 +100,记在账本第 i 行。
- 累加求和把从起点到当前的全部事件增量逐条加起来,对应会计把账本从头翻到尾。
- 当前状态 S(t)累加的最终结果就是任意时刻的状态;回放到第几条事件,就得到哪个时刻的状态。
CQRS读写分离
顾客在菜单上看到的菜名,和后厨实际做菜的方式是两回事。菜单可以印得又大又直观,配上图片和热量表;后厨只需要按配方精确执行。一个负责“好看好找”,一个负责“高效执行”,两者由一张点餐流程隔开——这就是读写分离的生活原型。
CQRS(Command Query Responsibility Segregation,命令查询职责分离)把这个洞察搬进软件:把系统对数据的操作分成命令(Command,写操作,改变状态)和查询(Query,读操作,不改变状态)。写模型专注处理命令、保证业务规则和事务一致性;读模型针对查询场景专门建表或建视图,承担报表、列表等重读负载,不再掺和业务逻辑。
读写模型之间靠事件流同步:命令写入事件日志后,投影(Projection)把事件翻译成读模型需要的数据结构。因为同步有一个时间差,读到的数据可能比写模型稍旧,这就是最终一致性(Eventual Consistency)——短暂滞后,但最终对齐。CQRS 特别适合读多写少、读写负载差异极大的系统,例如订单查询、报表统计。
菜单更新比后厨慢几秒,顾客不会在意,只要最终能看到新菜式即可。CQRS 让“读得舒服”和“写得严谨”不再互相妥协,而这中间的粘合剂,正是上节讲的不可变事件流。
- 写提交时间 T_commit命令写入事件日志并确认的时间,比如 50 毫秒,相当于服务员把点菜单录入收银机。
- 投影同步时间 T_projection事件从写模型同步到读模型的时间,比如 30 毫秒,相当于菜单印刷更新。
- 读查询获得新数据的时间 T_read读模型能看到最新数据的最短延迟是两者之和;在这个窗口内读到的可能是旧状态,即“最终一致”。
消息平台选型
火锅店和米其林餐厅都送菜,但方式完全不同。火锅店客流量大,一条长滑轨把菜盘源源不断传送出去,讲究量足、连续、快速,菜按桌号扔上传送带,顺序由轨道决定;米其林餐厅讲究每道菜送对位置、按规矩上,出餐口有多个小窗口,按菜品类型走不同通道,灵活但一次只能端一份。
做技术选型时,Kafka 就是那条“火锅滑轨”,RabbitMQ 就是那个“米其林出餐口”。Kafka 是分布式流处理平台,以高吞吐、消息持久化、分区并行著称,适合日志采集、实时数仓、流计算等“海量数据连续流动”的场景。
RabbitMQ 是传统消息代理,以交换机(Exchange)为核心,支持 direct、topic、fanout 等丰富路由规则,支持延迟队列、死信队列,适合“业务事件需要精细路由、单条消息延迟要求低”的场景。选型不是比谁强,而是看需求:吞吐优先选 Kafka,路由灵活优先选 RabbitMQ。Kafka 靠分区并行和顺序写盘赢得吞吐,代价是路由模型相对简单。
选火锅滑轨还是米其林窗口,取决于餐厅开在哪里。吞吐、延迟、路由灵活性、运维成本,四张“菜单”对照自己的订单量和桌型,才能选出合适的那条传送带。
- 单分区吞吐 T_partitionKafka 单个分区每秒能处理的消息条数,受磁盘顺序写速度限制,相当于一条滑轨的输送能力。
- 分区数 P主题拆分的分区数量,分区是消息并行处理的最小单位,相当于滑轨的条数。
- 总吞吐 T_total消息并行写入多个分区,总吞吐近似等于单分区吞吐乘以分区数;分区越多吞吐越大,但单个业务键的顺序保证只局限于它所在的那条分区。
工程挑战应对
传菜最怕三种情况:同一张订单小票被重复打印两遍,厨师照做两遍,顾客收到两份菜;小票被风吹乱了顺序,甜品先上、冷菜后到;一张小票在传送带上弄丢了,顾客的菜迟迟不上。现实餐厅会靠“单号”“排序夹”“收银复核”来解决——这正是分布式消息系统三大挑战的生活版本。
分布式环境下,消息同样面临不重、不乱、不丢三大挑战:网络抖动让生产者重发、消费者处理超时后消息被重复投递、Broker 宕机导致消息丢失。工程应对口诀是:生产端确认 + Broker 持久化 + 消费端手动 ACK 防丢;幂等消费防重;按业务键路由防乱。
幂等(Idempotency)是“重复执行等于一次执行”的性质:消费者在处理过的业务表里记录事件 ID,重复消息直接丢弃。消息顺序的保证思路是让同一业务键(如订单号)的消息路由到同一分区或队列,在单个消费者内串行处理。要获得“不丢不重”的效果,工程上普遍采用“至少一次投递 + 业务幂等”,而不是成本极高的“恰好一次”。
单号让重复小票现出原形,排序夹让上菜顺序稳定,复核让丢单有据可查——幂等、顺序、确认机制,就是分布式餐厅里这三样法宝的代码化身。
- 投递次数 N_delivered消息系统实际发给消费者的总次数;“至少一次投递”下它等于消息条数乘以平均投递倍数。
- 投递倍数 d每条消息平均被投递的次数,网络重试频繁时 d 大于 1,相当于顾客被重复催问了几次。
- 生效次数 N_effect幂等消费把重复消息识别并丢弃,真正执行一次业务逻辑的次数等于原始消息数。
实践案例应用
把目光放回这家餐厅的日常:开餐前,采购部根据预订量备菜;用餐中,传菜、加菜、催菜、买单此起彼伏;打烊后,会计根据一整天流水做结算。任何一个环节卡住,整家店都会乱套。聪明的老板会用一套“事件驱动”的流程把它串起来:每个动作都是一条事件,谁需要谁订阅。
真实系统的落地也是如此,最典型的三个场景是订单异步流程、实时数据管道、监控告警系统。它们分别对应“事件驱动让业务解耦”“事件驱动让数据流动”“事件驱动让系统自愈”。
订单场景中,下单服务发布 order_created 事件,库存、支付、积分、通知各自订阅,互不阻塞;实时数据管道中,业务日志进入 Kafka,流处理任务清洗、聚合后写入数据仓库,供报表与特征工程消费;监控告警中,指标采集器把异常作为事件发布,告警服务消费后按规则通知值班人并触发自动扩容。这三类系统组合起来,就是高并发业务后端的常见骨架。
从下单到结算,每个动作都在自己的轨道上流转;某条轨道出问题时,其它轨道照常运转,故障被隔离在一个小角落里。这就是事件驱动架构交给老板的答案:用松耦合换韧性,用异步换吞吐,用可回放换可审计。
- 单组件可用性 Aᵢ第 i 个服务或节点的可用性,比如 99.9%,相当于某位厨师不请假的概率。
- 串联连乘同步调用链中所有环节都必须可用,整体可用性是各环节可用性相乘,链越长乘积越小。
- 整体可用性 A_sys事件驱动把长链路拆成各管一段的异步轨道,某条轨道故障不影响其它轨道,整体可用性不再被最弱环节拖垮。
AI 应用场景
实时特征计算管道
阶段:特征工程与数据管道
解决的问题:传统批量 ETL 每隔几小时才更新一次特征,用户行为发生后推荐系统反应迟钝;事件驱动让行为事件实时流入特征服务,秒级更新统计特征与向量表示,从而支撑在线推理的实时性。
具体案例:某电商推荐系统把用户点击、加购行为发布到 Kafka,特征服务订阅后实时更新用户兴趣向量,召回层在几百毫秒内拿到最新特征参与排序,点击率显著提升。
模型训练异步流水线
阶段:模型训练与生命周期管理
解决的问题:数据版本更新、代码提交、定时调度都要触发训练任务,硬编码互相调用会让链路又长又脆;事件驱动让“数据就绪”事件触发训练、评估、上线三个服务异步接力。
具体案例:某算法平台在 ETL 完成后发布 data_ready 事件,训练服务订阅后自动启动训练;训练结束再发布 model_ready 事件,评估与部署服务继续接力,全流程无需人工介入。
推理服务弹性伸缩与告警
阶段:模型服务与运维监控
解决的问题:推理服务流量突增时人工扩容来不及,服务被压垮;将 QPS、延迟等指标异常作为事件发布,告警服务消费后自动触发扩容,同时通知值班人,实现系统自愈。
具体案例:某 NLP 服务在大促期间 QPS 超过阈值,指标事件被发布到消息队列,自动扩缩容组件消费事件后拉起新实例,同时向值班群推送告警,整体可用性保持稳定。
Python 演示
import uuid class Event: def __init__(self, name, data, event_id=None): self.id = event_id or str(uuid.uuid4()) self.name = name self.data = data class EventBus: def __init__(self): self.subscribers = {} def publish(self, event): print("[事件中心] 广播事件:", event.name, event.id) for handler in self.subscribers.get(event.name, []): handler(event) def subscribe(self, name, handler): self.subscribers.setdefault(name, []).append(handler) class OrderService: def place_order(self, order_id, amount): print("[订单服务] 创建订单:", order_id, "金额:", amount) return Event("order_created", {"order_id": order_id, "amount": amount}) class InventoryService: def __init__(self): self.processed = set() def on_order_created(self, event): if event.id in self.processed: print("[库存服务] 重复事件已忽略:", event.id) return self.processed.add(event.id) print("[库存服务] 锁定库存: 订单", event.data["order_id"]) class PaymentService: def on_order_created(self, event): print("[支付服务] 扣款:", event.data["amount"], "元") class NotificationService: def on_order_created(self, event): print("[通知服务] 发短信: 订单", event.data["order_id"], "已创建") def main(): bus = EventBus() inventory = InventoryService() bus.subscribe("order_created", inventory.on_order_created) bus.subscribe("order_created", PaymentService().on_order_created) bus.subscribe("order_created", NotificationService().on_order_created) order_svc = OrderService() event = order_svc.place_order("A001", 299.0) bus.publish(event) bus.publish(event) if __name__ == "__main__": main()
class EventBus:事件中心:维护“事件名 → 处理器列表”的订阅关系,是整个传递带的心脏。def publish(self, event):发布接口:遍历该事件的订阅者并逐一调用处理器,发布者根本不关心谁在处理。self.subscribers.setdefault(name, []).append(handler)订阅登记:把处理器挂到对应事件名下,新增服务只需一行注册。return Event("order_created", {...})订单服务创建事件后立即返回,不等待任何下游,这就是异步解耦的代码形态。if event.id in self.processed:幂等检查:重复事件直接忽略,保证“至少一次投递”不产生副作用。bus.publish(event)同一事件连着发布两次,模拟网络重试造成的重复投递,运行后能看到库存服务只锁定一次。
常见误区
误区我以为只要把 Kafka 或 RabbitMQ 引进来,把接口调用改成发消息,就是事件驱动了。系统还是同步等待结果,只是把 HTTP 换成了 MQ。
正解事件驱动的灵魂是“异步 + 解耦 + 事件建模”:生产者发完事件就继续自己的事,不等待消费者结果;事件描述“已发生的事实”而不是“派发任务”。如果发完消息还同步等待结果、把每个下游都写死在一条链路里,那只是把队列当管道用的伪事件驱动。就像把订单小票塞进传递带,却还拽着厨师问“做好了吗”,那传递带就白装了。
为何常见:工具比思想更容易被感知,初学者看到消息队列这个“新工具”就以为抓住了关键,忽略了背后的异步协作模型。
误区事件日志听起来和数据库的 binlog 差不多,都是把操作记下来,说白了就是多一份日志备份。
正解数据库日志是辅助手段,作用是崩溃恢复,业务查询仍然读“当前值”;事件溯源把事件日志当作唯一真相源,当前状态只是事件流的投影,任何时刻都可以回放重建。两者最本质的差别:binlog 记“数据变成了什么”,事件溯源记“发生了什么”。类比一下,行车记录仪只能看你开过哪条路,而完整的剧本能让演员随时重演任何一幕。
为何常见:两者都是“追加写入、按序回放”的形态,表面相似让人忽略了它们在系统中的位置完全不同——一个是配角工具,一个是主角真相源。
误区网上说 Kafka 每秒能处理百万条消息,那所有消息需求都用 Kafka 最好,RabbitMQ 已经过时了。
正解Kafka 的高吞吐来自“分区并行 + 顺序写盘”,代价是路由能力弱、消费者与分区绑定、跨分区没有全局顺序;RabbitMQ 在复杂路由、延迟队列、灵活确认上有明显优势,适合业务事件分发。选型标准是场景:日志流和数仓管道选 Kafka,订单状态机、延迟通知选 RabbitMQ。火锅店滑轨和米其林小窗口各有各的店,流量大的路边摊硬上米其林流程只会拖慢出餐。
为何常见:性能数字最直观,基准测试里 Kafka 一骑绝尘,容易让人忽略需求维度的多样性。
误区我直接把消费者实例开到最大数量,反正多几个进程总比少几个强,吞吐肯定上去了。
正解在 RabbitMQ 工作队列里适度增加消费者确实能提升吞吐,但在 Kafka 里,同一分区的消息只会被消费者组内的一个消费者处理,消费者数量超过分区数后,多出来的消费者完全闲置。而且消息按业务键路由到同一分区时,必须由单个消费者串行处理才能保住顺序。比喻:同一条火锅滑轨上站再多传菜员,也只能依次取菜;想让更多人干活,得多开几条轨道。
为何常见:“人多力量大”的生活直觉不符合分区的并行模型,没看到并行度的天花板来自分区数而不是进程数。
误区消息系统都说了会把消息持久化到磁盘,那应该很安全,我可以不用管 ACK 和幂等吧。
正解消息系统提供的是“不丢的能力”而非“不丢的保证”:生产者发完没收到确认、Broker 落盘前宕机、消费者处理完但还没 ACK 就崩溃,这些窗口都可能丢消息或重复投递。标准做法是生产端确认 + 副本持久化 + 消费端手动 ACK,同时接受“至少一次”投递并用幂等兜底。传送带只能保证小票在正常运转时不掉,但厨师没打勾就被叫走,小票照样会被重新投递一次。
为何常见:厂商宣传里的“持久化”让人以为是保险箱,忽略了分布式系统里“确认”和“窗口期”才是可靠性的真正战场。
面试题
Q1. 事件驱动架构中,服务之间通过什么方式进行通信?
- A.直接同步调用对方接口
- B.通过事件异步发布订阅← 正确答案
- C.通过共享数据库读写
- D.通过配置文件热加载
答案:B。事件驱动的核心是“异步发布订阅”:生产者把事件交给通道就离开,消费者各取所需,双方无需互相认识,就像服务员把订单小票放上传递带。A 是传统同步调用,C 会造成数据库耦合,D 与通信机制无关。
Q2. 消息队列中“生产者”的主要职责是什么?
- A.从队列中取走消息并处理
- B.把消息放入队列中← 正确答案
- C.管理队列的存储和副本
- D.负责消费者实例扩容
答案:B。生产者是消息的来源,职责是把事件封装成消息放入队列,然后继续做自己的事。取消息是消费者的职责,管理存储与副本是 Broker 的职责,扩容是运维系统的职责。就像写小票的是服务员,不是厨师或老板。
Q3. 事件溯源模式中,系统的当前状态是如何得到的?
- A.从最新快照中直接读取
- B.对事件日志累加回放← 正确答案
- C.从数据库覆盖式保存
- D.通过定时任务定时刷新
答案:B。事件溯源只追加事件,当前状态是把事件流从起点聚合回放出来的投影。A 把快照当成了主存储,快照只是加速手段;C 是传统 CRUD;D 与状态重建无关。类比:余额是把流水账从第一页加到当前页算出来的。
Q4. CQRS 读写分离带来的好处有哪些?
- A.读模型可按查询场景优化← 正确答案
- B.写模型专注业务规则事务← 正确答案
- C.读延迟一定降为零
- D.系统无需处理最终一致性
答案:AB。CQRS 让读模型单独建表、按查询优化,写模型专注命令与事务,两者互不拖累。但读写同步存在延迟窗口,读到的可能是旧数据,所以 C 的“零延迟”和 D 的“无需一致性”都是错误选项。就像菜单可以排版精美,但更新总比后厨慢半拍。
Q5. 保证消息“不重不漏”最常用的工程手段是什么?
- A.幂等键加去重消费← 正确答案
- B.增加消费者的数量
- C.缩短消息的过期时间
- D.把队列分成更多分区
答案:A。分布式环境下很难做到数学上的“恰好一次”,工程上普遍采用“至少一次投递 + 幂等消费”:消费者记录已处理的事件 ID,重复消息直接丢弃,达到业务上的不重不漏。加消费者、缩短过期、加分区的确能提升吞吐,但都不解决重复和丢失问题。
Q6. RabbitMQ 的交换机(Exchange)主要解决什么问题?
- A.按路由规则分发消息到队列← 正确答案
- B.加速磁盘的顺序写入
- C.持久化海量的事件日志
- D.保证跨机房消息同步
答案:A。交换机是 RabbitMQ 路由能力的核心,生产者把消息交给交换机,交换机按绑定规则(direct、topic、fanout)把消息分发到一个或多个队列。顺序写盘和日志持久化是 Kafka 的强项,跨机房同步需要额外的镜像或复制方案。
Q7. 某系统要求同一用户的订单事件严格按序处理,最合适的方案是什么?
- A.按业务键路由到同一分区← 正确答案
- B.让消费者并发处理所有消息
- C.使用多条队列分散负载
- D.提高消息处理的超时时间
答案:A。顺序保证的前提是“同一业务键的消息进入同一分区或队列,并被同一消费者串行处理”。并发消费和多队列会打乱同一用户的先后顺序;提高超时只是放宽等待,不解决顺序。就像同一桌的菜必须走同一条滑轨,不能分到两条轨道上。
Q8. Kafka 中某个主题只有 3 个分区,却部署了 6 个消费者,结果是?
- A.六个消费者均匀分担所有消息
- B.三个消费者将处于空闲状态← 正确答案
- C.吞吐量提升到原来的两倍
- D.消息顺序得到进一步的保证
答案:B。Kafka 消费者组中,一个分区同一时刻只能被一个消费者消费,3 个分区最多让 3 个消费者工作,其余 3 个闲置,吞吐不会翻倍。想要更多并行必须增加分区。类比:一条滑轨只能站一个传菜员,人多也只能排队等着。
Q9. 以下哪些是事件驱动架构带来的好处?
- A.服务之间耦合度大幅降低← 正确答案
- B.系统具备弹性扩展能力← 正确答案
- C.所有调用变成同步等待
- D.消息自带严格的全局顺序
答案:AB。事件驱动通过发布订阅让服务互不感知,降低耦合;消费者可以按负载增减实例,扩展更弹性。C 恰好说反了,事件驱动追求异步;D 是常见误解,事件只在分区或队列内有顺序,没有全局顺序。就像每桌的菜按桌号排队,但桌与桌之间没有统一顺序。
Q10. 关于“至少一次投递”语义,下列说法正确的有?
- A.消息可能被重复投递← 正确答案
- B.能保证消息不会丢失← 正确答案
- C.消费者必须做幂等处理← 正确答案
- D.消息一定严格按序到达
答案:ABC。“至少一次投递”意味着每条消息至少被送达一次,因此可能重复(A 对)、不会丢(B 对);正因为可能重复,消费者必须用幂等兜底(C 对)。投递次数和顺序是两件事,乱序仍然可能发生,所以 D 错。就像传菜员会把同一张小票多送一次,但保证不弄丢。