上一篇文章:<<互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第二篇>>,咱們分析了 ack 機制的底層實現原理(delivery tag
機制),還有消除處理失敗時的nack機制如何觸發消息重發。html
經過這個,已經讓你們進一步對消費端保證數據不丟失的方案的理解更進一層了。面試
這篇文章,咱們將會對 ack 底層的delivery tag
機制進行更加深刻的分析,讓你們理解的更加透徹一些。微信
面試時,若是被問到消息中間件數據不丟失問題的時候,能夠更深刻到底層,給面試官進行分析。架構
首先,咱們要給你們介紹一下RabbitMQ的prefetch count
這個概念。併發
你們看過上篇文章以後應該都知道了,對每一個 channel
(其實對應了一個消費者服務實例,你大致能夠這麼來認爲),RabbitMQ 投遞消息的時候,都是會帶上本次消息投遞的一個delivery tag
的,惟一標識一次消息投遞。異步
而後,咱們進行 ack 時,也會帶上這個 delivery tag
,基於同一個 channel
進行 ack,ack 消息裏會帶上 delivery tag
讓RabbitMQ知道是對哪一次消息投遞進行了 ack,此時就能夠對那條消息進行刪除了。高併發
你們先來看一張圖,幫助你們回憶一下這個delivery tag
的概念。fetch
因此你們能夠考慮一下,對於每一個channel
而言(你就認爲是針對每一個消費者服務實例吧,好比一個倉儲服務實例),其實都有一些處於unack
狀態的消息。3d
好比RabbitMQ正在投遞一條消息到channel
,此時消息確定是unack
狀態吧?code
而後倉儲服務接收到一條消息之後,要處理這條消息須要耗費時間,此時消息確定是unack
狀態吧?
同時,即便你執行了ack
以後,你要知道這個ack
他默認是異步執行的,尤爲若是你開啓了批量ack
的話,更是有一個延遲時間纔會ack
的,此時消息也是unack
吧?
那麼你們考慮一下,RabbitMQ 他可以無限制的不停給你的消費者服務實例推送消息嗎?
明顯是不能的,若是 RabbitMQ 給你的消費者服務實例推送的消息過多過快,好比都有幾千條消息積壓在某個消費者服務實例的內存中。
那麼此時這幾千條消息都是unack
的狀態,一直積壓着,是否是有可能會致使消費者服務實例的內存溢出?內存消耗過大?甚至內存泄露之類的問題產生?
因此說,RabbitMQ 是必需要考慮一下消費者服務的處理能力的。
你們看看下面的圖,感覺一下若是消費者服務實例的內存中積壓消息過多,都是unack
的狀態,此時會怎麼樣。
正是由於這個緣由,RabbitMQ基於一個prefetch count
來控制這個unack message
的數量。
你能夠經過「channel.basicQos(10)」
這個方法來設置當前channel
的prefetch count
。
舉個例子,好比你要是設置爲10的話,那麼意味着當前這個channel
裏,unack message
的數量不能超過10個,以此來避免消費者服務實例積壓unack message過多。
這樣的話,就意味着RabbitMQ正在投遞到channel過程當中的unack message
,以及消費者服務在處理中的unack message
,以及異步ack以後還沒完成 ack 的unack message
,全部這些message 加起來,一個 channel 也不能超過10個。
若是你要簡單粗淺的理解的話,也大體能夠理解爲這個prefetch count
就表明了一個消費者服務同時最多能夠獲取多少個 message 來處理。因此這裏也點出了 prefetch 這個單詞的意思。
prefetch 就是預抓取的意思,就意味着你的消費者服務實例預抓取多少條 message 過來處理,可是最多隻能同時處理這麼多消息。
若是一個 channel 裏的unack message
超過了prefetch count
指定的數量,此時RabbitMQ就會中止給這個 channel 投遞消息了,必需要等待已經投遞過去的消息被 ack 了,此時才能繼續投遞下一個消息。
老規矩,給你們上一張圖,咱們一塊兒來看看這個東西是啥意思。
好!如今你們對 ack 機制底層的另一個核心機制:prefetch 機制也有了一個深入的理解了。
此時,我們就應該來考慮一個問題了。就是如何來設置這個prefetch count
呢?這個東西設置的過大或者太小有什麼影響呢?
其實你們理解了上面的圖就很好理解這個問題了。
假如說咱們把 prefetch count
設置的很大,好比說3000,5000,甚至100000,就這樣特別大的值,那麼此時會如何呢?
這個時候,在高併發大流量的場景下,可能就會致使消費者服務的內存被快速的消耗掉。
由於假如說如今MQ接收到的流量特別的大,每秒都上千條消息,並且此時你的消費者服務的prefetch count
還設置的特別大,就會致使可能一瞬間你的消費者服務接收到了達到prefetch count
指定數量的消息。
打個比方,好比一會兒你的消費者服務內存裏積壓了10萬條消息,都是unack的狀態,反正你的prefetch count設置的是10萬。
那麼對一個channel,RabbitMQ就會最多容忍10萬個unack狀態的消息,在高併發下也就最多可能積壓10萬條消息在消費者服務的內存裏。
那麼此時致使的結果,就是消費者服務直接被擊垮了,內存溢出,OOM,服務宕機,而後大量unack的消息會被從新投遞給其餘的消費者服務,此時其餘消費者服務同樣的狀況,直接宕機,最後形成雪崩效應。
全部的消費者服務由於扛不住這麼大的數據量,所有宕機。
你們來看看下面的圖,本身感覺一下現場的氛圍。
那麼若是反過來呢,咱們要是把prefetch count設置的很小會如何呢?
好比說咱們把 prefetch count 設置爲1?此時就必然會致使消費者服務的吞吐量極低。由於你即便處理完一條消息,執行ack了也是異步的。
給你舉個例子,假如說你的 prefetch count = 1
,RabbitMQ最多投遞給你1條消息處於 unack 狀態。
此時好比你剛處理完這條消息,而後執行了 ack 的那行代碼,結果不幸的是,ack須要異步執行,也就是須要100ms以後纔會讓RabbitMQ感知到。
那麼100ms以後RabbitMQ感知到消息被ack了,此時纔會投遞給你下一條消息!
這就尷尬了,在這100ms期間,你的消費者服務是否是啥都沒幹啊?
這不就直接致使了你的消費者服務處理消息的吞吐量可能降低10倍,甚至百倍,千倍,都有這種可能!
你們看看下面的圖,感覺一下低吞吐量的現場。
因此鑑於上面兩種極端狀況,RabbitMQ官方給出的建議是prefetch count通常設置在100~300之間。
也就是一個消費者服務最多接收到100~300個message來處理,容許處於unack狀態。
這個狀態下能夠兼顧吞吐量也很高,同時也不容易形成內存溢出的問題。
可是其實在咱們的實踐中,這個prefetch count你們徹底是能夠本身去壓測一下的。
好比說慢慢調節這個值,不斷加大,觀察高併發大流量之下,吞吐量是否愈來愈大,並且觀察消費者服務的內存消耗,會不會OOM、頻繁FullGC等問題。
其實經過最近幾篇文章,基本上已經把消息中間件的消費端如何保證數據不丟失這個問題剖析的較爲深刻和透徹了。
若是你是基於RabbitMQ來作消息中間件的話,消費端的代碼裏,必須考慮三個問題:手動ack、處理失敗的nack、prefetch count的合理設置
這三個問題背後涉及到了各類機制:
這些底層機制和問題,我們都一步步分析清楚了。
因此到如今,單論消費端這塊的數據不丟失技術方案,相信你們在面試的時候就能夠有一整套本身的理解和方案能夠闡述了。
接下來下篇文章開始,咱們就來具體聊一聊:消息中間件的生產端如何保證數據不丟失。
互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第一篇
互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第二篇
互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第四篇
來源:【微信公衆號 - 石杉的架構筆記】