(一)TTL
1.TTL概念
?TTL又叫過期時間
RabbitMQ可以對隊列和消息設置TTL,當消息到達過期時間還沒有被消費時就會自動刪除
注:這里我們說的對隊列設置TTL,是對隊列上的消息設置TTL并不是對隊列本身,不是說隊列過期時間到了,隊列被刪除,而是消息到達此隊列后會給他設定一個過期時間,這個時間到了,消息會刪除,不是隊列刪除(如果同時此消息本身帶有TTL過期時間,按短的來)
2.設置消息的TTL
? 那我們說可以對隊列和消息設置TTL
? 那我們現在來先寫對每條消息設置TTL(就是針對每一條消息設置消息的expiration參數,單位是毫秒)
那我們來看生產者代碼(這里配置文件不需要去更改)
@RequestMapping("ttl")public String TTLPro(){String s1="ttl test";Message message=new Message(s1.getBytes(StandardCharsets.UTF_8));message.getMessageProperties().setExpiration("10000");RabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE,"ttl",message);return "發送成功";}
? 我們來看現象,我這里設置過期時間為10s,按理說到達隊列后,如果10s鐘還沒有被消費掉,就會自動過期?
10s后?
?如果我們不設置TTL就表示消息不會過期,如果設置為0的化,就表示除非此時可以直接將消息給消費者,否者就會被丟棄
?3.設置隊列的TTL
? 設置隊列的TTL是比較簡單的,但是注意,我們隊列如果存在的話,我們是不可以直接改代碼,然后更改隊列的配置信息的,同時交換機也是這樣,如果我們想改,可以再聲明個隊列,或者把隊列先刪了再創建(此時隊列上的消息會丟失)
設置隊列過期時間,只需要在隊列上配置ttl屬性就可以,我這里設置了5s
此時我們隊列的特性就又多了個TTL?
?然后我們繼續向接口發送消息
5s后就變成了(真的是5s 騙人是g)
4.兩者區別?
?設置隊列TTL屬性的方法,一旦消息過期,就會立即從隊列中刪除
?設置消息TTL的方法,一旦消息過期(且不是隊列中第一個消息),消息并不會立即刪除,而是在要發送給消費者之前進行判定,如果過期了再刪
?那我們就有疑問了,這是為什么呢?? ?本質上,是為了提高性能,因為設置隊列的過期時間,他們消息的最長存在時間就是隊列的過期時間,所有消息的存在時間都小于等于隊列過期時間,所以此時隊列中已過期的元素大部分都在隊列頭部,RabbitMQ只需要定期從隊頭開始掃描是否有過期消息即可
? 而設置消息TTL,每條消息的過期時間都不同,如果想要刪除所有過期時間,就需要掃描整個隊列,很影響性能,所以不如等到用到了此消息,再判定是否過期,如果過期了再刪除
(二)死信
1.死信概念
?死信就是因為一些原因(包括消息過期,消息被拒絕接收,隊列達到最大長度)無法被消費的消息。
?那既然有這些無法被處理的信息,那一定就有存儲他們的隊列,有隊列就要有交換機,那么這個隊列就叫做死信隊列(DLQ),這個交換機就叫死信交換機(DLX)
?本質上與正常的交換機和隊列沒什么區別
消息變成死信后,會被發送到死信交換機,然后由死信交換機綁定到死信隊列中
2.代碼演示
首先我們要聲明一個死信隊列和死信交換機進行綁定,哪至于正常的隊列,我們就用剛剛的TTL為5的隊列吧
@Bean("ttlExchange")public Exchange ttlExchange(){return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).durable(true).build();}@Bean("ttlQueue")public Queue ttlQueue(){return QueueBuilder.durable(Constants.TTL_QUEUE).ttl(5000).deadLetterExchange(Constants.DEAD_EXCHANGE).deadLetterRoutingKey("dead").build();}@Bean("ttlBind")public Binding ttlBind(@Qualifier("ttlExchange") Exchange ackExchange,@Qualifier("ttlQueue") Queue queue){return BindingBuilder.bind(queue).to(ackExchange).with("ttl").noargs();}@Bean("deadExchange")public Exchange deadExchange(){return ExchangeBuilder.directExchange(Constants.DEAD_EXCHANGE).durable(true).build();}@Bean("deadQueue")public Queue deadQueue(){return QueueBuilder.durable(Constants.DEAD_QUEUE).build();}@Bean("deadBind")public Binding deadBind(@Qualifier("deadExchange") Exchange ackExchange,@Qualifier("deadQueue") Queue queue){return BindingBuilder.bind(queue).to(ackExchange).with("dead").noargs();}
然后我們發送消息等待5s看一下
?
然后我們看看剛才綁定死信交換機的那個隊列特征?
我們發現又多了兩個特征
那驗證完TTL過后,我們來看消息被拒絕的情況
首先我們要把消息確認模式改成手動確認,然后拒絕接收消息
@RabbitListener(queues = Constants.TTL_QUEUE)public void ListenerQueue2(Message message,Channel channel) throws IOException {long Tag=message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息: "+ new String(message.getBody())+" TagID: "+Tag);int num=3/0; //模擬失敗channel.basicAck(Tag,false);System.out.println("處理完成");}catch (Exception e){channel.basicReject(Tag,false);}}
然后我們調用接口,看死信隊列,我們發現確實多了一條死信消息?
?
那第三種產生死信的消息是,隊列滿了,那我們就需要更改一下我們隊列?
@Bean("ttlQueue")public Queue ttlQueue(){return QueueBuilder.durable(Constants.TTL_QUEUE).ttl(5000).deadLetterExchange(Constants.DEAD_EXCHANGE).deadLetterRoutingKey("dead").maxLength(5l).build();}
?
此時我們注意我們要的是long類型,如果傳錯了會給我們報錯的
那我們再來看這個隊列
?我們發現又多了一個特征
我們到此已經給隊列設置5個特征了,我們來分別看一下
1)D:設置隊列為持久化的
2)TTL:設置隊列的過期時間
3)Lim:設置隊列的最大長度
4)DLX:設置了死信交換機
5)DLK:設置了死信RoutingKey
3.死信面試題
死信概念,死信來源,死信場景
前兩個我們都說過了,這里主要說一下死信的應用場景
比如我們用戶支付訂單,支付系統會給我們訂單系統返回當前訂單的支付狀態
?為了保障支付信息不丟失,需要使用死信隊列機制,當消息消費異常時,會放到死信隊列中(有可能存在用戶支付,但是消息沒有被消費或者異常拒絕的情況),此時我們放到死信隊列中,再對這個數據進行處理(可能是人工確認)
還有一些應用場景包括:
消息丟棄,直接丟棄這些無法處理的消息,避免他們占用系統資源
日志收集:將死信消息作為日志收集,用于后續分析和定位