1.2 应用场景盘点与选型权衡 本节摘要:消息队列适合"结果允许延迟、动作需要解耦"的场景,典型如异步任务、流量削峰、事件广播、跨系统数据同步;不适合强一致事务、即时查询和低延迟强顺序链路。本节在 1.1 的机理之上完成选型决策,给出正反两面的判定清单,并用一个积分系统的完整案例演示引入前后的架构差异。 上一节算清了收益与代价的账,这一节把账本落到具体场景:拿到一个业务需求,怎么判断该不该用消息队列?这决定了你后面的所有代码是投资还是负债。 先划一条资格线 判定的核心只有一个问题:这个动作的结果,调用方需要立刻知道吗?需要,就留在同步链路;不需要,才有资格进队列。沿着这条资格线,常见场景分成两拨。 适合消息队列的场景,按出现频率排序: 异步任务:发短信、生成报表、转码压缩。
本节摘要:消息队列适合"结果允许延迟、动作需要解耦"的场景,典型如异步任务、流量削峰、事件广播、跨系统数据同步;不适合强一致事务、即时查询和低延迟强顺序链路。本节在 1.1 的机理之上完成选型决策,给出正反两面的判定清单,并用一个积分系统的完整案例演示引入前后的架构差异。
上一节算清了收益与代价的账,这一节把账本落到具体场景:拿到一个业务需求,怎么判断该不该用消息队列?这决定了你后面的所有代码是投资还是负债。
判定的核心只有一个问题:**这个动作的结果,调用方需要立刻知道吗?**需要,就留在同步链路;不需要,才有资格进队列。沿着这条资格线,常见场景分成两拨。
适合消息队列的场景,按出现频率排序:
不适合的场景同样要记牢:账户扣款与入账这种强一致操作,消息队列的最终一致窗口可能造成超卖或负余额;用户登录后的即时鉴权查询,走队列等于主动给响应时间加几十毫秒起跳;而严格先后依赖的处理链(比如先算总额再分摊优惠),异步化后要额外设计排序与依赖管理,得不偿失。
还有一个容易被忽略的维度:团队运维能力。引入一个 Broker,意味着多一个需要监控、备份、升级、故障演练的常驻组件。小团队、低流量、无专职运维的系统,用数据库轮询表可能比维护一套 RabbitMQ 更省心。选型从来不只是技术题。
背景:某内容平台的下单接口在支付成功后同步调用积分服务加积分,逻辑只有三十行,平日无恙。一次运营活动让支付量涨了八倍,积分服务数据库连接池打满,支付回调随之超时——和第 1 章开篇的事故同构,这次我们要动手修。
操作:改造分四步。第一步,梳理依赖关系,确认"加积分"符合资格线:用户晚几秒到账积分完全可接受,且支付服务不依赖加积分的结果。第二步,支付服务侧删掉对积分服务的直接调用,改为向交换机 payment.events 发布一条 payment.succeeded 事件。第三步,积分服务启动一个消费者,绑定该交换机,收到事件后写积分流水。第四步,加一个兜底:积分服务处理失败的消息进入死信队列(机制详见第 3 章),由对账任务定时补偿。
# 改造后的支付服务侧:只发事件,不调积分 channel.basic_publish( exchange="payment.events", routing_key="payment.succeeded", body=json.dumps({ "order_id": order_id, "user_id": user_id, "amount": 19900, # 单位:分 "paid_at": "2026-08-29T23:15:00+08:00", }).encode(), properties=pika.BasicProperties( content_type="application/json", delivery_mode=2)) # 持久化消息,防服务器重启丢失
# 改造后的积分服务侧:独立消费,互不影响 def on_payment(ch, method, properties, body): event = json.loads(body) credit_service.add_points(event["user_id"], event["amount"] // 100) ch.basic_ack(delivery_tag=method.delivery_tag) # 处理成功才签收 channel.basic_consume( queue="points.payment.q", on_message_callback=on_payment) channel.start_consuming()
结果:支付回调的响应时间不再包含积分服务的耗时,积分服务被打挂时支付链路完好无损。压测数据对比:同步版在大促流量下支付成功率从 99.2% 跌到 71%;异步版支付成功率保持 99.5%,代价是积分到账延迟从零变成峰值时段约四十秒。
解读:注意改造带来的隐性变化。事件里我们只放支付域自己知道的事实,不包含"该加多少积分"的业务规则——规则属于积分服务。这样积分规则变更时支付服务零改动,解耦的价值就在这里。另外,消息用 JSON 而非内部对象序列化,是为了避免消费者与生产者共享代码,那是一种隐蔽的耦合。
变式一:新需求要求积分到账后立刻发短信通知用户。按广播思路,短信服务再起一个消费者绑定同一交换机即可,支付与积分服务一行代码不动——这就是 1.1 说的广播收益的实操形态。变式二:如果财务要求积分与支付"必须同成功同失败",那么资格线判定变了,此路不通,应改用本地消息表加事务的方案。同一个需求,资格线一变,选型就变。
某团队把"查询商品详情"也改成了走队列,理由是"解耦缓存和数据库"。结果页面响应从八十毫秒涨到一秒半——查询需要立刻返回,这是资格线的硬伤。更糟的是高峰期队列堆积,用户看到的不是旧数据而是慢数据。复盘结论:读路径异步化几乎总是错的,消息队列的舒适区在写路径和通知路径。还剩一类灰色地带值得单独交代:延迟可接受但失败不可接受的操作——比如支付成功后的发货指令,晚几分钟可以,绝不能丢。它落在资格线的边界上,正确姿势是消息队列加补偿的组合拳:走队列保吞吐,配死信与对账保必达,第 3 章的全链路方案就是为它准备的。判级不是非黑即白,边界场景的答案永远是"加一层兜底"。
⚠️ 常见坑:把消息队列当数据库用。有人把业务状态全塞进消息属性里流转,三个月后没人说得清哪条消息是权威数据。队列里只该有"发生了什么"的事件,权威状态永远在数据库里。
场景判断完毕,下一节终于轮到主角登场:写第一个生产者,把追踪单上第一条消息真正发出去。