1. 程式人生 > >SpringBoot使用RabbitMQ延時佇列

SpringBoot使用RabbitMQ延時佇列

延時佇列

延時佇列的使用場景:

1.訂單業務:在電商中,使用者下單後30分鐘後未付款則取消訂單。

2.簡訊通知:使用者下單並付款後,1分鐘後發簡訊給使用者。

延時佇列實現思路

AMQP協議和RabbitMQ佇列本身沒有直接支援延遲佇列功能,但是我們可以通過RabbitMQ的兩個特性來曲線實現延遲佇列:

特性一:Time To Live(TTL)

RabbitMQ可以針對Queue設定x-expires 或者 針對Message設定 x-message-ttl,來控制訊息的生存時間,如果超時(兩者同時設定以最先到期的時間為準),則訊息變為dead letter(死信)
RabbitMQ針對佇列中的訊息過期時間有兩種方法可以設定。
A: 通過佇列屬性設定,佇列中所有訊息都有相同的過期時間。
B: 對訊息進行單獨設定,每條訊息TTL可以不同。

如果同時使用,則訊息的過期時間以兩者之間TTL較小的那個數值為準。訊息在佇列的生存時間一旦超過設定的TTL值,就成為dead letter

特性二:Dead Letter Exchanges(DLX)

RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可選)兩個引數,如果佇列內出現了dead letter,則按照這兩個引數重新路由轉發到指定的佇列。
x-dead-letter-exchange:出現dead letter之後將dead letter重新發送到指定exchange
x-dead-letter-routing-key:出現dead letter之後將dead letter重新按照指定的routing-key傳送
隊列出現dead letter的情況有:
訊息或者佇列的TTL過期
佇列達到最大長度

訊息被消費端拒絕(basic.reject or basic.nack)並且requeue=false

SpringBoot整合RabbitMQ

在 pom.xml 中新增 spring-boot-starter-amqp的依賴

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

在 application.yml檔案中配置rabbitmq相關內容

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

具體編碼實現

1.配置佇列

package com.lzc.rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
@Slf4j
public class DelayRabbitConfig {


    /**
     * 延遲佇列 TTL 名稱
     */
    private static final String ORDER_DELAY_QUEUE = "user.order.delay.queue";
    /**
     * DLX,dead letter傳送到的 exchange
     * 延時訊息就是傳送到該交換機的
     */
    public static final String ORDER_DELAY_EXCHANGE = "user.order.delay.exchange";
    /**
     * routing key 名稱
     * 具體訊息傳送在該 routingKey 的
     */
    public static final String ORDER_DELAY_ROUTING_KEY = "order_delay";

    public static final String ORDER_QUEUE_NAME = "user.order.queue";
    public static final String ORDER_EXCHANGE_NAME = "user.order.exchange";
    public static final String ORDER_ROUTING_KEY = "order";

    /**
     * 延遲佇列配置
     * <p>
     * 1、params.put("x-message-ttl", 5 * 1000);
     * 第一種方式是直接設定 Queue 延遲時間 但如果直接給佇列設定過期時間,這種做法不是很靈活,(當然二者是相容的,預設是時間小的優先)
     * 2、rabbitTemplate.convertAndSend(book, message -> {
     * message.getMessageProperties().setExpiration(2 * 1000 + "");
     * return message;
     * });
     * 第二種就是每次傳送訊息動態設定延遲時間,這樣我們可以靈活控制
     **/
    @Bean
    public Queue delayOrderQueue() {
        Map<String, Object> params = new HashMap<>();
        // x-dead-letter-exchange 聲明瞭佇列裡的死信轉發到的DLX名稱,
        params.put("x-dead-letter-exchange", ORDER_EXCHANGE_NAME);
        // x-dead-letter-routing-key 聲明瞭這些死信在轉發時攜帶的 routing-key 名稱。
        params.put("x-dead-letter-routing-key", ORDER_ROUTING_KEY);
        return new Queue(ORDER_DELAY_QUEUE, true, false, false, params);
    }
    /**
     * 需要將一個佇列繫結到交換機上,要求該訊息與一個特定的路由鍵完全匹配。
     * 這是一個完整的匹配。如果一個佇列繫結到該交換機上要求路由鍵 “dog”,則只有被標記為“dog”的訊息才被轉發,
     * 不會轉發dog.puppy,也不會轉發dog.guard,只會轉發dog。
     * @return DirectExchange
     */
    @Bean
    public DirectExchange orderDelayExchange() {
        return new DirectExchange(ORDER_DELAY_EXCHANGE);
    }
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(delayOrderQueue()).to(orderDelayExchange()).with(ORDER_DELAY_ROUTING_KEY);
    }

    @Bean
    public Queue orderQueue() {
        return new Queue(ORDER_QUEUE_NAME, true);
    }
    /**
     * 將路由鍵和某模式進行匹配。此時佇列需要繫結要一個模式上。
     * 符號“#”匹配一個或多個詞,符號“*”匹配不多不少一個詞。因此“audit.#”能夠匹配到“audit.irs.corporate”,但是“audit.*” 只會匹配到“audit.irs”。
     **/
    @Bean
    public TopicExchange orderTopicExchange() {
        return new TopicExchange(ORDER_EXCHANGE_NAME);
    }

    @Bean
    public Binding orderBinding() {
        // TODO 如果要讓延遲佇列之間有關聯,這裡的 routingKey 和 繫結的交換機很關鍵
        return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY);
    }

}

2.建立一個Order實體類

package com.lzc.rabbitmq.dataobject;

import lombok.Data;

import java.io.Serializable;

@Data
public class Order implements Serializable {


    private static final long serialVersionUID = -2221214252163879885L;
    
    private String orderId; // 訂單id

    private Integer orderStatus; // 訂單狀態 0:未支付,1:已支付,2:訂單已取消

    private String orderName; // 訂單名字
}

3.接收者

package com.lzc.rabbitmq.config;

import com.lzc.rabbitmq.dataobject.Order;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class DelayReceiver {

    @RabbitListener(queues = {DelayRabbitConfig.ORDER_QUEUE_NAME})
    public void orderDelayQueue(Order order, Message message, Channel channel) {
        log.info("###########################################");
        log.info("【orderDelayQueue 監聽的訊息】 - 【消費時間】 - [{}]- 【訂單內容】 - [{}]",  new Date(), order.toString());
        if(order.getOrderStatus() == 0) {
            order.setOrderStatus(2);
            log.info("【該訂單未支付,取消訂單】" + order.toString());
        } else if(order.getOrderStatus() == 1) {
            log.info("【該訂單已完成支付】");
        } else if(order.getOrderStatus() == 2) {
            log.info("【該訂單已取消】");
        }
        log.info("###########################################");
    }
}

4.傳送者

package com.lzc.rabbitmq.config;

import com.lzc.rabbitmq.dataobject.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class DelaySender {

    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendDelay(Order order) {
        log.info("【訂單生成時間】" + new Date().toString() +"【1分鐘後檢查訂單是否已經支付】" + order.toString() );
        this.amqpTemplate.convertAndSend(DelayRabbitConfig.ORDER_DELAY_EXCHANGE, DelayRabbitConfig.ORDER_DELAY_ROUTING_KEY, order, message -> {
            // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那麼這一句也可以省略,具體根據業務需要是宣告 Queue 的時候就指定好延遲時間還是在傳送自己控制時間
            message.getMessageProperties().setExpiration(1 * 1000 * 60 + "");
            return message;
        });
    }
}

5.測試,訪問http://localhost:8080/sendDelay,檢視日誌輸出

package com.lzc.rabbitmq.controller;

import com.lzc.rabbitmq.config.DelaySender;
import com.lzc.rabbitmq.config.Sender;
import com.lzc.rabbitmq.dataobject.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.DelayQueue;

@RestController
public class TestController {

    @Autowired
    private DelaySender delaySender;

    @GetMapping("/sendDelay")
    public Object sendDelay() {
        Order order1 = new Order();
        order1.setOrderStatus(0);
        order1.setOrderId("123456");
        order1.setOrderName("小米6");

        Order order2 = new Order();
        order2.setOrderStatus(1);
        order2.setOrderId("456789");
        order2.setOrderName("小米8");

        delaySender.sendDelay(order1);
        delaySender.sendDelay(order2);
        return "ok";
    }
}

6.日誌輸出

【訂單生成時間】Mon Jun 18 11:55:36 CST 2018【1分鐘後檢查訂單是否已經支付】Order(orderId=123456, orderStatus=0, orderName=小米6)
【訂單生成時間】Mon Jun 18 11:55:36 CST 2018【1分鐘後檢查訂單是否已經支付】Order(orderId=456789, orderStatus=1, orderName=小米8)
 ###########################################
【orderDelayQueue 監聽的訊息】 - 【消費時間】 - [Mon Jun 18 11:56:36 CST 2018]- 【訂單內容】 - [Order(orderId=123456, orderStatus=0, orderName=小米6)]
【該訂單未支付,取消訂單】Order(orderId=123456, orderStatus=2, orderName=小米6)
 ###########################################
 ###########################################
【orderDelayQueue 監聽的訊息】 - 【消費時間】 - [Mon Jun 18 11:56:36 CST 2018]- 【訂單內容】 - [Order(orderId=456789, orderStatus=1, orderName=小米8)]
【該訂單已完成支付】
 ###########################################

相關推薦

php訂單處理-佇列

延遲佇列,顧名思義它是一種帶有延遲功能的訊息佇列。 那麼,是在什麼場景下我才需要這樣的佇列呢? 一、背景 先看看一下業務場景: 1.會員過期前3天傳送召回通知 2.訂單支付成功後,5分鐘後檢測下游環節是否都正常,比如使用者購買會員後,各種會員狀態是否都設定成功 3.如何定期檢查處於退款狀態

go佇列

package main import ( "errors" "flag" "fmt" log "github.com/cihub/seelog" "github.com/garyburd/redigo/redis" "github.com/robfig/cron" "runtime" "strings"

Redis 非同步訊息佇列佇列

        訊息中介軟體,大家都會想到  Rabbitmq 和 Kafka 作為訊息佇列中介軟體,來給應用程式之間增加非同步訊息傳遞功能。這兩個中介軟體都是專業的訊息佇列中介軟體,特性之多超出了大多數人的理解能力。但是這種屬於重量級的應

redis —— 佇列

我們平時習慣於使用 Rabbitmq 和 Kafka 作為訊息佇列中介軟體,來給應用程式之間增加非同步訊息傳遞功能。這兩個中介軟體都是專業的訊息佇列中介軟體,特性之多超出了大多數人的理解能力。 使用過 Rabbitmq 的同學知道它使用起來有多複雜,發訊息之前要建立 Exchange,再建立 Q

rabbitmq實現佇列(死信佇列

基於佇列和基於訊息的TTL TTL是time to live 的簡稱,顧名思義指的是訊息的存活時間。rabbitMq可以從兩種維度設定訊息過期時間,分別是佇列和訊息本身。 佇列訊息過期時間-Per-Queue Message TTL: 通過設定佇列的x-message-ttl引數來設定指定佇列上訊息的存活時

佇列

這節延時佇列就是乘著上節的鎖衝突處理的延時佇列來的。   但是在此之前我要講一下我們如何編輯python檔案,因為總是寫在命令列不能儲存程式碼,也不能修改。 我們可以先vi  檔名.py這樣就建立了一個.py檔案 然後我們到這個檔案所在的位置開啟這個檔案就可以編輯了 儲存後在命令

訂單15分鐘未支付超時取消-佇列

延遲佇列,顧名思義它是一種帶有延遲功能的訊息佇列。 那麼,是在什麼場景下我才需要這樣的佇列呢? 一、背景 先看看一下業務場景: 1.會員過期前3天傳送召回通知 2.訂單支付成功後,5分鐘後檢測下游環節是否都正常,比如使用者購買會員後,各種會員狀態是否都設定成功

你真的瞭解佇列嗎(一)

1 使用場景 關閉空閒連線。伺服器中,有很多客戶端的連線,空閒一段時間之後需要關閉之。 清理過期資料業務上。比如快取中的物件,超過了空閒時間,需要從快取中移出。 任務超時處理。在網路協議滑動視窗請求應答式互動時,處理超時未響應的請求。 下單之後如果三十分鐘之內沒有付款就自動取消訂單。 訂餐

Java 使用RabbitMQ外掛實現佇列

Springboot專案,windows環境 環境配置 在rabbitmq 3.5.7及以上的版本提供了一個外掛(rabbitmq-delayed-message-exchange)來實現延遲佇列功能。同時外掛依賴Erlang/OPT 18.0及以上。 外掛下載地址: http

使用Redis實現輕量級佇列

A:需求說明: 如果系統中需要用到定時執行計劃的,又不想用到中介軟體,如果輪詢資料庫的話,會導致大量資源消耗,這樣我們就可以使用Redis來實現類似功(需要使用rabbitMQ的請看這裡:https://blog.csdn.net/u010096717/article/de

Redis佇列方案

總體方案 建立一個定時任務,每一次執行完後間隔一定時間就會掃描快取,快取中一旦添加了任務,就會被掃描到,然後傳送到訊息佇列,監聽器一旦監聽到訊息就會進行處理,如果處理失敗,則再次生成任務(次數加1,時間戳會根據規則增加),到達規定次數後則不在執行 具體細

java 中高併發(DelayQueue)DelayQueue佇列操作例項(2018.7.16)

應用場景 :               使用者購票後,產生未支付訂單,系統30分鐘後未支付自動取消。(每個訂單放入執行緒中設定超時時間,自動處理)                  本文 用一個執行緒 來迴圈跑對列,取消佇列中的資料!!!業務實現思路:           

laravel 佇列的使用

config/queue.php檔案是佇列的配置檔案,可以修改你所需要的配置 在.env檔案中配置你需要的使用的儲存方式,本篇文章使用的是datebase資料庫方式,QUEUE_DRIVE

SpringBoot使用RabbitMQ佇列

延時佇列延時佇列的使用場景:1.訂單業務:在電商中,使用者下單後30分鐘後未付款則取消訂單。2.簡訊通知:使用者下單並付款後,1分鐘後發簡訊給使用者。延時佇列實現思路AMQP協議和RabbitMQ佇列本身沒有直接支援延遲佇列功能,但是我們可以通過RabbitMQ的兩個特性來曲

Docker下RabbitMQ佇列實戰兩部曲之一:極速體驗

有的應用場景中,向RabbitMQ發出訊息後,我們希望消費方不要立即消費,可以通過延時佇列來實現,思路是將訊息傳送到A佇列,此佇列沒有消費者,等訊息過期後會進入A佇列的Dead Letter Exchange中,B佇列綁定了這個Dead Letter Excha

RabbitMQ進階使用-佇列的配置(Spring Boot)

依賴 MAVEN配置pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spri

java實現rabbitMQ佇列詳解以及spring-rabbit整合教程

java實現rabbitMQ延時佇列詳解 這是我在公司開發中使用的倆套方案,感興趣的話可以看一下:點選下載 在實際的業務中我們會遇見生產者產生的訊息,不立即消費,而是延時一段時間在消費。RabbitMQ本身沒有直接支援延遲佇列功能,但是我們可以根據其特性Per-Queu

Docker下RabbitMQ佇列實戰兩部曲之二:細說開發

本章是《Docker下RabbitMQ延時佇列實戰兩部曲》的終篇,上一章《Docker下RabbitMQ延時佇列實戰兩部曲之一:極速體驗》我們快速體驗了延時佇列的生產和消費,今天來實戰整個開發過程; SpringBoot框架下進行RabbitMQ開發,相關

Springboot+rabbitmq實現佇列的兩種方式

什麼是延時佇列,延時佇列應用於什麼場景 延時佇列顧名思義,即放置在該佇列裡面的訊息是不需要立即消費的,而是等待一段時間之後取出消費。 那麼,為什麼需要延遲消費呢?我們來看以下的場景 網上商城下訂單後30分鐘後沒有完成支付,取消訂單(如:淘寶、去哪兒網) 系統

DelayQueue佇列操作例項

DelayQueue延時佇列,當佇列中的元素到達延遲時間時才會被取出。佇列元素會按照最終執行時間在佇列中進行排序。 最近剛學,本篇先給出一個實際使用的例子。 首先佇列物件當然就是DelayQueue。而佇列元素則需要實現Delayed這個介面,並實現該介面c