队列与异步任务:什么时候该拆分
「下单后发短信慢」这类问题的标准答案是丢进队列。但队列不是免费的,什么时候值得拆分,需要一个判断框架。
判断标准:三个问题
- 用户是否需要立刻看到结果? 发短信、生成报表、同步到搜索引擎——不需要,可异步。扣库存、计算价格——需要,必须同步。
- 失败是否可接受? 短信失败可以重试甚至放弃;扣款失败必须当场告诉用户。
- 耗时可否预付? 处理耗时远大于接口耗时(如视频转码),同步等待毫无意义。
三个问题里有一个「不需要/可接受/可以」,就值得拆。
消息可靠性的三个环节
生产 → Broker → 消费
- 生产端:本地消息表。业务和消息记录在同一个事务里落库,后台任务扫描发送,失败重试;
- Broker:持久化 + 副本。Redis 做队列要开 AOF 且接受丢失风险,严肃场景用 RabbitMQ/Kafka/RocketMQ;
- 消费端:幂等(消息会重复投递)+ 失败重试 + 死信队列。
消费失败的分类处理很重要:可重试错误(网络抖动)进重试队列;不可重试错误(参数非法)直接进死信并告警,无限重试只会堆积。
顺序性问题
同一实体的消息要求有序时,按实体 ID 分区(Kafka 的 partition key),同一 key 落同一分区即保序。全局有序的代价极高,几乎没有业务真的需要。
常见的过度设计
- QPS 几十的系统引入 Kafka 集群,运维成本远超收益;
- 简单的延时任务(15 分钟关单)硬上 MQ 延迟消息,用数据库扫描或 Redis 过期监听更简单;
- 把队列当 RPC 用,同步语义伪装成异步,链路追踪一塌糊涂。
一个务实建议
先让链路跑起来(哪怕同步),用监控找到真实的耗时分布,再把确实拖慢接口的环节异步化。异步化应该是测量驱动的重构,而不是架构起步时的仪式。