前言
延遲消費在 RabbitMq 沒有屬性可以設置,只能通過 死信交換器(DLX)和設置過期時間(TTL)結合起來達到延遲的效果,所以我要介紹DLX和TTL以及實現延遲隊列。
正文
使用所有框架和中間件的版本
框架 | 版本 |
Spring Boot | 2.1.5.RELEASE |
RabbitMq | 3.7.15 |
JDK |
1.8.0_144 |
Erlang | 22.0.2 |
過期時間(TTL)
TTL是Time To Live的縮寫, 也就是生存時間。RabbitMq支持對消息和隊列設置TTL,對消息這設置是在發送的時候指定,對隊列設置是從消息入隊列開始計算, 只要超過了隊列的超時時間配置, 那麼消息會自動清除。
如果兩種方式一起使用消息對TTL和隊列的TTL之間較小的爲準,也就是消息5s過期,隊列是10s,那麼5s的生效。
默認是沒有過期時間的,表示消息沒有過期時間;如果設置爲0,表示消息在投遞到消費者的時候直接被消息,否則丟棄。
設置消息的過期時間用 x-message-ttl 參數實現,單位毫秒。
設置隊列的過期時間用 x-expires 參數,單位毫秒,注意,不能設置爲0。
死信交換器(DLX)
DLX是Dead-Letter-Exchange的縮寫,全稱死信交換器。成爲死信隊列後,可以被重新發送到另外一個交換器中,這個交換器就是DLX,綁定DLX到隊列稱爲死信隊列。注意,死信隊列和死信交換器不一樣哦。
成爲死信一般由以下幾種情況:
- 消息被拒絕 (basic.reject or basic.nack) 且帶 requeue=false 參數
- 消息的TTL-存活時間已經過期
- 隊列長度限制被超越(隊列滿)
DLX和一般的交換器沒有區別,可以聲明在任何的隊列上,理解爲隊列的一個屬性吧。當這個隊列有死信時會根據設置自動的將死信重新發布到設置的DLX上進行消費。這個消費了死信的隊列稱之爲死信隊列,並不是綁定了DLX的隊列是死信隊列,大家要區分清楚。
通過在隊列裏設置 x-dead-letter-exchange 參數來聲明DLX,如果當前DLX是 direct 類型還要 聲明 x-dead-letter-routing-key 參數來指定路由鍵,如果沒有指定,則使用原隊列的路由鍵。
延遲隊列
通過DLX和TTL模擬出延遲隊列的功能,即,消息發送以後,不讓消費者拿到,而是等待過期時間,變成死信後,發送給死信隊列進行消費。
延遲隊列流程圖
maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
RabbitMq配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=yanlin
spring.rabbitmq.password=yanlin
生產者
@RestController
public class DelayController {
@Autowired
private AmqpTemplate amqpTemplate;
@GetMapping("/delay/{id}")
public String delayTest(@PathVariable Integer id) {
Order order = new Order(id, "我等了10s");
amqpTemplate.convertAndSend("normal_exchange", "normal_key", order);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
String dateString = sdf.format(new Date());
return "請求時間" + dateString;
}
}
配置監聽
@Configuration
public class DelayListener {
//死信隊列
private static final String DELAY_QUEUE = "delay_queue";
//正常隊列
private static final String NORMAL_QUEUE = "normal_queue";
//死信交換器
private static final String DELAY_EXCHANGE = "delay_exchange";
//死信路由鍵
private static final String DELAY_KEY = "delay_key";
/**
* 正常隊列
*
* @return
*/
@Bean
public Queue normalQueue() {
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl", 10000);//設置10s過期時間
//x-dead-letter-exchange參數是設置該隊列的死信交換器(DLX)
map.put("x-dead-letter-exchange", DELAY_EXCHANGE);
//x-dead-letter-routing-key參數是給這個DLX指定路由鍵
map.put("x-dead-letter-routing-key", DELAY_KEY);
return new Queue(NORMAL_QUEUE, true, false, false, map);
}
/**
* 正常交換器
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return new DirectExchange("normal_exchange", true, false);
}
/**
* 正常隊列綁定
*
* @param normalQueue
* @param normalExchange
* @return
*/
@Bean
public Binding normalBinding(Queue normalQueue, DirectExchange normalExchange) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with("normal_key");
}
/**
* 死信隊列
*
* @return
*/
@Bean
public Queue delayQueue() {
return new Queue(DELAY_QUEUE);
}
/**
* 死信交換器
*
* @return
*/
@Bean
public DirectExchange delayExchange() {
return new DirectExchange(DELAY_EXCHANGE, true, false);
}
/**
* 死信隊列綁定交換器
*
* @param delayQueue
* @param delayExchange
* @return
*/
@Bean
Binding delayBinding(Queue delayQueue, DirectExchange delayExchange) {
return BindingBuilder.bind(delayQueue).to(delayExchange).with(DELAY_KEY);
}
消費者
@Service
public class DelayConsumer {
/**
* 延遲消費方法
*
* @param order
*/
@RabbitListener(queues = "delay_queue")
@RabbitHandler
public void delay(Order order) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
System.out.println("------" + sdf.format(new Date()) + "------");
System.out.println("請觀察頁面返回的時間與上面打印時間對比");
System.out.println("這是延遲10s消費的消息:" + order.getName());
}
}
啓動項目,生產者發送消息後返回的時間與消費者控制檯打印的時間對比,差了10s,延遲隊列完成。
項目如圖
github地址 https://github.com/362460453/rabbitMQ-demo