Aman Kumar Singh

Redis 清單是一種有序的字串序列,你可以從任一端推入與取出。這種簡單的結構使其成為兩種常見需求的最佳選擇:生產者新增工作、消費者取走工作的佇列,以及限制長度的近期項目列表。清單並非能解決所有佇列問題(Streams 才適合更複雜的案例),但對於簡單的先進先出工作與「最新 N 項」列表,它是快速且正確的工具。

這是 Redis Masterclass 的第 5 部分,接續 hashes

推入與取出

清單支援兩端的操作,左端與右端:

LPUSH queue:emails "job1"    # 加入到開頭(左側)
RPUSH queue:emails "job2"    # 加入到結尾(右側)
LPOP queue:emails            # 從開頭移除並回傳
RPOP queue:emails            # 從結尾移除並回傳
LLEN queue:emails            # 長度
LRANGE queue:emails 0 -1     # 所有元素

Enter fullscreen mode Exit fullscreen mode

將一端的推入與另一端的取出結合,就能組成佇列。LPUSH 加上 RPOP 為先進先出:最舊的項目會先被取出。LPUSH 加上 LPOP 則是後進先出,也就是堆疊。每個操作都是原子的,因此多個生產者與消費者可以安全地對同一個清單進行操作。

簡單的工作佇列

基本的工作佇列是由生產者執行 LPUSH 新增工作,工作者執行 RPOP 取出:

# 生產者
LPUSH jobs:email '{"to":"[email protected]","template":"welcome"}'

# 工作者
RPOP jobs:email    # 取得下一個工作,若為空則回傳 nil

Enter fullscreen mode Exit fullscreen mode

直接使用 RPOP 的問題在於:當佇列為空時會立即回傳 nil,因此工作者必須在迴圈中不斷輪詢,浪費 CPU 且增加延遲。解決方法是使用阻塞版本:

BRPOP jobs:email 5    # 等待最多 5 秒,直到有工作可取

Enter fullscreen mode Exit fullscreen mode

BRPOP 會阻塞直到有項目可用(或逾時),因此工作者可以有效睡眠,並在工作抵達時立即喚醒。這讓清單成為真正的推播式工作佇列,無需輪詢。

可靠性缺口

在使用清單作為工作佇列之前,有一個限制需要了解。透過 RPOPBRPOP,當工作者取出工作的那一刻,該工作就已從清單中消失。若工作者在處理過程中當機,工作就會遺失,因為它既不在佇列中,也沒有完成。普通清單提供的是「最多一次」的傳遞,適合可容許遺失的工作,但不適合無法承受遺失的任務。

經典的緩解方式是使用 LMOVE(或較舊的 RPOPLPUSH),它會以原子方式從佇列中取出並推送到「處理中」清單:

LMOVE jobs:email jobs:processing RIGHT LEFT

Enter fullscreen mode Exit fullscreen mode

此時工作位於 jobs:processing,而工作者正在處理。成功時工作者會從中移除;若當機,工作仍留在處理中清單,可被恢復。這種可靠佇列模式彌補了遺失的缺口,但需要手動管理。當你需要真正的傳遞保證、消費者群組與確認機制時,Redis Streams(系列後續文章)正是為此設計,通常是重要工作的更好選擇。清單適合簡單或最佳努力的佇列,Streams 則適合可靠的佇列。

限制長度的列表:「最新 N 項」模式

清單的另一個常見用途是建立有上限的近期項目列表:例如最近 10 則通知、近期活動或時間軸。你可以使用 LPUSH 將新項目推到前端,並將清單修剪到固定長度,避免無限制成長:

LPUSH user:1:notifications "You have a new follower"
LTRIM user:1:notifications 0 9    # 僅保留最新的 10 項

Enter fullscreen mode Exit fullscreen mode

LTRIM 會保留指定的範圍,並丟棄其餘項目。在前端推入並修剪到前 N 項,就能得到自我維護的「最新 N 項」列表,且可用 LRANGE user:1:notifications 0 -1 高效讀取。這是通知與近期活動小工具的乾淨模式,因為你只會顯示最新幾項。

什麼時候該使用或不使用清單作為佇列

為了清楚區分:

  • 使用清單 適用於簡單的先進先出/後進先出佇列、可容許遺失的最佳努力工作,以及限制長度的近期項目列表。它輕量且快速。
  • 使用 LMOVE 轉移到處理中清單 適用於需要基本當機恢復,但不需要完整保證的場景。
  • 使用 Streams 適用於需要可靠傳遞、確認機制、多個消費者群組或重播的場景。清單並未設計用於這些用途,強行使用會導致脆弱的自訂管理。

清單是簡單的有序序列結構:可從任一端推入與取出、阻塞等待工作、修剪以限制長度。對於簡單佇列與近期項目列表,它是正確的選擇,而了解其可靠性限制能幫助你決定何時改用 Streams。

下一篇,我們將介紹集合:Redis 中具有快速成員測試與集合運算的無序唯一值集合。

重點摘要

  • Redis 清單是一種有序序列,可從任一端推入與取出;結合相反兩端即可構成先進先出佇列。
  • 使用 BRPOP(阻塞式取出),讓工作者有效等待工作,而非輪詢空佇列。
  • 普通清單佇列為「最多一次」:若工作者在處理中當機,工作就會遺失。
  • LMOVE 轉移到處理中清單可提供基本當機恢復;若需要真正的傳遞保證,請改用 Streams。
  • LPUSH 加上 LTRIM 可輕量維護「最新 N 項」列表,適合通知與近期活動。

常見問題

如何用 Redis 清單建立佇列?

生產者使用 LPUSH 將項目推入清單,工作者使用 RPOP(或 BRPOP)從另一端取出,以達成先進先出。使用阻塞式的 BRPOP,讓工作者等待新工作,而不是輪詢空清單。

使用清單作為工作佇列有什麼問題?

直接使用 RPOP/BRPOP 會立即移除工作,若工作者在處理中當機,工作就會遺失(最多一次傳遞)。可使用 LMOVE 轉移到處理中清單以恢復,或改用 Redis Streams 取得真正的傳遞保證。

BRPOP 的作用是什麼?

這是阻塞式取出:它會等待清單中有項目可用,或逾時後才回傳。這讓工作者可在工作抵達前保持睡眠,而非不斷輪詢,提供推播式佇列。

如何讓清單只保留最新的 N 項?

使用 LPUSH 推入新項目,再執行 LTRIM key 0 N-1 保留最新的 N 項,其餘捨棄。這能輕量維護自我限制的近期項目列表。

建立佇列時,該使用清單還是 Streams?

清單適合簡單或最佳努力的佇列,以及近期項目列表。當你需要可靠傳遞、確認機制、消費者群組或重播時,請使用 Streams,因為清單無法提供這些功能,除非進行脆弱的手動管理。

延伸閱讀


This article was originally published on amanksingh.com/blog/redis-lists-queues.

關於作者

Aman Kumar Singh 是印度諾伊達的團隊主管兼資深軟體工程師,撰寫全端工程、系統設計,以及使用 TypeScript、Next.js、NestJS、PostgreSQL 與 Redis 建構生產級 SaaS 的相關文章。