怎樣保證消息發佈的可靠性
rabbitMQ爲我們提供了在生產者端來保證消息的可靠性的一系列方式
包括以下幾種:
隊列持久化
在聲明隊列的時候把druable參數設置爲true 就可以保證隊列的持久化
隊列高可用
使用鏡像隊列保證隊列的高可用
消息持久化
在發佈消息的時候設置deliveryMode 爲2
事物方式
事務的實現主要是對信道(Channel)的設置,主要的方法有三個:
-
channel.txSelect()聲明啓動事務模式;
-
channel.txComment()提交事務;
-
channel.txRollback()回滾事務;
在發送消息之前,需要聲明 channel 爲事務模式,提交或者回滾事務即可。
開啓事務後,客戶端和 RabbitMQ 之間的通訊交互流程: -
客戶端發送給服務器 Tx.Select(開啓事務模式) 服務器端返回 Tx.Select-Ok(開啓事務模式 ok) 推送消息
-
客戶端發送給事務提交 Tx.Commit 服務器端返回 Tx.Commit-Ok
以上就完成了事務的交互流程,如果其中任意一個環節出現問題,就會拋出 IoException,這樣用戶就可以攔截異常進行事務回滾,或決定要不要重
復消息。
基於事物的方式是非常可靠的,但是同時他也帶來了非常大的性能問題,所以一般我們不採用。
發送方確認
由於事物方式存在比較大的性能問題,所以RabbitMQ爲我們提供了另外一種更好地方案,也就是發送方確認模式,該模式對性能的影響幾乎可以忽略不計。
使用發佈者確認的時候,生產者把信道設置爲comfirm狀態。生產者和RabbitMQ之間通過一個唯一的ID進行消息的確認,
對於不可路由的消息,如果發現消息不可路由之後,會判斷消有沒有設置mandatory,如果設置了,輝縣回調ReturnListener裏面的方法,然後返回ack給confirmListener
對於可以路由的消息,如果消息設置了持久化或者有高可用鏡像隊列,那麼mq彙總消息持久化並且同步到所有的鏡像之後進行確認。
發送方確認有三種方式
1、channel.waitForConfirms()普通發送方確認模式;消息到達交換器,就會返回 true。
2、channel.waitForConfirmsOrDie()批量確認模式;使用同步方式等所有的消息發送之後纔會執行後面代碼,只要有一個消息未到達交換器就會
拋出 IOException 異常。
3、channel.addConfirmListener()異步監聽發送方確認模式;
異步確認的代碼示例:
/**
* @Title: 生產者確認(只有當消息持久化之後(包括所有鏡像鏡像)纔會被確認)
* @MethodName:
* @param
* @Return
* @Exception
* @Description: 默認都對隊列和消息做持久化
* @author: jenkin
* @date: 2020-04-11 10:04
*/
@Test
public void productConfirm(){
Connection connection = Common.getConnection();
try {
//創建一個信道,用於消息通信
Channel channel = connection.createChannel();
//聲明一個持久化的direct交換器
channel.exchangeDeclare( DIRECT_EXCHANGE,"direct",true);
//聲明一個隊列,這裏持久化參數爲true,如果不持久化,那麼一旦mq出故障重啓就會丟失隊列以及隊列裏面的數據
//這裏exclusive爲false,標識這個隊列可以被多個消費者消費
//autoDelete爲false,如果爲true的話當隊列裏面沒有消費者之後就會自動刪除,bug前提是要被消費過之後纔會去判斷
//arguments這是一個map,可以聲明隊列的過期時間,最大空間,長度,以及超時時間
channel.queueDeclare(DIRECT_QUEUE_1,true,false,false,null);
//綁定隊列和交換器的關係
//這裏把DIRECT_QUEUE_1隊列綁定到DIRECT_EXCHANGE 交換器上面,並且使用DIRECT_QUEUE_1作爲路由鍵發送消息
channel.queueBind(DIRECT_QUEUE_1,DIRECT_EXCHANGE,DIRECT_QUEUE_1);
//發送方確認回調
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("handleAck====》"+deliveryTag+" multiple "+multiple);
}
//只有mq內部發生錯誤的時候纔有執行nack
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("handleNack====》"+deliveryTag);
}
});
//添加路由失敗回調 發送方確認一般要結合失敗者通知使用
//如果設置了 mandatory爲true,那麼路由失敗的時候會把會回調這個方法
channel.addReturnListener((replyCode, replyText, exchange, routingKey, properties, body) -> {
System.out.println("消息:"+new String(body, StandardCharsets.UTF_8)+" 路由失敗: exchange: "+exchange+" 報文:"+replyText+" 路由鍵: "+routingKey);
});
//開啓確認
channel.confirmSelect();
//使用信道發送消息
//mandatory 這個標識如果爲true,那麼如果消息無法被路由的時候就會返調用basic.Return 把消息返回給生產者,
//可以添加一個ReturnListener ,在消息被返回之後會在這裏進行回調
// 反之直接丟棄
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
//消息持久化
AMQP.BasicProperties persistentTextPlain = MessageProperties.PERSISTENT_TEXT_PLAIN;
channel.basicPublish(DIRECT_EXCHANGE,DIRECT_QUEUE_1,true, persistentTextPlain,("測試消息:"+i).getBytes());
}
Thread.sleep(60000);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
備用交換器
備用交換器是在第一次聲明交換器時被指定,用來提供一種預先存在的交換器,如果主交換器無法路由消息,那麼消息將被路由到這個新的備用交換器。
使用方式
//聲明備用隊列
channel.queueDeclare(FANOUT_BACK_QUEUE,true,false,false,null);
// 聲明備用交換器,當主交換器無法路由,就會使用這個交換器
channel.exchangeDeclare( FANOUT_BACK_EXCHANGE,"fanout",true,false,null);
//隊列綁定,匹配所有的路右鍵
channel.queueBind(FANOUT_BACK_QUEUE,FANOUT_BACK_EXCHANGE,"#");
//聲明一個持久化的direct交換器,把備用交換器綁定到主交換器裏面
Map<String,Object> argsMap = new HashMap<String,Object>();
argsMap.put("alternate-exchange",FANOUT_BACK_EXCHANGE);
//在主交換器上面設置備用交換器
channel.exchangeDeclare( DIRECT_EXCHANGE,"direct",true,false,argsMap);
當然,在聲明瞭備用交換器之後也需要新增一個消費者去消費備用交換器隊列上面的消息