同步调用多了,为什么堵车

下单要调库存、调优惠、调积分、调通知,服务之间同步调用像排队过收费站:任何一个下游慢了,整个请求就跟着慢;任何一个下游挂了,上游就直接失败。调用链越深,耦合越紧。

事件驱动的思路是换个协作方式:我不直接喊你干活,而是把"发生了什么"放进队列,谁关心谁自己来取。这样上游不等下游、下游不拖累上游。

同步调用问的是「你帮我办了吗」,事件驱动说的是「这件事已经发生了,需要的来拿」。
事件驱动带来的三个能力
  • 解耦生产者不知道谁在消费。
  • 削峰突发流量先进队列,消费者按自己的节奏处理。
  • 重放消息还在,就能重新处理一遍。
事件驱动示意:蓝紫渐变背景左侧三个发光生产方模块,经一条弯曲发光传送带式消息管道连向右侧三个发光消费方模块,管道上漂浮发光圆形事件消息节点,中段有发光队列容器,扁平科技插画
生产者把事件放进管道,消费者按自己的节奏取走,两边不再互相等待。

消息队列的基本模型

消息队列的核心是三个角色:生产者把消息发到队列,队列暂存并路由,消费者从队列取消息处理。主题之下还有分区、消费者组这些概念,用来控制并行度和顺序。

角色职责典型问题
生产者发布事件发失败了怎么补偿
队列/主题暂存与路由积压了怎么处理
消费者取消息处理处理失败要不要重试

先把这个三角想清楚,再去挑具体组件:要不要顺序、要不要广播、允许丢多少消息,答案都不一样。

可靠投递:至少一次与幂等

完全不多不少地把消息投递一次,在分布式里几乎做不到。实际工程普遍接受「至少一次」投递,代价是消费者可能收到重复消息,所以消费端必须幂等:同一事件处理两遍,结果和一遍一样。

# 幂等处理的通用做法:先查后写,带上处理标识
def handle_order_created(msg):
    key = "processed:" + msg["order_id"]
    if redis.setnx(key, "1", ttl=3600):
        # 首次处理:真正执行业务逻辑
        update_inventory(msg)
        send_notification(msg)
    # 重复消息直接跳过,业务结果不变

用处理标识去重,是消息场景里最常用的幂等手段,比单纯靠数据库唯一键更可控。

事件与命令:别混着用

「请把库存扣掉」是命令,带有明确意图,通常只有一个消费者;「订单已创建」是事件,表达已发生的事实,可以有多个消费者各取所需。把两者混用,消息模型很快就会乱。

维度命令事件
语义要求执行告知事实
时态将来要做已经发生
消费者通常一个可以多个

设计时先问一句:这条消息是在「下指令」还是「播报事实」?答案决定它该怎么命名、怎么路由。

# 发布事件的可靠姿势:本地事件表 + 异步发送
1. 业务事务内:写入业务数据,同时插入一条「待发送」事件记录
2. 事务提交成功后再真正发送
3. 发送成功 → 把事件标记为「已发送」
4. 发送失败 → 保留记录,由定时任务重试兜底
# 先落库再发消息,避免出现「消息发了、业务没生效」的尴尬