網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

石杉的架構筆記發表於2019-01-11

歡迎關注個人公眾號:石杉的架構筆記(ID:shishan100)

週一至週五早8點半!精品技術文章準時送上!

目錄

(1)前情提示

(2)ack機制回顧

(3)ack機制實現原理:delivery tag

(4)RabbitMQ如何感知倉儲服務例項當機

(5)倉儲服務處理失敗時的訊息重發

(6)階段總結

1、前情提示

上一篇文章網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(1),我們初步介紹了之前制定的那些訊息中介軟體資料不丟失的技術方案遺留的問題。

一個最大的問題,就是生產者投遞出去的訊息,可能會丟失。

丟失的原因有很多,比如訊息在網路傳輸到一半的時候因為網路故障就丟了,或者是訊息投遞到MQ的記憶體時,MQ突發故障當機導致訊息就丟失了。

針對這種生產者投遞資料丟失的問題,RabbitMQ實際上是提供了一些機制的。

比如,有一種重量級的機制,就是事務訊息機制。採用類事務的機制把訊息投遞到MQ,可以保證訊息不丟失,但是效能極差,經過測試效能會呈現幾百倍的下降。

所以說現在一般是不會用這種過於重量級的機制,而是會用輕量級的confirm機制。

但是我們這篇文章還不能直接講解生產者保證訊息不丟失的confirm機制,因為這種confirm機制實際上是採用了類似消費者的ack機制來實現的。

所以,要深入理解confirm機制,我們得先從這篇文章開始,深入的分析一下消費者手動ack機制保證訊息不丟失的底層原理。

2、ack機制回顧

其實手動ack機制非常的簡單,必須要消費者確保自己處理完畢了一個訊息,才能手動傳送ack給MQ,MQ收到ack之後才會刪除這個訊息。

如果消費者還沒傳送ack,自己就當機了,此時MQ感知到他的當機,就會重新投遞這條訊息給其他的消費者例項。

通過這種機制保證消費者例項當機的時候,資料是不會丟失的。

再次提醒一下大家,如果還對手動ack機制不太熟悉的同學,可以回頭看一下之前的一篇文章:扎心!線上服務當機時,如何保證資料100%不丟失?。然後這篇文章,我們將繼續深入探討一下ack機制的實現原理。

3、ack機制實現原理:delivery tag

如果你寫好了一個消費者服務的程式碼,讓他開始從RabbitMQ消費資料,這時這個消費者服務例項就會自己註冊到RabbitMQ。

所以,RabbitMQ其實是知道有哪些消費者服務例項存在的。

大家看看下面的圖,直觀的感受一下:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

如圖不清晰請移步同名公眾號 接著,RabbitMQ就會通過自己內部的一個“basic.delivery”方法來投遞訊息到倉儲服務裡去,讓他消費訊息。

投遞的時候,會給這次訊息的投遞帶上一個重要的東西,就是“delivery tag”,你可以認為是本次訊息投遞的一個唯一標識。

這個所謂的唯一標識,有點類似於一個ID,比如說訊息本次投遞到一個倉儲服務例項的唯一ID。通過這個唯一ID,我們就可以定位一次訊息投遞。

所以這個delivery tag機制不要看很簡單,實際上他是後面要說的很多機制的核心基礎。

而且這裡要給大家強調另外一個概念,就是每個消費者從RabbitMQ獲取訊息的時候,都是通過一個channel的概念來進行的。

大家回看一下下面的消費者程式碼片段,我們必須是先對指定機器上部署的RabbitMQ建立連線,然後通過這個連線獲取一個channel。

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

而且如果大家還有點印象的話,我們在倉儲服務裡對訊息的消費、ack等操作,全部都是基於這個channel來進行的,channel又有點類似於是我們跟RabbitMQ進行通訊的這麼一個控制程式碼,比如看看下面的程式碼:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

另外這裡提一句:之前寫那篇文章講解手動ack保證資料不丟失的時候,有很多人提出疑問:為什麼上面程式碼裡直接是try finally,如果程式碼有異常,那還是會直接執行finally裡的手動ack?其實很簡單,自己加上catch就可以了。

好的,我們們繼續。你大概可以認為這個channel就是進行資料傳輸的一個管道吧。對於每個channel而言,一個“delivery tag”就可以唯一的標識一次訊息投遞,這個delivery tag大致而言就是一個不斷增長的數字。

大家來看看下面的圖,相信會很好理解的:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

如果採用手動ack機制,實際上倉儲服務每次消費了一條訊息,處理完畢完成排程發貨之後,就會傳送一個ack訊息給RabbitMQ伺服器,這個ack訊息是會帶上自己本次訊息的delivery tag的。

我們們看看下面的ack程式碼,是不是帶上了一個delivery tag?

channel.basicAck(
        delivery.getEnvelope().getDeliveryTag(), 
        false);
複製程式碼

然後,RabbitMQ根據哪個channel的哪個delivery tag,不就可以唯一定位一次訊息投遞了?

接下來就可以對那條訊息刪除,標識為已經處理完畢。

這裡大家必須注意的一點,就是delivery tag僅僅在一個channel內部是唯一標識訊息投遞的。

所以說,你ack一條訊息的時候,必須是通過接受這條訊息的同一個channel來進行。

大家看看下面的圖,直觀的感受一下。

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

其實這裡還有一個很重要的點,就是我們可以設定一個引數,然後就批量的傳送ack訊息給RabbitMQ,這樣可以提升整體的效能和吞吐量。

比如下面那行程式碼,把第二個引數設定為true就可以了。

channel.basicAck(
            delivery.getEnvelope().getDeliveryTag(), 
            true);
複製程式碼

看到這裡,大家應該對這個ack機制的底層原理有了稍微進一步的認識了。起碼是知道delivery tag是啥東西了,他是實現ack的一個底層機制。

然後,我們再來簡單回顧一下自動ack、手動ack的區別。

實際上預設用自動ack,是非常簡單的。RabbitMQ只要投遞一個訊息出去給倉儲服務,那麼他立馬就把這個訊息給標記為刪除,因為他是不管倉儲服務到底接收到沒有,處理完沒有的。

所以這種情況下,效能很好,但是資料容易丟失。

如果手動ack,那麼就是必須等倉儲服務完成商品排程發貨以後,才會手動傳送ack給RabbitMQ,此時RabbitMQ才會認為訊息處理完畢,然後才會標記訊息為刪除。

這樣在傳送ack之前,倉儲服務當機,RabbitMQ會重發訊息給另外一個倉儲服務例項,保證資料不丟。

4、RabbitMQ如何感知到倉儲服務例項當機

之前就有同學提出過這個問題,但是其實要搞清楚這個問題,其實不需要深入的探索底層,只要自己大致的思考和推測一下就可以了。

如果你的倉儲服務例項接收到了訊息,但是沒有來得及排程發貨,沒有傳送ack,此時他當機了。

我們想一想就知道,RabbitMQ之前既然收到了倉儲服務例項的註冊,因此他們之間必然是建立有某種聯絡的。

一旦某個倉儲服務例項當機,那麼RabbitMQ就必然會感知到他的當機,而且對傳送給他的還沒ack的訊息,都傳送給其他倉儲服務例項。

所以這個問題以後有機會我們可以深入聊一聊,在這裡,大家其實先建立起來這種認識即可。

我們再回頭看看下面的架構圖:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

5、倉儲服務處理失敗時的訊息重發

首先,我們來看看下面一段程式碼:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

假如說某個倉儲服務例項處理某個訊息失敗了,此時會進入catch程式碼塊,那麼此時我們怎麼辦呢?難道還是直接ack訊息嗎?

當然不是了,你要是還是ack,那會導致訊息被刪除,但是實際沒有完成排程發貨。

這樣的話,資料不是還是丟失了嗎?因此,合理的方式是使用nack操作。

就是通知RabbitMQ自己沒處理成功訊息,然後讓RabbitMQ將這個訊息再次投遞給其他的倉儲服務例項嘗試去完成排程發貨的任務。

我們只要在catch程式碼塊里加入下面的程式碼即可:

channel.basicNack(
            delivery.getEnvelope().getDeliveryTag(), 
            true);
複製程式碼

注意上面第二個引數是true,意思就是讓RabbitMQ把這條訊息重新投遞給其他的倉儲服務例項,因為自己沒處理成功。

你要是設定為false的話,就會導致RabbitMQ知道你處理失敗,但是還是刪除這條訊息,這是不對的。

同樣,我們還是來一張圖,大家一起來感受一下:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

6、階段總結

這篇文章對之前的ack機制做了進一步的分析,包括底層的delivery tag機制,以及訊息處理失敗時的訊息重發。

通過ack機制、訊息重發等這套機制的落地實現,就可以保證一個消費者服務自身突然當機、訊息處理失敗等場景下,都不會丟失資料。

End

如有收穫,請幫忙轉發,您的鼓勵是作者最大的動力,謝謝!

一大波微服務、分散式、高併發、高可用的原創系列文章正在路上

歡迎掃描下方二維碼,持續關注:

網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2)【石杉的架構筆記】

石杉的架構筆記(id:shishan100)

十餘年BAT架構經驗傾囊相授

推薦閱讀:

1、拜託!面試請不要再問我Spring Cloud底層原理

2、【雙11狂歡的背後】微服務註冊中心如何承載大型系統的千萬級訪問?

3、【效能優化之道】每秒上萬併發下的Spring Cloud引數優化實戰

4、微服務架構如何保障雙11狂歡下的99.99%高可用

5、兄弟,用大白話告訴你小白都能聽懂的Hadoop架構原理

6、大規模叢集下Hadoop NameNode如何承載每秒上千次的高併發訪問

7、【效能優化的祕密】Hadoop如何將TB級大檔案的上傳效能優化上百倍

8、拜託,面試請不要再問我TCC分散式事務的實現原理!

9、【坑爹呀!】最終一致性分散式事務如何保障實際生產中99.99%高可用?

10、拜託,面試請不要再問我Redis分散式鎖的實現原理!

11、【眼前一亮!】看Hadoop底層演算法如何優雅的將大規模叢集效能提升10倍以上?

12、億級流量系統架構之如何支撐百億級資料的儲存與計算

13、億級流量系統架構之如何設計高容錯分散式計算系統

14、億級流量系統架構之如何設計承載百億流量的高效能架構

15、億級流量系統架構之如何設計每秒十萬查詢的高併發架構

16、億級流量系統架構之如何設計全鏈路99.99%高可用架構

17、七張圖徹底講清楚ZooKeeper分散式鎖的實現原理

18、大白話聊聊Java併發面試問題之volatile到底是什麼?

19、大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?

20、大白話聊聊Java併發面試問題之談談你對AQS的理解?

21、大白話聊聊Java併發面試問題之公平鎖與非公平鎖是啥?

22、大白話聊聊Java併發面試問題之微服務註冊中心的讀寫鎖優化

23、網際網路公司的面試官是如何360°無死角考察候選人的?(上篇)

24、網際網路公司面試官是如何360°無死角考察候選人的?(下篇)

25、Java進階面試系列之一:哥們,你們的系統架構中為什麼要引入訊息中介軟體?

26、【Java進階面試系列之二】:哥們,那你說說系統架構引入訊息中介軟體有什麼缺點?

27、【行走的Offer收割機】記一位朋友斬獲BAT技術專家Offer的面試經歷

28、【Java進階面試系列之三】哥們,訊息中介軟體在你們專案裡是如何落地的?

29、【Java進階面試系列之四】扎心!線上服務當機時,如何保證資料100%不丟失?

30、一次JVM FullGC的背後,竟隱藏著驚心動魄的線上生產事故!

31、【高併發優化實踐】10倍請求壓力來襲,你的系統會被擊垮嗎?

32、【Java進階面試系列之五】訊息中介軟體叢集崩潰,如何保證百萬生產資料不丟失?

33、億級流量系統架構之如何在上萬併發場景下設計可擴充套件架構(上)?

34、億級流量系統架構之如何在上萬併發場景下設計可擴充套件架構(中)?

35、億級流量系統架構之如何在上萬併發場景下設計可擴充套件架構(下)?

36、億級流量架構第二彈:你的系統真的無懈可擊嗎?

37、億級流量系統架構之如何保證百億流量下的資料一致性(上)

38、億級流量系統架構之如何保證百億流量下的資料一致性(中)?

39、億級流量系統架構之如何保證百億流量下的資料一致性(下)?

40、網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(1)

作者:石杉的架構筆記 連結:juejin.im/post/5c263a… 來源:掘金 著作權歸作者所有,轉載請聯絡作者獲得授權!

相關文章