RabbitMQ 延遲消息應用場景:
- 訂單指定時間內未支付則自動取消
- 用戶註冊後,三天內未登陸,自動短信提醒
- 新入駐店鋪,十天內未上傳產品,自動短信提醒
- 用戶發起退貨退款,3天內未得到處理則自動通知相關運營人員
- 預定會議,會議前10分鐘自動提醒各位參會人員
- 提醒類場景,如坐火車,坐飛機,接待外來參觀人員等等
RabbitMQ實現延時消息有兩種方案,第一種是採用rabbitmq-delayed-message-exchange 插件實現,第二種則是利用DLX(Dead Letter Exchanges)+ TTL(消息存活時間)來間接實現。
DLX(Dead Letter Exchanges)+ TTL(消息存活時間)流程圖
DLX+ TTL缺點:
消息到了過期時間可能並不會按時“死亡“,因爲 RabbitMQ 只會檢查第一個消息是否過期,如果過期則丟到死信隊列,如果第一個消息的延時時長很長,而第二個消息的延時時長很短,則第二個消息並不會優先得到執行。
延遲插件安裝:
RabbitMQ的延遲插件rabbitmq_delayed_message_exchange,此處不詳細講。
實現延遲消息(未支付訂單指定時間內自動取消):
一個典型的延遲消息使用場景,待支付訂單30分鐘不支付(待支付狀態),訂單就會被取消(取消狀態)
<!--消息隊列相關依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
-
application.yml
添加RabbitMQ的相關配置; -
spring: rabbitmq: host: localhost # rabbitmq的連接地址 port: 5672 # rabbitmq的連接端口號 virtual-host: /# rabbitmq的虛擬host username: admin # rabbitmq的用戶名 password: 123 # rabbitmq的密碼 publisher-confirms: true #如果對異步消息需要回調必須設置爲true
-
創建RabbitMQ配置,主要用於配置交換機、隊列和綁定關係;
-
/** * 定時取消未支付訂單消息隊列配置 */ @Configuration public class CancelNonPayOrderRabbitMqConfig { /** * 聲明交換機 */ @Bean CustomExchange delayedCustomExchange() { //創建一個自定義交換機,可以發送延遲消息 Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange("order-delayed-exchange", "x-delayed-message",true, false,args); } /** * 聲明隊列 */ @Bean public Queue delayedQueue() { return new Queue("order-delayed-queue"); } /** * 將隊列綁定到交換機 */ @Bean public Binding delayedQueueBindingDelayedCustomExchange(CustomExchange delayedCustomExchange,Queue delayedQueue) { return BindingBuilder .bind(queue) .to(delayedCustomExchange) .with("order-delayed-routing_key") .noargs(); } }
-
待支付訂單消息生產者,通過給消息設置
x-delay
頭來設置消息從交換機發送到隊列的延遲時間;/** * 訂單消息的生產者,用戶創建訂單時調用sendMessage方法,此時訂單狀態爲待支付 */ @Component public class SenderOrderMassge { private static Logger LOGGER =LoggerFactory.getLogger(OrderMassgeSender.class); @Autowired private AmqpTemplate amqpTemplate; /** * orderId 訂單id * delayTimes 延遲時間,單位毫秒 */ public void sendMessage(Long orderId,final long delayTimes){ //發消息 amqpTemplate.convertAndSend("order-delayed-exchange", "order-delayed-routing_key", orderId, new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //給消息設置延遲毫秒值 message.getMessageProperties().setHeader("x-delay",delayTimes); return message; } }); LOGGER.info("send order success, orderId:{}",orderId); } }
-
取消訂單消息的處理者,用於處理訂單延遲插件隊列中的消息。
/** * 取消訂單消息的處理者 */ @Component @RabbitListener(queues = "order-delayed-queue") public class CancelNonPayOrderReceiver { private static Logger LOGGER =LoggerFactory.getLogger(CancelNonPayOrderReceiver.class); @Autowired private OrderService orderService; @RabbitHandler public void handle(Long orderId){ LOGGER.info("handle non pay order, orderId:{}",orderId); //更改訂單狀態爲"已取消"狀態 orderService.cancelOrder(orderId); LOGGER.info("訂單已經取消,orderId:{}",orderId); } }
-
然後在我們的訂單業務實現類中添加如下邏輯,當下單成功之前,往消息隊列中發送一個訂單的延遲消息,這樣如果訂單沒有被支付的話,就能取消訂單了;
/** * 訂單管理Service */ @Service public class OrderServiceImpl implements OrderService { private static Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class); @Autowired private SenderOrderMassge senderOrderMassge ; @Override public Result addOrder(Order order) { //設置訂單ID和狀態待支付 long orderId = 10L;//訂單ID生成邏輯自行設計 order.setOrderId(orderId); order.setStatus("待支付"); //此處保存訂單信息邏輯 //獲取訂單超時時間30分鐘 long delayTimes = 1800* 1000; //發送延遲消息 senderOrderMassge.sendMessage(orderId, delayTimes); return Result.success(0, "下單成功"); } @Override public Order findOrder(Long orderId) { //通過orderId獲取訂單信息,具體獲取自己實現 return order; } @Override public void cancelOrder(Long orderId) { //獲取訂單信息,判斷訂單狀態,如果是已支付則不取消訂單 Order order = findOrder(orderId) if("待支付".equals(order.status)){ //取消訂單操作 order.setOrderId(orderId); order.setStatus("已取消"); //此處保存訂單信息邏輯 } LOGGER.info("cancel order success,orderId:{}",orderId); } }
總結
- 創建訂單都會丟消息到延時消息隊列,如果設置30分鐘過期時間,會有30分鐘的冗餘,不管已支付,未支付,都會多佔30分鐘的內存
- 如果訂單已經支付,丟掉此消息即可