消息中間件之RabbitMQ
死信隊列
一、死信隊列
1.1. 簡述
RabbitMQ
死信隊列俗稱備胎隊列,消息中間件因爲某種原因拒收該消息後,可以轉移到死信隊列中存放,死信隊列也可以有交換機和路由key等。
1.2. 產生原因:
- 消息隊列投遞的到
MQ
中存放,消息已經過期 - 隊列達到最大的長度,隊列容器已經滿了生產者拒絕接收消息
- 消費者消費多次消息失敗,就會轉移到死信隊列中
1.3. 注意:
死信隊列最好不和和正常隊列存放在同一個服務器中,應該分開服務器存放。
二、Springboot
整合死信隊列
2.1. Maven
依賴
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.2. 配置文件
這是需要先創建一個/long
的virtual-host
否則啓動 報錯。
spring:
rabbitmq:
# 連接地址
host: 192.168.252.137
# 端口號
port: 5672
# 賬號
username: guest
# 密碼
password: guest
# 地址
virtual-host: /long
相關隊列配置
public class RabbitVariable {
/** 死信交換機 */
public final static String DLX_EXCHANGE = "long_dlx_exchange";
/** 死信隊列 */
public final static String DLX_ORDER_DLX_QUEUE = "long_order_dlx_queue";
/** 路由key */
public final static String DLX_ROUTING_KEY = "long_dlx_key";
/** 訂單交換機 */
public final static String ORDER_EXCHANGE = "long_order_exchange";
/** 訂單隊列 */
public final static String ORDER_QUEUE = "long_order_queue";
/** 路由key */
public final static String ORDER_ROUTING_KEY = "long_order_key";
}
2.3. 配置類
@Component
public class RabbitConfig {
/**
* 死信交換機
* @return
*/
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(RabbitVariable.DLX_EXCHANGE);
}
/**
* 死信隊列
* @return
*/
@Bean
public Queue dlxQueue() {
return new Queue(RabbitVariable.DLX_ORDER_DLX_QUEUE);
}
/**
* 綁定死信隊列到死信交換機
* @return
*/
@Bean
public Binding binding() {
return BindingBuilder.bind(dlxQueue())
.to(dlxExchange())
.with(RabbitVariable.DLX_ROUTING_KEY);
}
/**
* 訂單業務交換機
* @return
*/
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(RabbitVariable.ORDER_EXCHANGE);
}
/**
* 聲明訂單隊列
* @return
*/
@Bean
public Queue orderQueue() {
// 訂單隊列綁定到我們的死信交換機
Map<String, Object> arguments = new HashMap<>(2);
// 綁定我們的死信交換機
arguments.put("x-dead-letter-exchange", RabbitVariable.DLX_EXCHANGE);
// 綁定我們的路由key
arguments.put("x-dead-letter-routing-key", RabbitVariable.DLX_ROUTING_KEY);
return new Queue(RabbitVariable.ORDER_QUEUE, true, false, false, arguments);
}
/**
* 綁定訂單隊列到訂單交換機
* @return
*/
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue())
.to(orderExchange())
.with(RabbitVariable.ORDER_ROUTING_KEY);
}
}
2.4. 死信隊列消費者
/**
* @author 墨龍吟
* @version 1.0.0
* @ClassName OrderDlxConsumer.java
* @Description 死信消費者
* @createTime 2020年03月02日 - 17:36
*/
@Component
public class OrderDlxConsumer {
@RabbitListener(queues = RabbitVariable.DLX_ORDER_DLX_QUEUE)
public void orderConsumer(String msg) {
System.out.println("死信隊列消費訂單消息" + msg);
}
}
2.5. 訂單消費者
在測試死信對列的時候將代碼註釋掉就可以
@Component
public class OrderConsumer {
/**
* 監聽隊列回調的方法
*
* @param msg
*/
@RabbitListener(queues = RabbitVariable.ORDER_QUEUE)
public void orderConsumer(String msg) {
System.out.println("正常訂單消費者消息:" + msg);
}
}
2.6. 控制器發送消息
/**
* 投遞消息,失效時間10s Expiration 時間單位是ms
* @return
*/
@GetMapping("/sendMsg")
public String sendMsg() {
String msg = "發送消息" + UUID.randomUUID();
System.out.println("發送消息:" + msg);
rabbitTemplate.convertAndSend(RabbitVariable.ORDER_EXCHANGE, RabbitVariable.ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
return "success";
}
2.7. 結果
三、RabbitMQ
解決消息堆積
產生的背景:如果沒有及時的消費者消費消息,生產者一直不斷往隊列服務器存放消息會導致消息堆積。
對應的場景:
- 沒有消費者消費的情況下:通過死信隊列、設置消息有效期(相當於對我們的消息設置有效期,在規定的時間內如果沒有消費的話,自動過期,過期的時候會執行客戶端回調監聽的方法將消息存放到數據庫記錄,後期實現不補償。)
- 有一個消費者消費的情況:應該提高我們的消費者,實現集羣
四、消息中間件獲取消費結果
解決方案: 異步+主動查詢
簡單實現:(對第二部分代碼就行修改就可以)
4.1. 修改控制器
@GetMapping("/sendMsg")
public OrderEntity sendOrderMsg() {
// 訂單實體
OrderEntity entity = new OrderEntity(UUID.randomUUID().toString(), "訂單消息實體");
String jsonString = JSONObject.toJSONString(entity);
sendMsg(jsonString);
// 返回訂單實體,或者訂單ID
return entity;
}
/** 異步投遞消息(使用同的話可以,使用ACK確認機制,但是效率比較低) */
@Async
public void sendMsg(String msg) {
rabbitTemplate.convertAndSend(RabbitVariable.ORDER_EXCHANGE, RabbitVariable.ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
// 投遞失敗可以將失敗的訂單信息存儲到專門的表中去記錄
}
/**
* 1. 客戶端主動查詢接口
* 2. 查詢(投遞失敗表)該訂單的消息是否投遞失敗
* 3. 在查詢訂單數據庫
*/
@RequestMapping("getOrder")
public Object getOrder(String orderId) {
OrderEntity orderEntity = orderMapper.getOrder(orderId);
if (orderEntity == null) {
return "消息正在異步的處理中";
}
return orderEntity;
}
4.2. 訂單消費者
@Component
public class OrderConsumer {
/**
* 監聽隊列回調的方法
*
* @param msg
*/
@RabbitListener(queues = RabbitVariable.ORDER_QUEUE)
public void orderConsumer(String msg) {
System.out.println("正常訂單消費者消息:" + msg);
OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
if (orderEntity == null) {
return;
}
// 保存到數據庫
}
}