Pub/Sub vs Stream:Redis 版的訊息系統
· tech
📑 目錄
Redis 也能當訊息系統,但它有兩套截然不同的東西,用錯就會莫名其妙掉訊息、或殺雞用牛刀:Pub/Sub(廣播,丟了就丟)和 Stream(留著的 log,像一台縮小版 Kafka)。這是整個 Redis 系列的收尾,也剛好把它接回 Kafka——因為 Stream 幾乎就是把 Kafka 的核心概念,濃縮進一個 Redis 資料結構。
一個丟了就丟,一個留著可重播
XADD 進去就留著(可設 MAXLEN 上限),consumer 能從任意位置讀、離線後上線還能從上次位置補讀,還帶 consumer group 與 ack。這個「丟了就丟 vs 留著記帳」的差別,跟 RabbitMQ 的 queue vs Kafka 的 log 是同一條軸Pub/Sub:廣播,適合「漏了也沒差」的即時通知
Pub/Sub 的模型三句話講完:SUBSCRIBE 訂閱一個 channel、PUBLISH 往 channel 發、所有當下在線的訂閱者立刻收到。它是 fire-and-forget——broker 不記誰收過、不重送、不留存。所以它天生只適合「漏一兩則也無所謂」的即時廣播:線上狀態、即時通知、跨節點的快取失效廣播(叫大家把某個 key 清掉)。
SUBSCRIBE news # 訂閱(這條連線進入訂閱模式)
PUBLISH news "hello" # 另一條連線發佈,只有此刻在線的訂閱者收得到
千萬別拿它做「不能掉」的任務派發——訂閱者重啟的那幾秒、網路抖一下,那段時間的訊息就永遠消失了,而且你連「掉了」都不會知道。
Stream:留著的 log + consumer group,像輕量 Kafka
要「不能掉、可重播、多個 worker 分工」,就用 Stream。它是一條 append-only log,XADD 寫入、每則有一個遞增 ID;更關鍵的是它有消費者群組(consumer group),行為幾乎就是 Kafka 的翻版:
XACK 確認;沒 ack 的訊息(像 C2 拿了 m4 卻掛掉)會留在 pending(PEL),之後被重新指派、重送——這就是 at-least-once。看出來了嗎?這幾乎是 Kafka consumer group 的縮小版XADD orders * item book qty 2 # 寫一則(* = 自動生遞增 ID)
XGROUP CREATE orders workers 0 # 建一個從頭開始的 group
XREADGROUP GROUP workers c1 COUNT 10 STREAMS orders > # c1 取新訊息
XACK orders workers 1699-0 # 處理完,確認這則(否則留在 PEL)
XPENDING orders workers # 看有哪些「拿了還沒 ack」的
那到底何時該直接上 Kafka?
Stream 這麼像 Kafka,那還要 Kafka 幹嘛?分界在規模與定位:
- 用 Redis Stream:輕量的訊息 / 任務佇列,量沒有大到誇張、保留時間短、你本來就有 Redis、不想為了一個佇列再架一整套 Kafka。
- 直接上 Kafka:超大 throughput、要長期保留(TB 級、幾天到幾週,可回溯重算)、巨量 fan-out(幾十個消費組各讀一份)、要生態(Kafka Connect、Streams)、要更硬的持久保證。Redis Stream 的資料活在記憶體裡(靠 RDB/AOF 落盤、又通常設 MAXLEN 截斷),它天生不是為「保留海量歷史」設計的。
一句話:Redis Stream 是「順手好用的輕量佇列」,不是「Kafka 的替代品」。 需求還小就別為了一個佇列扛一座 Kafka;但真需要 Kafka 那些能力時,硬用 Stream 撐,就是在重造一個殘缺的 Kafka。
反思
「丟了就丟」還是「留著記帳」,先問這一句
Pub/Sub 和 Stream 的選擇,說到底只有一個問題:漏掉一則,你承受得起嗎? 承受得起(即時通知、狀態廣播),Pub/Sub 又輕又簡單;承受不起(訂單、扣款、任務派發),就得用會留存、能重播、有 ack 的 Stream。這跟我在 RabbitMQ 那條「留著 vs 拿走」、Kafka 的「log vs queue」是同一種思考——選訊息方案前,先想清楚『漏一則會發生什麼』,答案自己就把工具挑好了。我看過太多事故,root cause 就是拿 fire-and-forget 的東西去送「絕對不能掉」的訊息,還一直以為是別的地方壞了。
好的抽象會「長成同一個形狀」
Redis Stream 讓我很有感的一點:它幾乎是把 Kafka 的核心——append log、offset、consumer group、ack、pending——濃縮進一個 Redis 資料結構。兩個團隊、兩套完全不同的實作,最後長出的骨架卻高度雷同。這說明那些概念不是 Kafka 的專利,而是「可靠的持久訊息」這個問題的自然解——誰認真去做,都會長出 log + offset + group + ack 這副形狀。這也是我學東西越來越省力的原因:看懂一個好抽象,就看懂了一票。學透 Kafka 的 consumer group,再看 Redis Stream、看別家的訊息系統,都是同一個故事換套皮。
收尾:Redis 的每一面,都在證明「它不只是快取」
寫到這篇,整個 Redis 系列剛好收口。回頭看這一路——資料結構、單執行緒、持久化、過期淘汰、快取三災、分散式鎖、複製、Sentinel、Cluster、交易與 Lua、到這篇的訊息系統——每一面其實都在證明開篇那句話:Redis 是一台記憶體資料結構伺服器,「快取」只是它最出名的一個用法。 它能當鎖、當佇列、當排行榜、當計數器、當輕量訊息 log,是因為它給了你一套又快又原子的資料結構,剩下的是你怎麼組合。這也是我最想留給讀完整個系列的你的一句話:別把 Redis 框在「快取」那個小盒子裡——當你開始把它當「手邊一台隨叫隨到的資料結構伺服器」,很多原本要架大東西才能解的問題,會突然變得又輕又簡單。