本文轉載於: https://mp.weixin.qq.com/s/uhMrqR__6qgpl7vrE_otTQ
1 引言
我經常聽到很多人討論,關於把 Redis 當作隊列來用是否合適
的問題。
有些人表示贊成,他們認為 Redis
很輕量,用作隊列很方便。
也些人則反對,認為 Redis
會丟
數據,最好還是用專業
的隊列中間件更穩妥
究竟哪種方案更好呢?
這篇文章,就聊一聊把 Redis
當作隊列,究竟是否合適這個問題。
從簡單到復雜,一步步梳理其中的細節,把這個問題真正的講清楚。
看完這篇文章后對這個問題你會有全新的認識。
在文章的最后,還會告訴你關於技術選型
的思路,文章有點長,希望你可以耐心讀完
1.1 Redis中List隊列
1.1.1 簡單使用
從最簡單的開始:List
隊列
首先,我們先從最簡單的場景開始講起
如果你的業務需求足夠簡單,想把 Redis
當作隊列來使用,肯定最先想到的就是使用 List
這個數據類型
因為List
底層的實現就是一個鏈表
,在頭部和尾部操作元素,時間復雜度都是 O(1)
,這意味着它非常符合消息隊列的模型。
如果把 List
當作隊列,你可以這么來用。
生產者使用 LPUSH
發布消息:
127.0.0.1:6379> LPUSH queue msg1
(integer) 1
127.0.0.1:6379> LPUSH queue msg2
(integer) 2
消費者這一側,使用 RPOP
拉取消息:
127.0.0.1:6379> RPOP queue
"msg1"
127.0.0.1:6379> RPOP queue
"msg2"
這個模型非常簡單,也很容易理解。
但這里有個小問題,當隊列中已經沒有消息了,消費者在執行 RPOP
時,會返回 NULL
127.0.0.1:6379> RPOP queue
(nil) // 沒消息了
而我們在編寫消費者邏輯時,一般是一個死循環
,這個邏輯需要不斷地從隊列中拉取消息進行處理,偽代碼一般會這么寫:
while true:
msg = redis.rpop("queue")
// 沒有消息,繼續循環
if msg == null:
continue
// 處理消息
handle(msg)
如果此時隊列為空,那消費者依舊會頻繁拉取消息,這會造成CPU 空轉
,不僅浪費 CPU
資源,還會對 Redis
造成壓力。
1.1.2 解決cpu空轉問題
怎么解決這個問題呢?
也很簡單,當隊列為空時,我們可以休眠
一會,再去嘗試拉取消息。代碼可以修改成這樣:
while true:
msg = redis.rpop("queue")
// 沒有消息,休眠2s
if msg == null:
sleep(2)
continue
// 處理消息
handle(msg)
這就解決了 CPU
空轉問題
這個問題雖然解決了,但又帶來另外一個問題:當消費者在休眠等待時,有新消息來了,那消費者處理新消息就會存在延遲
假設設置的休眠時間是 2s
,那新消息最多存在 2s
的延遲。
1.1.3 Redis阻塞式拉取
要想縮短這個延遲,只能減小休眠的時間。但休眠時間越小,又有可能引發 CPU
空轉問題。
魚和熊掌不可兼得
那如何做,既能及時處理新消息,還能避免 CPU
空轉呢?
Redis
是否存在這樣一種機制:如果隊列為空,消費者在拉取消息時就阻塞等待
,一旦有新消息過來,就通知我的消費者立即處理新消息呢?
幸運的是,Redis
確實提供了「阻塞式」拉取消息的命令:BRPOP / BLPOP
,這里的 B
指的是阻塞Block
現在,你可以這樣來拉取消息了:
while true:
// 沒消息阻塞等待,0表示不設置超時時間
msg = redis.brpop("queue", 0)
if msg == null:
continue
// 處理消息
handle(msg)
使用 BRPOP
這種阻塞式方式拉取消息時,還支持傳入一個超時時間
,如果設置為 0
,則表示不設置超時,直到有新消息才返回,否則會在指定的超時時間后返回 NULL
這個方案不錯,既兼顧了效率,還避免了 CPU
空轉問題,一舉兩得
注意:
如果設置的超時時間太長,這個連接太久沒有活躍過,可能會被 Redis Server
判定為無效連接,之后 Redis Server
會強制把這個客戶端踢下線。所以,采用這種方案,客戶端要有重連機制。
解決了消息處理不及時的問題,你可以再思考一下,這種隊列模型,有什么缺點?
不支持重復消費
:消費者拉取消息后,這條消息就從List
中刪除了,無法被其它消費者再次消費,即不支持多個消費者消費同一批數據消息丟失
:消費者拉取到消息后,如果發生異常宕機,那這條消息就丟失了
第一個問題是功能上的,使用 List
做消息隊列,它僅僅支持最簡單的,一組生產者對應一組消費者,不能滿足多組生產者和消費者的業務場景
第二個問題就比較棘手了,因為從 List
中 POP
一條消息出來后,這條消息就會立即從鏈表中刪除了。也就是說,無論消費者是否處理成功,這條消息都沒辦法再次消費了。這也意味着,如果消費者在處理消息時異常宕機,那這條消息就相當於丟失了。
1.2 Redis發布訂閱
1.2.1 簡單使用
發布/訂閱模型:Pub/Sub
從名字就能看出來,這個模塊是 Redis
專門是針對發布/訂閱
這種隊列模型設計的
它正好可以解決前面提到的第一個問題:重復消費
。
即多組生產者、消費者的場景,我們來看它是如何做的。
Redis
提供了 PUBLISH / SUBSCRIBE
命令,來完成發布、訂閱的操作。
假設你想開啟 2 個消費者,同時消費同一批數據,就可以按照以下方式來實現。
首先,使用 SUBSCRIBE
命令,啟動 2 個消費者,並訂閱
同一個隊列。
// 2個消費者 都訂閱一個隊列
127.0.0.1:6379> SUBSCRIBE queue
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "queue"
3) (integer) 1
此時,2 個消費者都會被阻塞住,等待新消息的到來。
之后,再啟動一個生產者,發布一條消息。
127.0.0.1:6379> PUBLISH queue msg1
(integer) 1
這時,2 個消費者就會解除阻塞,收到生產者發來的新消息。
127.0.0.1:6379> SUBSCRIBE queue
// 收到新消息
1) "message"
2) "queue"
3) "msg1"
看到了么,使用 Pub/Sub
這種方案,既支持阻塞式拉取消息,還很好地滿足了多組消費者,消費同一批數據的業務需求。
除此之外,Pub/Sub
還提供了匹配訂閱
模式,允許消費者根據一定規則,訂閱多個自己感興趣的隊列
// 訂閱符合規則的隊列
127.0.0.1:6379> PSUBSCRIBE queue.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "queue.*"
3) (integer) 1
這里的消費者,訂閱了 queue.*
相關的隊列消息。
之后,生產者分別向 queue.p1
和 queue.p2
發布消息。
127.0.0.1:6379> PUBLISH queue.p1 msg1
(integer) 1
127.0.0.1:6379> PUBLISH queue.p2 msg2
(integer) 1
這時再看消費者,它就可以接收到這 2 個生產者的消息了。
127.0.0.1:6379> PSUBSCRIBE queue.*
Reading messages... (press Ctrl-C to quit)
...
// 來自queue.p1的消息
1) "pmessage"
2) "queue.*"
3) "queue.p1"
4) "msg1"
// 來自queue.p2的消息
1) "pmessage"
2) "queue.*"
3) "queue.p2"
4) "msg2"
我們可以看到,Pub/Sub
最大的優勢就是,支持多組生產者、消費者處理消息。
1.2.2 發布訂閱的缺點
講完了它的優點,那它有什么缺點呢?
其實,Pub/Sub
最大問題是:丟數據
如果發生以下場景,就有可能導致數據丟失:
- 消費者下線
- Redis 宕機
- 消息堆積
究竟是怎么回事?
這其實與 Pub/Sub
的實現方式有很大關系。
Pub/Sub
在實現時非常簡單,它沒有基於任何數據類型,也沒有做任何的數據存儲,它只是單純地為生產者、消費者建立數據轉發通道
,把符合規則的數據,從一端轉發到另一端。
一個完整的發布、訂閱消息處理流程是這樣的:
- 消費者訂閱指定隊列,
Redis
就會記錄一個映射關系:隊列->消費者
- 生產者向這個隊列發布消息,那
Redis
就從映射關系中找出對應的消費者,把消息轉發給它
看到了么,整個過程中,沒有任何的數據存儲,一切都是實時轉發的。
這種設計方案,就導致了上面提到的那些問題。
例如,如果一個消費者異常掛掉了,它再重新上線后,只能接收新的消息,在下線期間生產者發布的消息,因為找不到消費者,都會被丟棄掉。
如果所有消費者都下線了,那生產者發布的消息,因為找不到任何一個消費者,也會全部丟棄
所以,當你在使用 Pub/Sub
時,一定要注意:消費者必須先訂閱隊列,生產者才能發布消息,否則消息會丟失
這也是前面講例子時,我們讓消費者先訂閱隊列,之后才讓生產者發布消息的原因。
另外,因為 Pub/Sub
沒有基於任何數據類型實現,所以它也不具備數據持久化
的能力。
也就是說,Pub/Sub
的相關操作,不會寫入到 RDB
和 AOF
中,當 Redis
宕機重啟,Pub/Sub
的數據也會全部丟失。
最后,我們來看 Pub/Sub
在處理消息積壓
時,為什么也會丟數據?
當消費者的速度,跟不上生產者時,就會導致數據積壓的情況發生。
如果采用 List
當作隊列,消息積壓時,會導致這個鏈表很長,最直接的影響就是,Redis
內存會持續增長,直到消費者把所有數據都從鏈表中取出。
但 Pub/Sub
的處理方式卻不一樣,當消息積壓時,有可能會導致消費失敗和消息丟失!
這是怎么回事?
還是回到 Pub/Sub
的實現細節上來說。
每個消費者訂閱一個隊列時,Redis
都會在 Server
上給這個消費者在分配一個緩沖區
,這個緩沖區其實就是一塊內存。
當生產者發布消息時,Redis
先把消息寫到對應消費者的緩沖區中。
之后,消費者不斷地從緩沖區讀取消息,處理消息。
但是,問題就出在這個緩沖區
上
因為這個緩沖區其實是有上限
的(可配置),如果消費者拉取消息很慢,就會造成生產者發布到緩沖區的消息開始積壓,緩沖區內存持續增長。
如果超過了緩沖區配置的上限,此時,Redis
就會強制
把這個消費者踢下線
這時消費者就會消費失敗,也會丟失數據。
如果你有看過 Redis
的配置文件,可以看到這個緩沖區的默認配置:client-output-buffer-limit pubsub 32mb 8mb 60
它的參數含義如下:
32mb
:緩沖區一旦超過 32MB,Redis 直接強制把消費者踢下線8mb + 60
:緩沖區超過 8MB,並且持續 60 秒,Redis 也會把消費者踢下線
Pub/Sub
的這一點特點,是與 List
作隊列差異比較大的
從這里你應該可以看出,List
其實是屬於拉
模型,而 Pub/Sub
其實屬於推
模型。
List
中的數據可以一直積壓在內存中,消費者什么時候來拉
都可以。
但 Pub/Sub
是把消息先推
到消費者在 Redis Server
上的緩沖區中,然后等消費者再來取。
當生產、消費速度不匹配時,就會導致緩沖區的內存開始膨脹,Redis
為了控制緩沖區的上限,所以就有了上面講到的,強制把消費者踢下線的機制。
好了,現在我們總結一下 Pub/Sub
的優缺點:
- 支持發布 / 訂閱,支持多組生產者、消費者處理消息
- 消費者下線,數據會丟失
- 不支持數據持久化,
Redis
宕機,數據也會丟失 - 消息堆積,緩沖區溢出,消費者會被強制踢下線,數據也會丟失
有沒有發現,除了第一個是優點之外,剩下的都是缺點。
所以,很多人看到 Pub/Sub
的特點后,覺得這個功能很雞肋
也正是以上原因,Pub/Sub
在實際的應用場景中用得並不多
目前只有哨兵集群和Redis
實例通信時,采用了 Pub/Sub
的方案,因為哨兵正好符合即時通訊的業務場景。
我們再來看一下,Pub/Sub
有沒有解決,消息處理時異常宕機,無法再次消費的問題呢?
其實也不行,Pub/Sub
從緩沖區取走數據之后,數據就從 Redis
緩沖區刪除了,消費者發生異常,自然也無法再次重新消費。
好,現在我們重新梳理一下,我們在使用消息隊列時的需求。
當我們在使用一個消息隊列時,希望它的功能如下:
- 支持阻塞等待拉取消息
- 支持發布 / 訂閱模式
- 消費失敗,可重新消費,消息不丟失
- 實例宕機,消息不丟失,數據可持久化
- 消息可堆積
Redis
除了 List
和 Pub/Sub
之外,還有符合這些要求的數據類型嗎?
其實,Redis
的作者也看到了以上這些問題,也一直在朝着這些方向努力着。
Redis
作者在開發 Redis
期間,還另外開發了一個開源項目 disque
這個項目的定位,就是一個基於內存的分布式消息隊列中間件。
但由於種種原因,這個項目一直不溫不火。
終於,在 Redis 5.0
版本,作者把 disque
功能移植到了 Redis
中,並給它定義了一個新的數據類型:Stream
下面我們就來看看,它能符合上面提到的這些要求嗎?
1.3 Redis中的Stream
1.3.1 簡單使用
趨於成熟的隊列:Stream
我們來看 Stream
是如何解決上面這些問題的
我們依舊從簡單到復雜,依次來看 Stream
在做消息隊列時,是如何處理的?
首先,Stream
通過 XADD
和 XREAD
完成最簡單的生產、消費模型:
- XADD:發布消息
- XREAD:讀取消息
生產者發布 2 條消息:
// *表示讓Redis自動生成消息ID
127.0.0.1:6379> XADD queue * name zhangsan
"1618469123380-0"
127.0.0.1:6379> XADD queue * name lisi
"1618469127777-0"
使用 XADD
命令發布消息,其中的*
表示讓 Redis
自動生成唯一的消息 ID
這個消息 ID
的格式是時間戳-自增序號
消費者拉取消息:
// 從開頭讀取5條消息,0-0表示從開頭讀取
127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 0-0
1) 1) "queue"
2) 1) 1) "1618469123380-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618469127777-0"
2) 1) "name"
2) "lisi"
如果想繼續拉取消息,需要傳入上一條消息的 ID:
127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 1618469127777-0
(nil)
沒有消息,Redis
會返回 NULL
以上就是 Stream
最簡單的生產、消費
這里不再重點介紹 Stream
命令的各種參數,我在例子中演示時,凡是大寫的單詞都是固定
參數,凡是小寫的單詞,都是可以自己定義的,例如隊列名、消息長度等等,下面的例子規則也是一樣,為了方便你理解,這里有必要提醒一下。
下面我們來看,針對前面提到的消息隊列要求,Stream
都是如何解決的?
1.3.2 stream阻塞拉取
Stream
是否支持阻塞式
拉取消息?
可以的,在讀取消息時,只需要增加 BLOCK
參數即可
// BLOCK 0 表示阻塞等待,不設置超時時間
127.0.0.1:6379> XREAD COUNT 5 BLOCK 0 STREAMS queue 1618469127777-0
這時,消費者就會阻塞等待,直到生產者發布新的消息才會返回。
1.3.3 Stream支持發布 / 訂閱模式
也沒問題,Stream
通過以下命令完成發布訂閱:
XGROUP
:創建消費者組XREADGROUP
:在指定消費組下,開啟消費者拉取消息
下面我們來看具體如何做?
首先,生產者依舊發布 2 條消息:
127.0.0.1:6379> XADD queue * name zhangsan
"1618470740565-0"
127.0.0.1:6379> XADD queue * name lisi
"1618470743793-0"
之后,我們想要開啟 2 組消費者處理同一批數據,就需要創建 2 個消費者組:
// 創建消費者組1,0-0表示從頭拉取消息
127.0.0.1:6379> XGROUP CREATE queue group1 0-0
OK
// 創建消費者組2,0-0表示從頭拉取消息
127.0.0.1:6379> XGROUP CREATE queue group2 0-0
OK
消費者組創建好之后,我們可以給每個消費者組
下面掛一個消費者
,讓它們分別處理同一批數據。
第一個消費組開始消費:
// group1的consumer開始消費,>表示拉取最新數據
127.0.0.1:6379> XREADGROUP GROUP group1 consumer COUNT 5 STREAMS queue >
1) 1) "queue"
2) 1) 1) "1618470740565-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618470743793-0"
2) 1) "name"
2) "lisi"
同樣地,第二個消費組開始消費:
// group2的consumer開始消費,>表示拉取最新數據
127.0.0.1:6379> XREADGROUP GROUP group2 consumer COUNT 5 STREAMS queue >
1) 1) "queue"
2) 1) 1) "1618470740565-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618470743793-0"
2) 1) "name"
2) "lisi"
我們可以看到,這 2 組消費者,都可以獲取同一批數據進行處理了。
這樣一來,就達到了多組消費者「訂閱」消費的目的。
1.3.4 stream不丟消息
消息處理時異常,Stream
能否保證消息不丟失,重新消費?
除了上面拉取消息時用到了消息 ID
,這里為了保證重新消費,也要用到這個消息 ID。
當一組消費者處理完消息后,需要執行 XACK
命令告知 Redis
,這時 Redis
就會把這條消息標記為處理完成
// group1下的 1618472043089-0 消息已處理完成
127.0.0.1:6379> XACK queue group1 1618472043089-0
如果消費者異常宕機,肯定不會發送 XACK
,那么 Redis
就會依舊保留這條消息。
待這組消費者重新上線后,Redis
就會把之前沒有處理成功的數據,重新發給這個消費者。這樣一來,即使消費者異常,也不會丟失數據了。
// 消費者重新上線,0-0表示重新拉取未ACK的消息
127.0.0.1:6379> XREADGROUP GROUP group1 consumer1 COUNT 5 STREAMS queue 0-0
// 之前沒消費成功的數據,依舊可以重新消費
1) 1) "queue"
2) 1) 1) "1618472043089-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618472045158-0"
2) 1) "name"
2) "lisi"
1.3.5 stream持久化處理
Stream
是新增加的數據類型,它與其它數據類型一樣,每個寫操作,也都會寫入到 RDB
和 AOF
中
我們只需要配置好持久化策略,這樣的話,就算 Redis 宕機重啟,Stream
中的數據也可以從 RDB
或 AOF
中恢復回來。
1.3.6 stream消息堆積
消息堆積時,Stream 是怎么處理的?
其實,當消息隊列發生消息堆積時,一般只有 2 個解決方案:
生產者限流
:避免消費者處理不及時,導致持續積壓丟棄消息
:中間件丟棄舊消息,只保留固定長度的新消息
而 Redis
在實現 Stream
時,采用了第 2 個方案。
在發布消息時,你可以指定隊列的最大長度,防止隊列積壓導致內存爆炸。
// 隊列長度最大10000
127.0.0.1:6379> XADD queue MAXLEN 10000 * name zhangsan
"1618473015018-0"
當隊列長度超過上限后,舊消息會被刪除,只保留固定長度的新消息。
這么來看,Stream
在消息積壓時,如果指定了最大長度,還是有可能丟失消息的。
除了以上介紹到的命令,Stream
還支持查看消息長度XLEN
、查看消費者狀態XINFO
等命令,使用也比較簡單,你可以查詢官方文檔了解一下,這里就不過多介紹了。
好了,通過以上介紹,我們可以看到,Redis
的 Stream
幾乎覆蓋到了消息隊列的各種場景,是不是覺得很完美?
既然它的功能這么強大,這是不是意味着,Redis
真的可以作為專業的消息隊列中間件來使用呢?
但是還差一點
,就算 Redis
能做到以上這些,也只是趨近於專業的消息隊列。
原因在於 Redis
本身的一些問題,如果把其定位成消息隊列,還是有些欠缺的。
到這里,就不得不把 Redis
與專業的隊列中間件做對比了。
1.4 與專業消息對比
與專業的消息隊列對比
其實,一個專業的消息隊列,必須要做到兩大塊:
- 消息不丟
- 消息可堆積
前面我們討論的重點,很大篇幅圍繞的是第一點展開的。
這里我們換個角度,從一個消息隊列的「使用模型」來分析一下,怎么做,才能保證數據不丟?
使用一個消息隊列,其實就分為三大塊:生產者、隊列中間件、消費者
消息是否會發生丟失,其重點也就在於以下 3 個環節:
- 生產者會不會丟消息?
- 消費者會不會丟消息?
- 隊列中間件會不會丟消息?
1.4.1 生產者會不會丟消息
當生產者在發布消息時,可能發生以下異常情況:
- 消息沒發出去:網絡故障或其它問題導致發布失敗,中間件直接返回失敗
- 不確定是否發布成功:網絡問題導致發布超時,可能數據已發送成功,但讀取響應結果超時了
如果是情況 1,消息根本沒發出去,那么重新發一次就好了。
如果是情況 2,生產者沒辦法知道消息到底有沒有發成功?所以,為了避免消息丟失,它也只能繼續重試,直到發布成功為止。
生產者一般會設定一個最大重試次數,超過上限依舊失敗,需要記錄日志報警處理。
也就是說,生產者為了避免消息丟失,只能采用失敗重試的方式來處理。
但發現沒有?這也意味着消息可能會重復發送。
是的,在使用消息隊列時,要保證消息不丟,寧可重發,也不能丟棄。
那消費者這邊,就需要多做一些邏輯了。
對於敏感業務,當消費者收到重復數據數據時,要設計冪等邏輯,保證業務的正確性。
從這個角度來看,生產者會不會丟消息,取決於生產者對於異常情況的處理是否合理。
所以,無論是 Redis
還是專業的隊列中間件,生產者在這一點上都是可以保證消息不丟的。
1.4.2 消費者會不會丟消息
這種情況就是我們前面提到的,消費者拿到消息后,還沒處理完成,就異常宕機了,那消費者還能否重新消費失敗的消息?
要解決這個問題,消費者在處理完消息后,必須「告知」隊列中間件,隊列中間件才會把標記已處理,否則仍舊把這些數據發給消費者。
這種方案需要消費者和中間件互相配合,才能保證消費者這一側的消息不丟。
無論是 Redis
的 Stream
,還是專業的隊列中間件,例如 RabbitMQ、Kafka
,其實都是這么做的。
所以,從這個角度來看,Redis
也是合格的。
1.4.3 隊列中間件會不會丟消息
前面 2 個問題都比較好處理,只要客戶端和服務端配合好,就能保證生產端、消費端都不丟消息。
但是,如果隊列中間件本身就不可靠呢?
畢竟生產者和消費這都依賴它,如果它不可靠,那么生產者和消費者無論怎么做,都無法保證數據不丟。
在這個方面,Redis
其實沒有達到要求。
Redis
在以下 2 個場景下,都會導致數據丟失。
AOF
持久化配置為每秒寫盤,但這個寫盤過程是異步的,Redis
宕機時會存在數據丟失的可能
主從復制也是異步的,主從切換時,也存在丟失數據的可能(從庫還未同步完成主庫發來的數據,就被提成主庫)
基於以上原因我們可以看到,Redis
本身的無法保證嚴格的數據完整性。
所以,如果把 Redis
當做消息隊列,在這方面是有可能導致數據丟失的。
再來看那些專業的消息隊列中間件是如何解決這個問題的?
像 RabbitMQ
或 Kafka
這類專業的隊列中間件,在使用時,一般是部署一個集群,生產者在發布消息時,隊列中間件通常會寫多個節點
,以此保證消息的完整性。這樣一來,即便其中一個節點掛了,也能保證集群的數據不丟失。
也正因為如此,RabbitMQ、Kafka
在設計時也更復雜。畢竟,它們是專門針對隊列場景設計的。
但 Redis
的定位則不同,它的定位更多是當作緩存來用,它們兩者在這個方面肯定是存在差異的。
1.4.4 消息積壓怎么辦
因為 Redis
的數據都存儲在內存中,這就意味着一旦發生消息積壓,則會導致 Redis
的內存持續增長,如果超過機器內存上限,就會面臨被 OOM
的風險。
所以,Redis
的 Stream
提供了可以指定隊列最大長度的功能,就是為了避免這種情況發生。
但 Kafka、RabbitMQ
這類消息隊列就不一樣了,它們的數據都會存儲在磁盤上,磁盤的成本要比內存小得多,當消息積壓時,無非就是多占用一些磁盤空間,相比於內存,在面對積壓時也會更加坦然
綜上,我們可以看到,把 Redis
當作隊列來使用時,始終面臨的 2 個問題:
Redis
本身可能會丟數據- 面對消息積壓,
Redis
內存資源緊張
到這里,Redis
是否可以用作隊列,我想這個答案你應該會比較清晰了。
如果你的業務場景足夠簡單,對於數據丟失不敏感,而且消息積壓概率比較小的情況下,把 Redis 當作隊列是完全可以的。
而且,Redis
相比於 Kafka、RabbitMQ
,部署和運維也更加輕量。
如果你的業務場景對於數據丟失非常敏感,而且寫入量非常大,消息積壓時會占用很多的機器資源,那么我建議你使用專業的消息隊列中間件。