RabbitMQ實戰 三 高階特性

2022-07-04 10:12:11 字數 4401 閱讀 9188

在實際生產中,很難保障前三點的完全可靠,比如在極端的環境中,生產者傳送訊息失敗了,傳送端在接受確認應答時突然發生網路閃斷等等情況,很難保障可靠性投遞,所以就需要有第四點完善的訊息補償機制。

2.1.2.1 方案一:訊息資訊落庫,對訊息狀態進行打標(常見方案)

將訊息持久化到db並設定狀態值,收到consumer的應答就改變當前記錄的狀態.

再輪詢重新傳送沒接收到應答的訊息,注意這裡要設定重試次數.

方案流程圖

方案實現流程

比如我下單成功

step1 - 對訂單資料入biz db訂單庫,並對因此生成的業務訊息入msg db訊息庫

此處由於採用了兩個資料庫,需要兩次持久化操作,為了保證資料的一致性,有人可能就想著採用分布式事務,但在大廠實踐中,基本都是採用補償機制!

這裡一定要保證step1 中訊息都儲存成功了,沒有出現任何異常情況,然後生產端再進行訊息傳送。如果失敗了就進行快速失敗機制

對業務資料和訊息入庫完畢就進入

setp2 - 傳送訊息到 mq 服務上,如果一切正常無誤消費者監聽到該訊息,進入

step3 - 生產端有乙個confirm listener,非同步監聽broker回送的響應,從而判斷訊息是否投遞成功

step6 - 把抓取出來的訊息進行重新投遞(retry send),也就是從第二步開始繼續往下走

step7 - 當然有些訊息可能就是由於一些實際的問題無法路由到broker,比如routingkey設定不對,對應的佇列被誤刪除了,那麼這種訊息即使重試多次也仍然無法投遞成功,所以需要對重試次數做限制,比如限制3次,如果投遞次數大於三次,那麼就將訊息狀態更新為2,表示這個訊息最終投遞失敗,然後通過補償機制,人工去處理。實際生產中,這種情況還是比較少的,但是你不能沒有這個補償機制,要不然就做不到可靠性了。

思考:該方案在高併發的場景下是否合適

對於第一種方案,我們需要做兩次資料庫的持久化操作,在高併發場景下顯然資料庫存在著效能瓶頸.

其實在我們的核心鏈路中只需要對業務進行入庫就可以了,訊息就沒必要先入庫了,我們可以做訊息的延遲投遞,做二次確認,**檢查。下面然我們看方案二

2.1.2.2 訊息延遲投遞,兩次確認,**檢查(大規模海量資料方案)

大廠經典實現方案

當然這種方案不一定能保障百分百投遞成功,但是基本上可以保障大概99.9%的訊息是ok的,有些特別極端的情況只能是人工去做補償了,或者使用定時任務.

主要就是為了減少db操作

方案流程圖

方案實現流程

設計目的

少做一次db的儲存,在高併發場景下,最關心的不是訊息百分百投遞成功,而是一定要保證效能,保證能抗得住這麼大的併發量。所以能節省資料庫的操作就盡量節省,非同步地進行補償.

其實在主流程裡面是沒有callback service的,它屬於乙個補償的服務,整個核心鏈路就是生產端入庫業務訊息,傳送訊息到mq,消費端監聽佇列,消費訊息。其他的步驟都是乙個補償機制。

小結這兩種方案都是可行的,需要根據實際業務來進行選擇,方案二也是網際網路大廠更為經典和主流的解決方案.但是若對效能要求不是那麼高,方案一要更簡單.

比如資料庫的樂觀鎖,在執行更新操作前,先去資料庫查詢version,然後執行更新語句,以version作為條件,如果執行更新時有其他人先更新了這張表的資料,那麼這個條件就不生效了,也就不會執行操作了,通過這種樂觀鎖的機制來保障冪等性.

在業務高峰期最容易產生訊息重複消費問題,當con消費完訊息時,在給pro返回ack時由於網路中斷,導致pro未收到確認資訊,該條訊息就會重新傳送並被con消費,但實際上該消費者已成功消費了該條訊息,這就造成了重複消費.

而con - 冪等性,即訊息不會被多次消費,即使我們收到了很多一樣的訊息.

3.2.2.1 唯一id+指紋碼

核心:利用資料庫主鍵去重

小結首先我們需要根據訊息生成乙個全域性唯一id,然後還需要加上乙個指紋碼。這個指紋碼它並不一定是系統去生成的,而是一些外部的規則或者內部的業務規則去拼接,它的目的就是為了保障這次操作是絕對唯一的。

將id + 指紋碼拼接好的值作為資料庫主鍵,就可以進行去重了。即在消費訊息前呢,先去資料庫查詢這條訊息的指紋碼標識是否存在,沒有就執行insert操作,如果有就代表已經被消費了,就不需要管了

3.2.2.2 利用redis原子性

這裡我們使用redis實現冪等,還需要考慮如下問題

這裡只提用redis的原子性去解決mq冪等性重複消費的問題

mq的冪等性問題 根本在於的是生產端未正常接收ack,可能是網路抖動、網路中斷導致

可能的方案

con在消費開始時將 id放入到redis的bitmap中,pro每次生產資料時,從redis的bitmap對應位置若不能取出id,則生產訊息傳送,否則不進行訊息傳送。

但是有人可能會說,萬一con,proredis命令執行失敗了怎麼辦,雖然又出現重複消費又出現redis非正常執行命令的可能性極低,但是萬一呢?

ok,我們可以在redis命令執行失敗時,將訊息落庫,每日用定時器,對這種極特殊的訊息進行處理。

pro傳送訊息到broker,broker接收到訊息後,產生回送響應

pro中有乙個confirm listener非同步監聽響應應答

在channel上開啟確認模式:channel.confirmselect()

在channel上新增監聽:addconfirmlistener

監聽成功和失敗的返回結果,根據具體的結果對訊息進行重新傳送、或記錄日誌等後續處理

接下來就讓我們根據原理進行實操吧!

在基礎的api中的乙個關鍵的配置項:mandatory

自定義con實現只需要繼承 defaultconsumer 類,重寫 handledelivery 方法即可!

因此,我們需要限流

rabbitmq提供了一種qos (服務質量保證)功能,即在非自動確認訊息的前提下,如果一定數目的訊息 (通過基於con或者channel設定qos的值) 未被確認前,不消費新的訊息

不能設定自動簽收功能(autoack = false)

如果訊息未被確認,就不會到達con,目的就是給pro減壓

限流設定api

void basicqos(uint prefetchsize, ushort prefetchcount, bool global);

prefetchsize和global這兩項,rabbitmq沒有實現,暫且不研究

prefetchcount在autoack=false的情況下生效,即在自動應答的情況下該值無效

手工ack

void basicack(integer deliverytag,boolean multiple)

呼叫這個方法就會主動回送給broker乙個應答,表示這條訊息我處理完了,你可以給我下一條了。引數multiple表示是否批量簽收,由於我們是一次處理一條訊息,所以設定為false

當我們設定autoack=false時,就可以使用手工ack方式了,其實手工方式包括了手工ack與nack

當我們手工 ack 時,會傳送給broker乙個應答,代表訊息處理成功,broker就可回送響應給pro.

nack 則表示訊息處理失敗,如果設定了重回佇列,broker端就會將沒有成功處理的訊息重新傳送.

api:void basicnack(long deliverytag, boolean multiple, boolean requeue)
api:void basicack(long deliverytag, boolean multiple)
dlx - 死信佇列(dead-letter-exchange)

利用dlx,當訊息在乙個佇列中變成死信 (dead message) 之後,它能被重新publish到另乙個exchange中,這個exchange就是dlx.

正常宣告交換機、佇列、繫結,只不過我們需要在佇列加上乙個引數即可

arguments.put(" x-dead-letter-exchange","dlx.exchange");
這樣訊息在過期、requeue、 佇列在達到最大長度時,訊息就可以直接路由到死信佇列!

本文專注rabbitmq高階特性的學習

首先介紹了大廠在實際使用中是如何保障訊息投遞成功和冪等性的,以及對rabbitmq的確認訊息、返回訊息、ack與重回佇列、訊息的限流,以及對超時時間、死信佇列的使用

rabbitmq 100% 投遞成功方案詳解

rabbitmq 從入門到精通(二)

rabbitmq 冪等性概念及業界主流解決方案

rabbitmq從入門到精通(三)

python學習(三) 高階特性

在python中,不是越多越好,而是越少越好。不是越複雜越好,而是越簡單越好。1行 能實現的功能,決不寫5行 請始終牢記,越少,開發效率越高。取乙個list或tuple的部分元素是非常常見的操作,python提供了切片 slice 操作符,對應型別為slice型別,能大大簡化這種操作。可以先宣告乙個...

Spring實戰之三 高階裝配

primary和 component組合使用,宣告該bean在自動掃瞄時為首選bean,在遇到歧義時首先使用首選bean autowired inject 和 qualifier配合使用,在注入時指定要注入進去的是哪個bean,如 qualifier icecream 表明注入id 確切地講是限定符...

Vim實戰指南 三 高階技巧

當vi入門後,在你日常使用中,會遇到下面幾個問題 我編輯好的文字,怎麼無法儲存?是不是要重新來一次?我誤操作,怎麼回退撤銷?只能退出vi重新開啟嗎?我能同時複製多個不連續的行嗎?一行一行複製貼上太慢了。下面我將要介紹的內容不僅會解決上述問題,還會介紹幾個高階小技巧。vi file實際上,你可以用vi...