订单半小时没付款?用 RabbitMQ 死信队列兜底关单
电商下单后"30 分钟未支付自动关单",是消息队列最经典的应用之一。思路很简单:下单时发一条消息,让它半小时后才被消费;到点还没支付,消费者就执行关单。华为云 DMS 的 RabbitMQ 版完全兼容开源 RabbitMQ,Python 用 pika 就能连。延迟消息有几种做法,托管实例最稳的是"消息过期 + 死信队列"这套,不依赖任何插件,开箱即用。比起另起一个定时任务每隔几秒扫库查"哪些订单超时了",消息队列把"什么时候该处理"这件事交给中间件,业务代码只关心"到点做什么",既省扫描开销,又不漏单。

一、为什么用死信队列做延迟
RabbitMQ 原生没有"延迟投递"这种消息类型,但有个现成机制:给队列设 x-message-ttl(消息存活时间),消息在队列里没人消费、过了 TTL 就会变成"死信",再按 x-dead-letter-exchange 被转到另一个交换机。我们在死信侧挂一个消费者专门处理"超时"事件,就实现了延迟消费。这套只用到队列参数,托管版默认支持,不用装 rabbitmq_delayed_message_exchange 插件,最省心。
二、连接 DMS RabbitMQ
控制台"实例管理"里能拿到连接地址、端口、用户名密码。不开 SSL 用 5672,开了用 5671。Python 装 pika,用 PlainCredentials 填账号,连上后声明业务队列时把 TTL 和死信参数一起带上。下面这段是真实可用的骨架:
import pika
conf = {
'host': '10.0.0.10', # 实例连接地址
'port': 5672, # 非SSL用5672,SSL用5671
'username': 'root',
'password': 'your_password',
}
creds = pika.PlainCredentials(conf['username'], conf['password'])
conn = pika.BlockingConnection(
pika.ConnectionParameters(conf['host'], conf['port'], '/', creds))
channel = conn.channel()
三、声明带 TTL 的业务队列
关键在这步:业务队列 order.delay 设 x-message-ttl=1800000(30 分钟),并指定死信交换机 dlx.order 和死信路由键 order.timeout。下单服务把订单消息发到这个队列,消息会在里面安静待 30 分钟,到点自动"死信"转走。
channel.queue_declare(
queue='order.delay',
arguments={
'x-message-ttl': 1800000,
'x-dead-letter-exchange': 'dlx.order',
'x-dead-letter-routing-key': 'order.timeout',
})
# 下单后投递:消息半小时后才会进入死信队列
channel.basic_publish(exchange='', routing_key='order.delay',
body='order-10086')
四、消费死信做关单
另起一个消费者,监听死信交换机绑定的队列。它拿到的消息,必然是已经"躺够"30 分钟的订单,此时查一下支付状态,没付就关单。这样即便支付服务短暂抖动,延迟关单也不会丢,因为消息一直躺在队列里。
channel.exchange_declare(exchange='dlx.order', exchange_type='direct')
channel.queue_declare(queue='order.timeout')
channel.queue_bind(queue='order.timeout', exchange='dlx.order',
routing_key='order.timeout')
def callback(ch, method, props, body):
order_id = body.decode()
if not paid(order_id): # 查支付状态
close_order(order_id) # 未付则关单
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue='order.timeout', on_message_callback=callback)
channel.start_consuming()

五、几个容易踩的坑
第一,TTL 是队列级别参数,改 TTL 得重建队列,已存在的队列改不了。第二,消息一旦进了死信队列被消费并 ack,就结束了,所以关单逻辑要做幂等,重复收到同一条也安全。第三,如果下单时就已支付,最好主动把 order.delay 里的消息删掉或标记,避免半小时后还误关。第四,死信队列也要声明并且绑定到死信交换机,否则消息会丢。第五,消费者崩了别慌,消息没 ack 会重投,正好保证关单最终执行。第六,TTL 的精度不是毫秒级,RabbitMQ 是"到了过期时间且消息在队列头部"才会转死信,所以队列里如果堆着更早的消息,后一条的延迟可能略大于设定值;对"30 分钟"这种量级无感,对秒级精度要求就别用这招。第七,生产环境建议给死信消费者单独部署、加监控告警,关单失败能及时被人发现,而不是悄悄堆积。
六、小结
用 RabbitMQ 的 TTL + 死信队列做延迟消息,是托管实例上最稳的方案:不用装插件、声明即生效、消息不丢。下单投业务队列、超时进死信、消费者关单,三段串起来就是一套可靠的"自动关单"兜底。
- 点赞
- 收藏
- 关注作者
评论(0)