一、可靠性問題分析
在前面說過訊息的傳遞過程中有三個物件參與分別是:生產者、RabbitMQ(broker)、消費者;接下來就是要圍繞這三個物件來分析訊息在傳遞過程中會在哪些環節出來可靠性問題;
1.1、生產者丟失訊息
生產者傳送訊息到broker時,要保證訊息的可靠性,主要的方案有以下2種:
1.事務
2.confirm機制
1.1.1、事務
RabbitMQ提供了事務功能,也即在生產者傳送資料之前開啟RabbitMQ事務,然後再傳送訊息,如果訊息沒有成功傳送到RabbitMQ,那麼就丟擲異常,然後進行事務回滾,回滾之後再重新傳送訊息,如果RabbitMQ接收到了訊息,那麼進行事務提交,再開始傳送下一條資料。
優點
保證訊息一定能夠傳送到RabbitMQ中,傳送端不會出現訊息丟失的情況;
缺點
事務機制是阻塞(同步)的,每次傳送訊息必須要等到mq回應之後才能繼續傳送訊息,比較耗費效能,會導致吞吐量降下來
1.1.2、confirm模式
基於事務的特性,作為補償,RabbitMQ新增了訊息確認機制,也即confirm機制。confirm機制和事務機制最大的不同就是事務是同步的,confirm是非同步的,傳送完一個訊息後可以繼續傳送下一個訊息,mq接收到訊息後會非同步回撥介面告知訊息接收結果。生產者開啟confirm模式後,每次傳送的訊息都會分配一個唯一id,如果訊息成功傳送到了mq中,那麼就會返回一個ack訊息,表示訊息接收成功,反之會返回一個nack,告訴你訊息接收失敗,可以進行重試。依據這個機制,我們可以維護每個訊息id的狀態,如果超過一定時間還是沒有接收到mq的回撥,那麼就重發訊息。
1.1.3、confirm模式程式碼演示
其實這塊程式碼在前面幾篇文章的程式碼中有體現過;下面以springboot整合的方式再演示一種
pom.xml檔案
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--web包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.yml
spring: rabbitmq: #rabbitmq 連線配置 publisher-confirm-type: correlated # 開啟confirm確認模式 host: 192.168.0.1 port: 5672 username: admin password: admin server: port: 8081
@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); } } }
/** * 定義佇列和交換機 */ @Configuration public class QueueConfig { @Bean(name="confirmTestExchange") public FanoutExchange confirmTestExchange(){ return new FanoutExchange("confirmTestExchange",true,false); } @Bean(name = "confirmTestQueue") public Queue confirmTestQueue(){ return new Queue("confirm_test_queue",true,false,false); } @Bean public Binding confirmTestFanoutExcangeAndQueue(@Qualifier("confirmTestQueue")Queue queue,@Qualifier("confirmTestExchange") FanoutExchange fanoutExchange){ return BindingBuilder.bind(queue).to(fanoutExchange); } }
@RestController @RequestMapping(value = "/producer") @CrossOrigin public class Producer { @Autowired private RabbitTemplate rabbitTemplate; @Autowired private ConfirmCallbackService confirmCallbackService; @Autowired private ReturnCallbackService returnCallbackService; @GetMapping public void producer(){ rabbitTemplate.setConfirmCallback ( confirmCallbackService ); rabbitTemplate.convertAndSend ( "confirmTestExchange","","測試RabbitTemplate功能" ); } }
上面演示了訊息投放到交換機的案例,下面演示一個訊息從 exchange–>queue 投遞失敗則會返回一個 returnCallback的案例;生產端通過實現ReturnCallback介面,啟動訊息失敗返回,訊息路由不到佇列時會觸發該回撥介面
spring: rabbitmq: #rabbitmq 連線配置 publisher-confirm-type: correlated # 開啟confirm確認模式 publisher-returns: true #開啟退回模式 host: 192.168.0.1 port: 5672 username: admin password: admin server: port: 8081
rabbitTemplate.setMandatory(true);
@Component public class ReturnCallbackService implements RabbitTemplate.ReturnCallback { /** * * @param message 訊息物件 * @param i 錯誤碼 * @param s 錯誤資訊 * @param s1 交換機 * @param s2 路由鍵 */ @Override public void returnedMessage(Message message ,int i ,String s ,String s1 ,String s2) { System.out.println("訊息物件:" + message); System.out.println("錯誤碼:" + i); System.out.println("錯誤資訊:" + s); System.out.println("訊息使用的交換器:" + s1); System.out.println("訊息使用的路由key:" + s2); //業務程式碼處理 } }
yml配置
spring: rabbitmq: #rabbitmq 連線配置 publisher-confirm-type: correlated # 開啟confirm確認模式 publisher-returns: true #開啟退回模式 host: 124.71.33.75 port: 5672 username: admin password: ghy20200707rabbitmq server: port: 8081
public void producerLose(){ /** *確保訊息傳送失敗後可以重新返回到佇列中 */ rabbitTemplate.setMandatory(true); /** * 訊息投遞確認模式 */ rabbitTemplate.setConfirmCallback(confirmCallbackService); /** * 訊息投遞到佇列失敗回撥處理 */ rabbitTemplate.setReturnCallback(returnCallbackService); CorrelationData correlationData = new CorrelationData("id_"+System.currentTimeMillis()+""); //傳送訊息 rabbitTemplate.convertAndSend("directExchange", "RabbitTemplate","測試RabbitTemplate功能" ,correlationData); }
測試介面
1.2、消費者丟失訊息
其實在生產者和消費者中間,rabbitmq也是會丟失訊息的,解決方案就是持久化儲存,這個方案在前面有講過;所以在這裡就跳過;下面直接說訊息確認機制ack,ack指Acknowledge確認。 表示消費端收到訊息後的確認方式
- AcknowledgeMode.NONE:不確認
- AcknowledgeMode.AUTO:自動確認
- AcknowledgeMode.MANUAL:手動確認
spring: rabbitmq: #rabbitmq 連線配置 publisher-confirm-type: correlated # 開啟confirm確認模式 publisher-returns: true #開啟退回模式 host: 124.71.33.75 port: 5672 username: admin password: ghy20200707rabbitmq listener: simple: acknowledge-mode: manual #手動確認 server: port: 8081
/** * 消費者訊息確認機制 */ @Component @RabbitListener(queues = "confirm_test_queue") public class ReceiverMessage { @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())); //TODO 具體業務邏輯 // 手動簽收[引數1:訊息投遞序號,引數2:批量簽收] channel.basicAck(deliveryTag, true); } catch (Exception e) { //拒絕簽收[引數1:訊息投遞序號,引數2:批量拒絕,引數3:是否重新加入佇列] channel.basicNack(deliveryTag, true, true); } } }
要想測試異常很簡單,在程式碼加一個報錯語句就可以測試了,我這裡就不搞事了;
二、消費端限流
2.1、TTL
三、死信佇列
- 佇列訊息長度到達限制;
- 消費者拒接消費訊息,basicNack/basicReject,並且不把訊息重新放入原目標佇列,requeue=false;
- 原佇列存在訊息過期設定,訊息到達超時時間未被消費;