RabbitMQ訊息模式
RabbitMQ訊息模式
1、訊息如何保證100%的投遞?
什麼是生產端的可靠性投遞?
保障訊息的成功發出
保障MQ節點的成功接收
傳送端收到MQ節點(Broker)確認應答
完善的訊息進行補償機制
BAT/TMD網際網路大廠的解決方案:
訊息落庫,對訊息狀態進行打標
訊息的延遲投遞,做二次確認,回撥檢查
2、冪等性概念
冪等性是什麼?
我們可以借鑑資料庫的樂觀鎖機制
比如我們執行一條更新庫存的SQL語句
Update t_repository set count = count -1,version = version + 1 where version = 1
Elasticsearch也是嚴格遵循冪等性概念,每次資料更新,version+1(博主部落格前面有提到)
消費端-冪等性保障
在海量訂單產生的業務高峰期,如何避免訊息的重複消費問題?
消費實現冪等性,就意味著,我們的訊息永遠不會消費多次,即使我們收到了多條一樣的訊息
業界主流的冪等性操作
唯一ID+指紋碼機制,利用資料庫主鍵去重
利用Redis的原子性去實現
唯一ID+碼 機制
唯一ID+指紋碼機制,利用資料庫主鍵去重
Select count(1) from T_order where ID=唯一ID+指紋碼
好處:實現簡單
壞處:高併發下有資料庫寫入的效能瓶頸
解決方案:根據ID進行分庫分表進行演算法路由
利用Redis的原子性去實現
使用Redis進行冪等,需要考慮的問題
第一:我們是否要進行資料落庫,如果落庫的話,關鍵解決的問題是資料庫和快取如何做到原子性?
第二:如果不進行落庫,那麼都儲存到快取中,如何設定定時同步策略?
3、Confirm確認訊息
理解Confirm訊息確認機制
訊息的確認,是指生產者投遞訊息後,如果Broker收到訊息,則會給我們生產者 一個應答。
生產者進行接收應答,用來確定這條訊息是否正常的傳送到Broker,這種方式也是訊息的可靠性投遞的核心保障
如何實現Confirm確認訊息?
第一步:在Channel上開啟確認模式:channel.confirmSelect()
第二步:在channel上新增監聽:addConfirmListener,監聽成功和失敗的返回結果,根據具體的結果對訊息進行重新傳送、或記錄日誌等後續處理!
消費端程式碼
package com.zhoujun.rabblitmqapi.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Consumer {
public static void main(String[] args) throws Exception {
//1 建立ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.147.146");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 獲取C onnection
Connection connection = connectionFactory.newConnection();
//3 通過Connection建立一個新的Channel
Channel channel = connection.createChannel();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.#";
String queueName = "test_confirm_queue";
//4 宣告交換機和佇列 然後進行繫結設定, 最後制定路由Key
channel.exchangeDeclare(exchangeName, "topic", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
//5 建立消費者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, queueingConsumer);
while(true){
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.err.println("消費端: " + msg);
}
}
}
服務方提供程式碼
package com.zhoujun.rabblitmqapi.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
public class Producer {
public static void main(String[] args) throws Exception {
//1 建立ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.147.146");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 獲取C onnection
Connection connection = connectionFactory.newConnection();
//3 通過Connection建立一個新的Channel
Channel channel = connection.createChannel();
//4 指定我們的訊息投遞模式: 訊息的確認模式
channel.confirmSelect();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.save";
//5 傳送一條訊息
String msg = "Hello RabbitMQ Send confirm message!";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
//6 新增一個確認監聽
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-------no ack!-----------");
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-------ack!-----------");
}
});
}
}
4、Return返回訊息
Return Listener用於處理一些不可路由的訊息!
正常情況:我們的訊息生產者,通過指定一個Exchange和RoutingKey,把訊息送達到某一個佇列中去,然後我們的消費者監聽佇列,進行消費處理操作!
異常情況:在某些情況下,如果我們在傳送訊息的時候,當前的Exchange不存在或者指定的路由key路由不到,這個時候如果我們需要監聽這種不可達的訊息,就需要使用Return Listener!
在基礎API中有一個關鍵的配置項
Mandatory:如果為true,則監聽器會接收到路由不可達的訊息,然後進行後續處理,如果為false,那麼Broker端自動刪除該訊息!
消費端程式碼
package com.zhoujun.rabblitmqapi.returnlistener;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Consumer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.147.146");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String exchangeName = "test_return_exchange";
String routingKey = "return.#";
String queueName = "test_return_queue";
channel.exchangeDeclare(exchangeName, "topic", true, false, null);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, queueingConsumer);
while(true){
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.err.println("消費者: " + msg);
}
}
}
生產端程式碼
package com.zhoujun.rabblitmqapi.returnlistener;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.147.146");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String exchange = "test_return_exchange";
String routingKey = "return.save";
String routingKeyError = "abc.save";
String msg = "Hello RabbitMQ Return Message";
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange,
String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println("---------handle return----------");
System.err.println("replyCode: " + replyCode);
System.err.println("replyText: " + replyText);
System.err.println("exchange: " + exchange);
System.err.println("routingKey: " + routingKey);
System.err.println("properties: " + properties);
System.err.println("body: " + new String(body));
}
});
//訊息投遞成功,會被消費者所消費
// channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
//訊息不可達,將觸發ReturnListener
channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
}
}
相關文章
- RabbitMQ實戰:訊息通訊模式和最佳實踐MQ模式
- RabbitMQ的 RPC 訊息模式你會了嗎?MQRPC模式
- RabbitMQ訊息佇列MQ佇列
- [訊息佇列]RabbitMQ佇列MQ
- RabbitMQ訊息佇列(五):Routing 訊息路由MQ佇列路由
- MQ訊息佇列_RabbitMQMQ佇列
- RabbitMq之訊息確認MQ
- 訊息中介軟體rabbitMQMQ
- rabbitmq訊息佇列原理MQ佇列
- 訊息佇列之RabbitMQ佇列MQ
- 訊息佇列之 RabbitMQ佇列MQ
- RabbitMQ防止訊息丟失MQ
- RabbitMQ 訊息佇列 配置MQ佇列
- RabbitMQ實戰:理解訊息通訊MQ
- Rabbitmq可靠訊息投遞,訊息確認機制MQ
- RabbitMQ 訊息確認機制MQ
- RabbitMQ 訊息的可靠投遞MQ
- 連RabbitMQ的5種核心訊息模式都不懂,也敢說自己會用訊息佇列!MQ模式佇列
- RabbitMQ訊息佇列系列教程(一)認識RabbitMQMQ佇列
- RabbitMQ訊息佇列(九):Publisher的訊息確認機制MQ佇列
- 解析訊息中介軟體之RabbitMQMQ
- RabbitMQ 訊息佇列之 Exchange TypesMQ佇列
- SpringBoot2--RabbitMQ訊息Spring BootMQ
- RabbitMQ的訊息可靠性(五)MQ
- RabbitMQ訊息佇列(二):”Hello, World“MQ佇列
- RabbitMQ .NET訊息佇列使用入門(五)【RabbitMQ例子】MQ佇列
- 訊息中介軟體RabbitMQ_RabbitMQ快速入門3MQ
- 訊息中介軟體RabbitMQ_RabbitMQ叢集搭建8MQ
- 如何處理RabbitMQ 訊息堆積和訊息丟失問題MQ
- RabbitMQ訊息佇列(六):使用主題進行訊息分發MQ佇列
- SpringBoot:初探 RabbitMQ 訊息佇列Spring BootMQ佇列
- RabbitMQ,RocketMQ,Kafka 訊息模型對比分析MQKafka模型
- RabbitMQ 訊息佇列之佇列模型MQ佇列模型
- Laravel5.6 整合 RabbitMQ 訊息佇列LaravelMQ佇列
- 《RabbitMQ》 | 訊息丟失也就這麼回事MQ
- RabbitMq如何確保訊息不丟失MQ
- 實現rabbitmq訊息重新投遞方案MQ
- Kafka、RabbitMQ、RocketMQ訊息中介軟體的對比 —— 訊息傳送效能KafkaMQ