1. 兜底方案
尽管通过多种机制提高了消息的可靠性,仍无法保证MQ消息100%的成功传递。因此,交易服务需要有一个兜底方案来确保订单的支付状态一致性,即使MQ通知失败。
1.1. 方案思路
由于MQ通知可能会失败,交易服务可以主动查询支付状态,确保即便MQ通知丢失,支付状态仍能得到正确更新。
1.2. 方案流程
- 支付服务发送MQ消息:支付服务在用户支付成功后,通过MQ通知交易服务更新订单状态。
- MQ消息可靠性:为了确保MQ消息的可靠性,采用生产者确认、消费者确认、以及消费者失败重试等策略。
- 交易服务主动查询:当MQ通知失败时,交易服务主动定期查询支付状态。
- 定时任务:由于无法确定用户支付的确切时间,采用定时任务(例如每隔20秒查询一次)来确认订单是否已支付,并更新订单状态。
1.3. 关键点
- 定时任务:解决查询时机不准确的问题,确保订单支付状态的一致性。
- MQ通知与查询结合:结合MQ消息通知和定时查询,确保支付状态最终一致。
2. 延迟消息
在电商支付业务中,尤其是库存有限的商品,需要避免用户长时间占用库存资源。如果用户下单后长时间未付款,可能导致其他用户无法购买该商品。因此,需要在用户下单后的规定时间内检查支付状态,并取消未支付订单,释放库存资源。
2.1. 延迟任务需求
为确保在一定时间后检查支付状态,通常使用延迟任务。最简单的方式是通过MQ的延迟消息。
2.2. 延迟消息实现方式
在RabbitMQ中,可以通过以下两种方式实现延迟消息:
- 死信交换机(DLX) + TTL
- 延迟消息插件
2.3. 死信交换机与TTL
- 死信交换机:
- 死信是指满足以下条件的消息:
- 消费者拒绝消息,且
requeue=false
。 - 消息超时,未被消费。
- 队列满,无法投递消息。
- 消费者拒绝消息,且
- 死信消息会投递到指定的死信交换机(Dead Letter Exchange),然后根据绑定的路由规则,转发到另一个队列。
- 死信是指满足以下条件的消息:
- TTL(消息有效期):
- 通过设置消息TTL(生存时间),当消息的TTL到期时,消息变为死信,投递到死信交换机,再根据路由规则转发到目标队列,最终由消费者处理,从而实现延迟消费。
2.4. 延迟消息的实现
- 发送消息到
ttl.fanout
交换机,并设置TTL(如5000毫秒)。 - 消息进入
ttl.queue
队列,由于没有消费者,消息超时后变为死信。 - 死信投递到死信交换机
hmall.direct
,并根据之前的RoutingKey
(例如blue
)路由到direct.queue1
。 - 如果此时有消费者与
direct.queue1
队列绑定,则该消费者可以消费该延迟消息。
2.5. 死信交换机与延迟消息的总结
- 消息TTL是通过追溯方式实现的,当消息的TTL到期后并不会立即被移除,而是在消息处于队列头时才会被处理。
- 当队列中的消息堆积较多时,过期消息可能不会按时被处理,TTL设置可能存在不准确性。