寫在最前面
距離上一次發文章已經很久了,其實這段時間一直也沒有停筆,只不過在忙著找工作還有學校結課的事情,重新弄了一下部落格,後面也會陸陸續續會把文章最近更新出來~
- 這篇文章有點長,就分了兩篇
- PS:那個Github上Java知識問答的文章也沒有停筆,最近也會陸續更新
文章目錄:
6. 進階補充
6.1 過期時間設定(TTL)
過期時間(TTL)就是對訊息或者佇列設定一個時效,只有在時間範圍內才可以被被消費者接收穫取,超過過期時間後訊息將自動被刪除。
注:我們主要講訊息過期,在訊息過期的第一種方式中,順便也就會提到佇列過期的設定方式
- 通過佇列屬性設定,佇列中所有訊息都有相同的過期時間
- 對訊息進行單獨設定,每條訊息 TTL可以不同
兩種方法同時被使用時,以兩者過期時間 TTL 較小的那個數值為準。訊息在佇列的生存時間一旦超過設定的 TTL 值,就稱為 Dead Message 被投遞到死信佇列,消費者將無法再收到該訊息(死信佇列是我們下一點要講的)
6.1.1 應用於全部訊息的過期時間
- 配置類
@Configuration
public class RabbitMqConfiguration {
public static final String TOPIC_EXCHANGE = "topic_order_exchange";
public static final String TOPIC_QUEUE_NAME_1 = "test_topic_queue_1";
public static final String TOPIC_ROUTINGKEY_1 = "test.*";
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(TOPIC_EXCHANGE);
}
@Bean
public Queue topicQueue1() {
// 建立引數 Map 容器
Map<String, Object> args = new HashMap<>();
// 設定訊息過期時間 注意此處是數值 5000 不是字串
args.put("x-message-ttl", 5000);
// 設定佇列過期時間
args.put("x-expires", 8000);
// 在最後傳入額外引數 即這些過期資訊
return new Queue(TOPIC_QUEUE_NAME_1, true, false, false, args);
}
@Bean
public Binding bindingTopic1() {
return BindingBuilder.bind(topicQueue1())
.to(topicExchange())
.with(TOPIC_ROUTINGKEY_1);
}
}
- 建立引數 Map 容器:型別是在 Queue 引數中所要求的,要按照要求來。
- 設定訊息過期時間:這裡設定的訊息過期時間,會應用到所有訊息中。
- 設定佇列過期時間
- 傳入額外引數:將上述配置好的過期時間設定,通過 Queue 傳入即可。
- 生產者
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class RabbitMqTest {
/**
* 注入 RabbitTemplate
*/
@Autowired
@Test
public void testTopicSendMessage() {
rabbitTemplate.convertAndSend(RabbitMqConfiguration.TOPIC_EXCHANGE, "test.order.insert", "This is a message !");
}
}
不要配置消費者,然後就可以在 Web 管理器中看到效果了
6.1.2 應用於單獨訊息的過期時間
- 配置中保持最初的樣子就行了,就不需要配置過期時間了
- 生產者中配置訊息單獨的過期時間
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class RabbitMqTest {
/**
* 注入 RabbitTemplate
*/
@Autowired
@Test
public void testTopicSendMessage2() {
MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
public Message postProcessMessage(Message message){
// 注意此處是 字串 “5000”
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend(RabbitMqConfiguration.TOPIC_EXCHANGE, "test.order",
"This is a message 002 !",messagePostProcessor);
}
}
6.2 死信佇列
死信官方原文為 Dead letter ,它是RabbitMQ中的一種訊息機制,當你在消費訊息時,如果佇列以及佇列裡的訊息出現以下情況,說明當前訊息就成為了 “死信”,如果配置了死信佇列,這些資料就會傳送到其中,如果沒有配置就會直接丟棄。
- 訊息被拒絕
- 訊息過期
- 佇列達到最大長度
不過死信佇列並不是什麼很特殊的存在,我們只需要配置一個交換機,在消費的那個佇列中配置,出現死信就重新傳送到剛才配置的交換機中去,進而被路由到與交換機繫結的佇列中去,這個佇列也就是死信佇列,所以從建立上看,它和普通的佇列沒什麼區別。
6.2.1 應用場景
比如在一些比較重要的業務佇列中,未被正確消費的訊息,往往我們並不想丟棄,因為丟棄後如果想恢復這些資料,往往需要運維人員從日誌獲取到原訊息,然後重新投遞訊息,而配置了死信佇列,相當於給了未正確消費訊息一個暫存的位置,日後需要恢復的時候,只需要編寫對應的程式碼就可以了。
6.2.2 實現方式
- 定義一個處理死信的交換機和佇列
@Configuration
public class DeadRabbitMqConfiguration{
@Bean
public DirectExchange deadDirect(){
return new DirectExchange("dead_direct_exchange");}
@Bean
public Queue deadQueue(){
return new Queue("dead_direct_queue");}
@Bean
public Binding deadBinds(){
return BindingBuilder.bind(deadQueue()).to(deadDirect()).with("dead");
}
}
- 在正常的消費佇列中指定死信佇列
@Configuration
public class RabbitMqConfiguration {
public static final String TOPIC_EXCHANGE = "topic_order_exchange";
public static final String TOPIC_QUEUE_NAME_1 = "test_topic_queue_1";
public static final String TOPIC_ROUTINGKEY_1 = "test.*";
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(TOPIC_EXCHANGE);
}
@Bean
public Queue topicQueue1() {
// 設定過期時間
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000);
// 設定死信佇列交換器
args.put("x-dead-letter-exchange","dead_direct_exchange");
// 設定交換路由的路由key fanout 模式不需要配置此條
args.put("x-dead-letter-routing-key","dead");
return new Queue(TOPIC_QUEUE_NAME_1, true, false, false, args);
}
@Bean
public Binding bindingTopic1() {
return BindingBuilder.bind(topicQueue1())
.to(topicExchange())
.with(TOPIC_ROUTINGKEY_1);
}
}
6.3 記憶體及磁碟監控
6.3.1 記憶體告警及控制
為了防止避免伺服器因記憶體不夠而崩潰,所以 RabbitMQ 設定了一個閾值,當記憶體使用量超過閾值的時候,RabbitMQ 會暫時阻塞所有客戶端的連線,並且停止繼續接受新訊息。
有兩種方式可以修改這個閾值
- 通過命令(二選一即可)
- 命令的方式會在 Broker 重啟後失效
# 通過百分比設定的命令 <fraction> 處代表百分比小數例如 0.6
rabbitmqctl set_vm_memory_high_watermark <fraction>
# 通過絕對值設定的命令 <value> 處代表設定的一個固定值例如 700MB
rabbitmqctl set_vm_memory_high_watermark absolute <value>
- 通過修改配置檔案 rabbitmq.conf
- 配置檔案每次啟動都會載入,屬於永久有效
# 百分比設定 預設值為 0.4 推薦 0.4-0.7 之間
vm_memory_high_watermark.relative = 0.5
# 固定值設定
vm_memory_high_watermark.absolute = 2GB
6.3.2 記憶體換頁
在客戶端連線和生產者被阻塞之前,它會嘗試將佇列中的訊息換頁到磁碟中,這種思想在作業系統中其實非常常見,以最大程度的滿足訊息的正常處理。
當記憶體換頁發生後,無論持久化還是非持久化的訊息,都會被轉移到磁碟,而由於持久化的訊息本來就在磁碟中有一個持久化的副本,所以會優先移除持久化的訊息。
預設情況下,當記憶體達到閾值的 50 % 的時候,就會進行換頁處理。
可以通過設定 vm_memory_high_watermark_paging_ratio 修改
# 值小於 1, 如果大於 1 就沒有意義了
vm_memory_high_watermark_paging_ratio = 0.6
6.3.3 磁碟預警
如果無止境的換頁,也很有可能會導致耗盡磁碟空間導致伺服器崩潰,所以 RabbitMQ 又提供了一個磁碟預警的閾值,當低於這個值的時候就會進行報警,預設是 50MB,可以通過命令的方式修改
# 固定值
rabbitmqctl set_disk_free_limit <disk_limit>
# 百分數
rabbitmqctl set_disk_free_limit memory_limit <fraction>
6.4 訊息的可靠傳遞
生產者向 RabbitMQ 中傳送訊息的時候,可能會因為網路等種種原因導致傳送失敗,所以 RabbitMQ 提供了一系列保證訊息可靠傳遞的機制,可以大致分為生產者和消費者兩部分的處理
6.4.1 生產者中的機制
生產者作為訊息的傳送者,需要保證自己的訊息傳送成功,RabbitMQ 提供了兩種方式來保證這一點。-
- confirm 確認模式
- return 退回模式
6.4.1.1 confirm 確認模式
生產者傳送訊息後,會非同步等待接收一個 ack 應答,收到返回的 ack 確認訊息後,根據 ack是 true 還是 false,呼叫 confirmCallback 介面進行處理
- 配置類
spring:
rabbitmq:
# 傳送確認
publisher-confirm-type: correlated
- 實現 ConfirmCallback 介面的 confirm 方法
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {
/**
* @param correlationData 相關配置資訊
* @param ack exchange交換機 是否成功收到了訊息。true 成功,false代表失敗
* @param cause 失敗原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
//接收成功
System.out.println("訊息成功傳送到交換機");
} else {
//接收失敗
System.out.println("訊息傳送到交換機失敗,失敗原因: " + cause);
// TODO 可以處理失敗的訊息,例如再次傳送等等
}
}
}
- 宣告佇列和交換機
@Configuration
public class RabbitMqConfig {
@Bean()
public Queue confirmTestQueue() {
return new Queue("confirm_test_queue", true, false, false);
}
@Bean()
public FanoutExchange confirmTestExchange() {
return new FanoutExchange("confirm_test_exchange");
}
@Bean
public Binding confirmTestFanoutExchangeAndQueue() {
return BindingBuilder.bind(confirmTestQueue()).to(confirmTestExchange());
}
}
- 生產者
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class RabbitMqTest {
/**
* 注入 RabbitTemplate
*/
@Autowired
/**
* 注入 ConfirmCallbackService
*/
@Autowired
private ConfirmCallbackService confirmCallbackService;
@Test
public void testConfirm() {
// 設定確認回撥類
rabbitTemplate.setConfirmCallback(confirmCallbackService);
// 傳送訊息
rabbitTemplate.convertAndSend("confirm_test_exchange", "", "ConfirmCallback !");
}
}
6.4.1.2 return 退回模式
當 Exchange 傳送到 Queue 失敗時,會呼叫一個 returnsCallback,我們可以通過實現這個介面,然後來處理這種失敗的情況。
- 在配置檔案中開啟傳送回撥
spring:
rabbitmq:
# 傳送回撥
publisher-returns: true
- 實現 ReturnsCallback 的 returnedMessage 方法
// public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) 已經屬於過時方法了
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnsCallback {
@Override
public void returnedMessage(ReturnedMessage returned) {
System.out.println(returned);
}
}
- 宣告佇列和交換機(Direct 模式)
@Configuration
public class RabbitMqConfig {
@Bean()
public Queue returnsTestQueue() {
return new Queue("return_test_queue", true, false, false);
}
@Bean()
public DirectExchange returnsTestExchange() {
return new DirectExchange("returns_test_exchange");
}
@Bean
public Binding returnsTestDirectExchangeAndQueue() {
return BindingBuilder.bind(returnsTestQueue()).to(returnsTestExchange()).with("info");
}
}
- 生產者
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class RabbitMqTest {
/**
* 注入 RabbitTemplate
*/
@Autowired
/**
* 注入 ConfirmCallbackService
*/
@Autowired
private ConfirmCallbackService confirmCallbackService;
/**
* 注入 ReturnCallbackService
*/
@Autowired
private ReturnCallbackService returnCallbackService;
@Test
public void testReturn() {
// 確保訊息傳送失敗後可以重新返回到佇列中
rabbitTemplate.setMandatory(true);
// 訊息投遞到佇列失敗回撥處理
rabbitTemplate.setReturnsCallback(returnCallbackService);
// 訊息投遞確認模式
rabbitTemplate.setConfirmCallback(confirmCallbackService);
// 傳送訊息
rabbitTemplate.convertAndSend("returns_test_exchange", "info", "ReturnsCallback !");
}
}
- 修改不同的路由key,即可測試出結果。
6.4.2 消費者中的機制
6.4.2.1 ack 確認機制
ack 表示收到訊息的確認,預設是自動確認,但是它有三種型別
acknowledge-mode 選項介紹
- auto:自動確認,為預設選項
- manual:手動確認(按能力分配就需要設定為手動確認)
- none:不確認,傳送後自動丟棄
其中自動確認是指,當訊息一旦被消費者接收到,則自動確認收到,並把這個訊息從佇列中刪除。
但是在實際業務處理中,正確的接收到的訊息可能會因為業務上的問題,導致訊息沒有正確的被處理,但是如果設定了 手動確認方式,則需要在業務處理成功後,呼叫channel.basicAck(),手動簽收,如果出現異常,則呼叫 channel.basicNack()方法,讓其自動重新傳送訊息。
- 配置檔案
spring:
rabbitmq:
listener:
simple:
# 手動確認
acknowledge-mode: manual
- 消費者
@Component
@RabbitListener(queues = "confirm_test_queue")
public class TestConsumer {
@RabbitHandler
public void processHandler(String msg, Channel channel, Message message) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println("訊息內容: " + new String(message.getBody()));
System.out.println("業務出錯的位置:");
int i = 66 / 0;
// 手動簽收 deliveryTag標識代表佇列可以刪除了
channel.basicAck(deliveryTag, true);
} catch (Exception e) {
// 拒絕簽收
channel.basicNack(deliveryTag, true, true);
}
}
}
6.5 叢集 & 6.6 分散式事務(待更新)
由於這兩個點篇幅也不短,實在不願草草簡單寫上了事,放到後面單獨的文章編寫,釋出哇。
關於叢集的搭建暫時可參考:https://blog.csdn.net/belonghuang157405/article/details/83540148