Redis 的靈魂:五大資料結構 + 進階武器

· tech

#redis#data-structures

📑 目錄

上一篇說 Redis 的靈魂是「資料結構」——那這篇就把工具箱打開。Redis 用得好不好,九成看你會不會選對結構:選對了,一個排行榜三行命令搞定;選錯了,你會用一堆 GET/SET 在應用端硬幹本來伺服器一行就能做的事。先看五個核心結構,以及它們各自的招牌用途:

五大核心結構

五大核心結構:選對一個,問題解一半 Stringbytes / 數字 42 快取 · 計數器(INCR)· 分散式鎖(SET NX) List有序,兩端進出 佇列(LPUSH / RPOP)· 最新 N 筆(LPUSH+LTRIM) Hashfield → value name: Aidanage: 30 存物件 · 只改一個欄位,免整包搬進搬出 Set無序、自動去重 去重 · 標籤 · 交集(共同好友 SINTER) Sorted Set成員帶 score、自動排序 a:1b:2c:3 排行榜 · 範圍查詢 · 延遲佇列(score=時間)
五個結構就是五類問題的答案:要計數用 String(INCR 原子加一);要先進先出用 List;要存物件的欄位用 Hash;要去重或算交集用 Set;要排序、排名、範圍用 Sorted Set。選結構的訣竅,是先問「我要對這份資料做什麼操作」——結構的本質,就是把你最常做的那個操作變成 O(1) 或 O(log N)

這裡面最該花時間認識的是 Sorted Set(ZSet),它是 Redis 的皇冠上的寶石:每個成員都綁一個 score,Redis 幫你永遠維持排序。這一個特性就長出好幾種殺手級用法——排行榜(ZADD 記分、ZREVRANGE 取榜)是最直覺的;但把 score 設成時間戳,它立刻變成一個延遲佇列(ZRANGEBYSCORE 撈出「到期該處理」的任務);把 score 設成分頁游標,又能做穩定的範圍分頁。同一個結構,換個 score 的語意,就是完全不同的武器。

四件進階武器

核心五種之外,Redis 還有幾個為特定問題量身打造的進階結構,它們的共通哲學是——用一點精度或限制,換巨大的空間或速度:

四件進階武器:用一點精度/限制,換空間或速度 Bitmap每人一個 bit → 簽到、活躍統計省空間:1 億人 ≈ 12MB HyperLogLog近似基數(count distinct)固定 12KB 估上億 UV,誤差 ~0.81% Geo地理座標(底層是 Sorted Set)附近的人、範圍搜尋 Stream持久化 log + consumer group像輕量 Kafka(第 12 篇深入)
Bitmap 用一個個 bit 表達布林狀態,省空間到極致;HyperLogLog 犧牲一點準度(誤差不到 1%),就能用固定 12KB 算出上億的不重複數;Geo 其實是 Sorted Set 的包裝(把經緯度編碼成 score);Stream 則是持久化的訊息流,像一個內建在 Redis 裡的輕量 Kafka。它們都在示範同一種取捨:放棄一點「什麼都精確、什麼都能做」,換來對某個特定問題壓倒性的效率

其中 HyperLogLog 最能體現這種哲學:算「不重複的訪客數(UV)」如果用 Set 存每一個 user id,一億個訪客就要吃掉好幾 GB;HLL 用機率演算法,固定只花 12KB,就能估出上億的基數,誤差不到 1%。對「大概幾百萬 UV」這種不需要絕對精確的統計,這是壓倒性的划算——這也是工程判斷的一個縮影:先問「這真的需要精確嗎」,常常不需要,而不需要的地方,就有巨大的優化空間。

選結構的心法:先看操作,再選結構

貫穿這一切的心法只有一句:別從「我要存什麼」出發,要從「我要對它做什麼操作」出發。 同一份「使用者分數」,如果你只是要存起來讀回,String/Hash 就夠;但只要出現「排名」「取前十」「某分數區間」這種操作,答案立刻變成 Sorted Set。結構選對,那個操作就是 O(log N) 的一行命令;選錯,就是把整包資料撈回應用端、自己排序的一場災難。Redis 逼你重新重視一件學校教過、但工作後常忘記的事——資料結構的選擇,本身就是效能設計

redis-cli:五大結構的招牌命令

每個結構配一組最常用的命令,看一遍就抓到「這結構天生擅長什麼」:

# String:原子計數器
INCR views:page1                       # 就地 +1,不必讀回來加再寫回
# List:最新動態 / 佇列
LPUSH feed p3; LRANGE feed 0 9         # 頭塞、取最新 10 筆
# Hash:存物件的欄位
HSET user:1 name Aidan age 30; HGETALL user:1
# Set:去重與交集
SADD tag:redis u1 u2; SINTER tag:redis tag:db   # 同時有兩個標籤的人
# ZSet(皇冠):排行榜
ZADD rank 100 u1 95 u2; ZREVRANGE rank 0 2 WITHSCORES   # Top 3

命令本身就在告訴你選型答案:要排行榜,ZREVRANGE 天生就是 ZSet 的活;要交集(共同好友、同標籤),SINTER 天生就是 Set 的活。先想「我要下哪個操作」,結構自己就浮出來。

反思

選對資料結構,是 Redis 用得好不好的分水嶺

我帶新人時,最愛看的一個指標,就是他用 Redis 只會不會 GET/SET。只會這兩招的,通常把 Redis 當「快一點的 KV」,遇到排行榜就撈回來排、遇到去重就自己用一個 array 檢查;會用 ZSet、Set、Hash 的,同樣的需求程式碼少一半、又快又原子。這個差距不在「熟不熟 Redis 指令」,而在有沒有『用資料結構思考』的習慣——看到需求,先在腦中問「這對應到哪個結構」。這也是為什麼我覺得 Redis 對後端是很好的訓練:它把抽象的資料結構課,變成你每天都會用到、且立刻有效能回饋的實戰。

進階結構教我的:先問「這真的需要精確嗎」

HyperLogLog 對我是個觀念衝擊。以前我預設「統計就是要準」,直到看懂用 12KB 估上億 UV、誤差不到 1% 這件事——對一個放在 dashboard 上、給人看趨勢的 UV 數字,99% 準跟 100% 準有差嗎?沒有,但成本差了好幾個數量級。從那之後,我在做任何統計、任何查詢前都會多問一句:「這個結果,需要多精確?」 很多時候答案是「差不多就好」,而「差不多就好」的地方,往往藏著最大的優化空間。用一點點精度換巨大的資源節省,是工程上極划算、卻常被忽略的一手。

Redis 讓資料結構這門課「活」了過來

大學的資料結構課,對很多人是背複雜度、考完就還給老師的東西。但 Redis 把它變成活的:你每選一次結構,都在真實地決定系統的效能與行為;ZADD 是 O(log N)、SINTER 的成本取決於最小集合、對大 List 做 LINDEX 是 O(N)——這些不再是考卷上的符號,而是「會不會拖垮線上服務」的實際後果。我甚至覺得,想快速幫一個工程師打好資料結構的底,讓他認真用一輪 Redis,比刷題還有效——因為它把「選錯結構的代價」直接擺在你眼前,而痛過的東西,才記得住。