Redis如何實現(xiàn)一個消息隊列 如何是實現(xiàn)延時隊列 既然redis可以實現(xiàn)隊列,為什么還需要mq消息隊列

   如果你的業(yè)務(wù)需求足夠簡單,想把 Redis 當(dāng)作隊列來使用,肯定最先想到的就是使用 List 這個數(shù)據(jù)類型。因為 List 底層的實現(xiàn)就是一個「鏈表」,在頭部和尾部操作元素,時間復(fù)雜度都是 O(1),這意味著它非常符合消息隊列的模型如果把 List 當(dāng)作隊列,你可以這么來用。

生產(chǎn)者使用 LPUSH 發(fā)布消息:


圖片.png

消費者這一側(cè),使用 RPOP 拉取消息:


圖片.png

這個模型非常簡單,也很容易理解。

圖片.png

它可以支持多個生產(chǎn)者和多個消費者并發(fā)進(jìn)出消息,每個消費者拿到的消息都
是不同的列表元素

延時隊列得實現(xiàn)
延時隊列可以通過 Redis 的 zset (有序列表)來實現(xiàn)。我們將消息序列化成一個字符串作為 zset 的 value,這個消息的到期處理時間作為 score,然后用多個線程輪詢zset 獲取到期的任務(wù)進(jìn)行處理 。多個結(jié)程是為了保障可用性,萬一掛了一個線程還有其他線程可以繼續(xù)處理。因為有多個線程,所以需要考慮并發(fā)爭搶任務(wù),確保任務(wù)不會被多次執(zhí)行。

偽代碼后期實現(xiàn)

延時隊列的應(yīng)用
延時隊列( Delayed Job)在項目中應(yīng)該經(jīng)常會使用到,比如用戶下單超過30分鐘沒有支付,后臺就自動取消訂單;再比如新用戶注冊后,要求10分鐘后給用戶發(fā)一封郵件。這些需求都需要通過延時隊列實現(xiàn)。

既然redis可以實現(xiàn)隊列,為什么還需要mq消息隊列?
與專業(yè)的消息隊列對比,其實,一個專業(yè)的消息隊列,必須要做到兩大塊:
消息不丟
消息可堆積

這里我們換個角度,從一個消息隊列的【使用模型】來分析下,才能保證數(shù)據(jù)不丟失?使用一個消息隊列,分為三大塊:生產(chǎn)者,隊列中間件,消費者

圖片.png

1、生產(chǎn)者會不會丟消息?
2、消費者會不會丟消息?
3、隊列中間件會不會丟消息?

  1. 生產(chǎn)者會不會丟消息?
  1. 當(dāng)生產(chǎn)者在發(fā)布消息時,可能發(fā)生以下異常情況:
    消息沒發(fā)出去:網(wǎng)絡(luò)故障或其它問題導(dǎo)致發(fā)布失敗,[中間件]直接返回失敗
  2. 不確定是否發(fā)布成功:網(wǎng)絡(luò)問題導(dǎo)致發(fā)布超時,可能數(shù)據(jù)已發(fā)送成功,但讀取響應(yīng)結(jié)果超時了

如果是情況 1,消息根本沒發(fā)出去,那么重新發(fā)一次就好了。
如果是情況 2,生產(chǎn)者沒辦法知道消息到底有沒有發(fā)成功?所以,為了避免消息丟失,它也只能繼續(xù)重試,直到發(fā)布成功為止。
生產(chǎn)者一般會設(shè)定一個最大重試次數(shù),超過上限依舊失敗,需要記錄日志報警處理。也就是說,生產(chǎn)者為了避免消息丟失,只能采用失敗重試的方式來處理。但發(fā)現(xiàn)沒有?這也意味著消息可能會重復(fù)發(fā)送。是的,在使用消息隊列時,要保證消息不丟,寧可重發(fā),也不能丟棄。
那消費者這邊,就需要多做一些邏輯了。
對于敏感業(yè)務(wù),當(dāng)消費者收到重復(fù)數(shù)據(jù)數(shù)據(jù)時,要設(shè)計冪等邏輯,保證業(yè)務(wù)的正確性。從這個角度來看,生產(chǎn)者會不會丟消息,取決于生產(chǎn)者對于異常情況的處理是否合理。所以,無論是 Redis 還是專業(yè)的隊列中間件,生產(chǎn)者在這一點上都是可以保證消息不丟的。

  1. 消費者會不會丟消息?
    這種情況消費者拿到消息后,還沒處理完成,就異常宕機(jī)了,那消費者還能否重新消費失敗的消息?
    要解決這個問題,消費者在處理完消息后,必須「告知」隊列中間件,隊列中間件才會把標(biāo)記已處理,否則仍舊把這些數(shù)據(jù)發(fā)給消費者。
    這種方案需要消費者和中間件互相配合,才能保證消費者這一側(cè)的消息不丟。
    無論是 Redis 的 Stream,還是專業(yè)的隊列中間件,例如 RabbitMQ、Kafka,其實都是這么做的。
    所以,從這個角度來看,Redis 也是合格的。
  2. 隊列中間件會不會丟消息?
    前面 2 個問題都比較好處理,只要客戶端和服務(wù)端配合好,就能保證生產(chǎn)端、消費端都不丟消息。但是,如果隊列中間件本身就不可靠呢?
    畢竟生產(chǎn)者和消費這都依賴它,如果它不可靠,那么生產(chǎn)者和消費者無論怎么做,都無法保證數(shù)據(jù)不丟。
    在這個方面,Redis 其實沒有達(dá)到要求。
    Redis 在以下 2 個場景下,都會導(dǎo)致數(shù)據(jù)丟失。
    1.AOF 持久化配置為每秒寫盤,但這個寫盤過程是異步的,Redis 宕機(jī)時會存在數(shù)據(jù)丟失的可能
    2.[主從復(fù)制]也是異步的,主從切換時,也存在丟失數(shù)據(jù)的可能(從庫還未同步完成主庫發(fā)來的數(shù)據(jù),就被提成[主庫]
    基于以上原因我們可以看到,Redis 本身的無法保證嚴(yán)格的數(shù)據(jù)完整性。

所以,如果把 Redis 當(dāng)做消息隊列,在這方面是有可能導(dǎo)致數(shù)據(jù)丟失的。

再來看那些專業(yè)的[消息隊列中間件]是如何解決這個問題的?像 RabbitMQ 或 Kafka 這類專業(yè)的隊列中間件,在使用時,一般是部署一個集群,生產(chǎn)者在發(fā)布消息時,隊列中間件通常會寫「多個節(jié)點」,以此保證消息的完整性。這樣一來,即便其中一個節(jié)點掛了,也能保證集群的數(shù)據(jù)不丟失。

也正因為如此,RabbitMQ、Kafka在設(shè)計時也更復(fù)雜。畢竟,它們是專門針對隊列場景設(shè)計的。但 Redis 的定位則不同,它的定位更多是當(dāng)作緩存來用,它們兩者在這個方面肯定是存在差異的。最后,我們來看消息積壓怎么辦?

  1. 消息積壓怎么辦?
    因為 Redis 的數(shù)據(jù)都存儲在內(nèi)存中,這就意味著一旦發(fā)生消息積壓,則會導(dǎo)致 Redis 的內(nèi)存持續(xù)增長,如果超過機(jī)器內(nèi)存上限,就會面臨被 OOM 的風(fēng)險。所以,Redis 的 Stream 提供了可以指定隊列最大長度的功能,就是為了避免這種情況發(fā)生。但 Kafka、RabbitMQ 這類消息隊列就不一樣了,它們的數(shù)據(jù)都會存儲在磁盤上,磁盤的成本要比內(nèi)存小得多,當(dāng)消息積壓時,無非就是多占用一些磁盤空間,相比于內(nèi)存,在面對積壓時也會更加「坦然」。
    綜上,我們可以看到,把 Redis 當(dāng)作隊列來使用時,始終面臨的 2 個問題:
    Redis 本身可能會丟數(shù)據(jù)
    面對消息積壓,Redis 內(nèi)存資源緊張
    到這里,Redis 是否可以用作隊列,我想這個答案你應(yīng)該會比較清晰了。
    如果你的業(yè)務(wù)場景足夠簡單,對于數(shù)據(jù)丟失不敏感,而且消息積壓概率比較小的情況下,把 Redis 當(dāng)作隊列是完全可以的。
    而且,Redis 相比于 Kafka、RabbitMQ,部署和運維也更加輕量。如果你的業(yè)務(wù)場景對于數(shù)據(jù)丟失非常敏感,而且寫入量非常大,消息積壓時會占用很多的機(jī)器資源,那么我建議你使用專業(yè)的消息隊列中間件。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容