知識重點
1. RabbitMQ如果產生了消息堆積如何處理?
產生的背景:如果沒有及時的消費者消費消息,生產者一直不斷往隊列服務器存放消息會導致消息堆積
兩種場景:
1.沒有消費者消費的情況下: 死信隊列、設置消息有效期相當於對我們的消息設置有效期,在規定的時間內如果沒有消 費的話,自動過期,過期的時候會執行客戶端回調監聽的方法將消息存放到數據庫記錄,後期實現不補償。
2.有一個消費者消費的情況:應該提高我們的消費者 消費實現集羣
2. RabbitMQ如何徹底保證我們的消息不丟失?
1.MQ服務器端應該消息持久化到硬盤
2.生產者使用消息確認機制百分能夠將消息投遞到MQ成功
3.消費者使用手動acm機制確認消息百分百消費成功
3.如果隊列容量滿了,在繼續投遞可能會丟失 死信隊列
死信隊列:稱做爲備胎隊列,消息中間件隊列因爲某種消費拒絕存放該消息,可以轉移到死信隊列中存放。
死信隊列產生的背景:
1.生產者投遞消息到MQ中,消息過期了;
2.隊列的已經達到最大長度(隊列存放消息滿了)MQ拒絕接受存放該消息。
3.消費者多次消費該消息失敗的情況,也會存放死信。
死信隊列不能夠和正常隊列存放在同一個服務器中,應該分開服務器存放
簡單搭建死信隊列
1.聲明訂單和死信交換機、 訂單和死信隊列 、關鍵步驟訂單隊列綁定訂單交換機和死信交換機 、死信隊列綁定死信交換機
@Component
public class DeadLetterMQConfig {
/**
* 訂單交換機
*/
@Value("${mayikt.order.exchange}")
private String orderExchange;
/**
* 訂單隊列
*/
@Value("${mayikt.order.queue}")
private String orderQueue;
/**
* 訂單路由key
*/
@Value("${mayikt.order.routingKey}")
private String orderRoutingKey;
/**
* 死信交換機
*/
@Value("${mayikt.dlx.exchange}")
private String dlxExchange;
/**
* 死信隊列
*/
@Value("${mayikt.dlx.queue}")
private String dlxQueue;
/**
* 死信路由
*/
@Value("${mayikt.dlx.routingKey}")
private String dlxRoutingKey;
/**
* 聲明死信交換機
*/
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(dlxExchange);
}
/**
* 聲明死信隊列
*/
@Bean
public Queue dlxQueue() {
return new Queue(dlxQueue);
}
/**
* 聲明訂單業務交換機
*/
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(orderExchange);
}
/**
* 聲明訂單隊列 核心操作一
*/
@Bean
public Queue orderQueue() {
Map<String, Object> arguments = new HashMap<>(2);
// 綁定我們的死信交換機
arguments.put("x-dead-letter-exchange", dlxExchange);
// 綁定我們的路由key
arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
return new Queue(orderQueue, true, false, false, arguments);
}
/**
* 綁定訂單隊列到訂單交換機
*/
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(orderRoutingKey);
}
/**
* 綁定死信隊列到死信交換機
*/
@Bean
public Binding binding() {
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(dlxRoutingKey);
}
}
2.application.properties文件配置 交換機隊列相關名稱和其他配置
server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#開啓駝峯命名 譬如數據庫create_time 自動映射pojo屬性createTime
mybatis.configuration.map-underscore-to-camel-case=true
#配置virtual-host虛擬主機
spring.rabbitmq.virtual-host=/zhang_rabbit
#ip地址
spring.rabbitmq.host=127.0.0.1
#用戶名 密碼
spring.rabbitmq.username=zhang
spring.rabbitmq.password=zhang
#連接端口號
spring.rabbitmq.port=5672
#spring.rabbitmq.publisher-confirm-type=
#模擬演示死信隊列
mayikt.dlx.exchange=mayikt_order_dlx_exchange
mayikt.dlx.queue=mayikt_order_dlx_queue
mayikt.dlx.routingKey=dlx
##備胎交換機
mayikt.order.exchange=mayikt_order_exchange
mayikt.order.queue=mayikt_order_queue
mayikt.order.routingKey=mayikt.order
3.創建producer生產者.並設置消息10秒過期,驗證消息是否加入死信隊列
@RestController
public class DeadLetterController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 訂單交換機
*/
@Value("${mayikt.order.exchange}")
private String orderExchange;
@Autowired
private OrderMapper orderMapper;
/**
* 訂單路由key
*/
@Value("${mayikt.order.routingKey}")
private String orderRoutingKey;
// //方式一
// @RequestMapping("/sendOrderMsg")
// public String sendOrderMsg() {
// rabbitTemplate.convertAndSend(orderExchange,orderRoutingKey,"訂單消息", new MessagePostProcessor(){
// @Override
// public Message postProcessMessage(Message message) throws AmqpException {
// //消息設置過期時間
// message.getMessageProperties().setExpiration("10000");
// //消息設置唯一id
// // message.getMessageProperties().setUserId("10000");
// return message;
// }
// });
// return "success";
// }
//方式二 優化
@RequestMapping("/sendOrderMsg")
public String sendOrderMsg() {
// 1.生產訂單id
String orderId = System.currentTimeMillis() + "";
String orderName = "螞蟻課堂第六期報名";
OrderEntity orderEntity = new OrderEntity(orderName, orderId);
String msg = JSONObject.toJSONString(orderEntity);
sendMsg(msg); //rabbitmq生產消息 異步處理
return orderId;
// 後期客戶端主動使用orderId調用服務器接口 查詢該訂單id是否在數據庫中存在數據 消費成功 消費失敗
}
//rabbit發送消息
@Async //異步處理
public void sendMsg(String msg) {
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg,messagePostProcessor());
// 消息投遞失敗
}
//處理待發送消息
private MessagePostProcessor messagePostProcessor(){
return new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
};
}
/**
* 主動查詢接口
* 先查詢該訂單的消息是否投遞失敗
* 在查詢數據庫
*/
@RequestMapping("/getOrder")
public Object getOrder(String orderId) {
OrderEntity orderEntity = orderMapper.getOrder(orderId);
if (orderEntity == null) {
return "消息正在異步的處理中";
}
return orderEntity;
}
}
4.創建訂單消費者和死信消費者
@Component //死信隊列
public class OrderDlxConsumer {
/**
* 監聽我們的死信隊列
*/
@RabbitListener(queues = "mayikt_order_dlx_queue")
public void orderConsumer(String msg) {
System.out.println("死信隊列獲取消息:" + msg);
}
}
@Component
public class OrderConsumer {
@Autowired
private OrderMapper orderMapper;
/**
* 監聽我們的訂單隊列
*/
@RabbitListener(queues = "mayikt_order_queue")
public void orderConsumer(String msg) {
System.out.println("訂單隊列獲取消息:" + msg);
OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
if (orderEntity == null) {
return;
}
orderMapper.addOrder(orderEntity);
}
}
5.註釋掉訂單消費者,生產消息,發現10秒後,消息轉移到死信隊列並由死信隊列消費調 ,放開訂單消費者, 消息則由訂單隊列消費.
6.方式二優化: 生產者異步發送消息到隊列,並返回訂單id. 客戶端根據返回訂單id查詢訂單信息 , 查到表示消費成功 ,查不到表示正在處理或者消費失敗