記憶體資料結構伺服器 Redis (Redis)

一句話定位

Redis 不只是「一個快取」,而是一個記憶體中的資料結構伺服器(in-memory data structure store)。
它把 String / Hash / List / Set / Sorted Set 等程式語言常見型別直接放進記憶體,用單執行緒保證操作原子且行為好推理,換來 O(100,000) QPS、微秒級延遲。本質是一個速度優先的取捨:你犧牲了關聯式資料庫等級的耐久性,換取極致效能與一把「瑞士刀」—— 快取、分散式鎖、排行榜、限流、地理搜尋、串流、Pub/Sub 全都能做。

為什麼快、為什麼好推理

設計選擇 帶來什麼 代價 / 注意
In-memory(全在記憶體) 微秒級延遲,單實例 ~O(100k) 寫入/秒 容量受 RAM 限制;重啟/當機可能掉資料
Single-threaded(單執行緒命令處理) 每條命令原子、無鎖、無 race、行為易推理 單一慢命令(KEYS *、大 SORT)會卡住整個實例
C 實作 + 自訂 wire protocol 開銷極低,命令集可讀(SET/GET/INCR/SADD
值是資料結構 直接當 List/Set/Sorted Set 操作,不是 binary blob 用錯結構(拿 List 當 Set 用)會付出複雜度代價
單執行緒不等於「整台機器只用一核」

Redis 的命令執行是單執行緒(這就是原子性的來源),但持久化(fork 寫 RDB)、I/O 多工、6.0 後的多執行緒網路 I/O 仍會用到其他核。面試重點是:邏輯上一次只跑一條命令,所以 INCRSADD 這類操作天生原子,不需要你自己加鎖。


耐久性是明確取捨

Redis 為速度而生,耐久性不如關聯式資料庫。兩種持久化機制:

機制 做什麼 取捨
RDB(快照) 定期把整個資料集 fork 出來寫成二進位快照 恢復快、檔案小;兩次快照之間當機會掉資料
AOF(Append-Only File) 把每條寫命令追加到日誌 掉資料少(可設 everysec/always);檔案大、恢復較慢
AOF 不等於「資料庫等級的持久化保證」

即使開 AOF,預設 appendfsync everysec 仍可能掉最多 1 秒的寫入;設 always 又會嚴重拖慢吞吐。這跟傳統 DB 的「commit 必落盤」不同。需要「速度 + 真耐久」時可選 AWS MemoryDB(相容 Redis API,但用多 AZ 交易日誌保證寫入持久)。面試話術:把 Redis 當「可重建的加速層」,真實來源(source of truth)放在持久化資料庫。


支援的資料結構(這是 Redis 的靈魂)

結構 典型命令 經典用途
String SET / GET / INCR 快取值、計數器、限流計數
Hash HSET / HGETALL 物件(如 product:123 的多欄位)
List LPUSH / RPOP 簡單佇列、最近 N 筆
Set SADD / SISMEMBER / SCARD 去重、標籤、交集/聯集
Sorted Set ZADD / ZRANGE / ZREMRANGEBYRANK 排行榜、優先佇列、滑動視窗限流(log N)
Bloom Filter BF.ADD / BF.EXISTS 集合成員性快篩(省記憶體,允許偽陽性)
Geospatial GEOADD / GEOSEARCH 鄰近搜尋(geohash 底層)
Time Series TS.ADD / TS.RANGE 指標、監控資料

這些結構對應程式語言常見型別,所以你能直接「把資料當資料結構操作」,這正是 Redis 在面試中能深入討論具體實作的原因。

進階通訊模式:Pub/SubStreams,可部分取代 Kafka / SNS / SQS(見下方各節)。


部署架構模式

單節點 Single Node          HA Replica                    Cluster(分片)
┌──────────┐               ┌──────────┐                 ┌──────┐ ┌──────┐ ┌──────┐
│  Redis   │               │ Primary  │──同步複製──▶     │node A│ │node B│ │node C│
│ (all key)│               │          │   ┌─────────┐   │slot  │ │slot  │ │slot  │
└──────────┘               └──────────┘   │ Replica │   │0-5460│ │5461- │ │10923-│
   無容錯                      │          └─────────┘   └──────┘ │10922 │ │16383 │
   無擴展                      └─讀分流/故障轉移            水平擴展 └──────┘ └──────┘
模式 解決什麼 限制
單節點 最簡單,一個實例處理所有資料 無容錯、無法擴展,當機即不可用
HA Replica 主從複製,提升讀取與容錯 寫入仍集中在 primary;非同步複製有故障轉移掉資料風險
Cluster 16384 個 hash slot 分散到多節點,水平擴展 預設只支援單鍵操作;跨鍵操作需用 hash tag 保證同 slot

效能:為什麼「反模式」在 Redis 變可行


七大使用情境

1. Cache(最常見)

miss: App ─GET product:123─▶ Redis ─(miss)─▶ App ─查 DB─▶ App ─SETEX product:123 3600─▶ Redis
hit : App ─GET product:123─▶ Redis ─(JSON / Hash)─▶ App

2. Distributed Lock(分散式鎖)

確保「同一操作不被多實例同時執行」(Ticketmaster 搶票、Uber 派單):

簡單版:  SET lock:resource <id> NX EX 30   (NX=不存在才設、EX=30s 自動過期)
          成功 → 拿到鎖;失敗 → 等待 / 重試
釋放:    用 Lua 比對 id 再 DEL(避免刪到別人的鎖)
簡單鎖有兩個經典陷阱

沒設 TTL → 持鎖者當機,鎖永遠不放(死鎖)。② 設了 TTL 但業務超時 → 鎖過期被別人拿走,兩個 client 同時持鎖。進階用 Redlock 演算法跨多節點取鎖,並搭配 fencing token(單調遞增序號)讓下游拒絕舊持鎖者的寫入。完整討論見 09-Messaging-Coord/04-Distributed-Lock12-Ops-and-Reliability/01-Dealing-With-Contention

3. Leaderboard(排行榜)

Sorted Set 以 log(N) 維護排序,天生適合即時排行榜:

ZADD tiger_posts 500 "post1"
ZADD tiger_posts 1   "post2"
ZREMRANGEBYRANK tiger_posts 0 -6   # 只保留前 5 名
ZREVRANGE tiger_posts 0 9 WITHSCORES  # 取前 10

4. Rate Limiting(限流)

固定視窗 Fixed Window:
  INCR rl:user123        # 計數 +1
  若 > N → 拒絕
  EXPIRE rl:user123 W    # W 秒後 reset

滑動視窗 Sliding Window:
  用 Sorted Set 存 timestamp(score=時間),ZREMRANGEBYSCORE 清掉視窗外,
  再 ZCARD 算視窗內數量 —— 用 Lua script 包成一個原子操作

可對照 12-Ops-and-Reliability/02-Overload-Protection 的限流與 load shedding。

GEOADD key <lon> <lat> member
GEOSEARCH key FROMLONLAT <lon> <lat> BYRADIUS <r> km

底層用 geohash 索引;結果有時需再過濾,但對「附近 X 公里」是可行解。對照 04-Database-Indexing/05-Geospatial-Index

6. Event Sourcing / 工作佇列(Redis Streams)

Streams 是 append-only 日誌,類似輕量 Kafka,搭配 consumer group 做可靠工作佇列:

XADD mystream * field value                       # append
XREADGROUP GROUP grp consumer1 COUNT 10 STREAMS mystream >
XACK mystream grp <id>                            # 確認處理完成
XCLAIM mystream grp consumer2 60000 <id>          # 接手失敗 consumer 的待處理項

某 consumer 掛掉,其待處理(PEL, Pending Entries List)可被別人 XCLAIM 接手 → 至少一次交付。需要重量級持久化/重播仍選 09-Messaging-Coord/03-Kafka;輕量場景見 09-Messaging-Coord/02-Queue

7. Pub/Sub(即時推播)

PUBLISH channel message      # 發布者
SUBSCRIBE channel            # 訂閱者(連線開著就收得到)
Pub/Sub 是 at-most-once,不持久化

訊息發布時若 subscriber 離線,永久錯過,Redis 不保存、重連也不補發。

需求 選擇
即時推播、允許偶爾遺失(聊天、線上狀態、通知) Pub/Sub 很合適
需要可靠投遞 / 重播 不要用純 Pub/Sub → Redis Streams / Kafka / SNS+SQS

連線模型加分點:Cluster 模式下 Pub/Sub 對每個節點只需一條連線(不是每個 channel 一條)。即使有數百萬 channel,連線數 ≈ 節點數,所以大規模 channel 仍可行。對照 10-Design-Patterns/05-Real-time-Updates


Hot Key 問題(面試必提的缺點)

當某個 key 請求量遠高於其他 key(爆紅商品、名人貼文),它所在的單一節點被打爆 —— Cluster 分片也救不了,因為同一個 key 只會落在一個 slot。

百萬請求 ──全打 product:123──▶  node B(過載)   node A 閒  node C 閒
緩解策略 做法
客戶端本地快取 App 端 in-process 快取熱 key(local cache / near cache)
Key 分裂 / 隨機化 product:123 拆成 product:123:{0..9},讀時隨機挑一份
加讀取副本 + 動態擴容 HA Replica 分散讀流量
面試加分

主動提出 hot key 並給出對應策略(本地快取 + key 分裂 + 讀副本),會明顯為設計加分。延伸見 07-Caching-Storage/03-Distributed-Cache


面試話術

不要只說「我用 Redis 當快取」

而是:「我用 Redis 當加速層放在 source-of-truth DB 前面,TTL 控制新鮮度;同時用它的 Sorted Set 做即時排行榜SET NX EX 做分散式鎖避免搶票重複扣庫存。Redis 速度優先、耐久性是取捨,所以資料真實來源仍放在 06-Database-Tech/02-PostgreSQL,Redis 掛了也能從 DB 重建。」展示你知道 Redis 是多用途瑞士刀且清楚它的取捨。

常見情境:


常見 Deep Dive


自我測驗重點

問題 重點
為什麼 Redis 快又好推理 in-memory + single-threaded(命令原子、無鎖)
耐久性如何取捨 RDB 快照 / AOF 日誌;皆非 DB 級保證;要真耐久用 MemoryDB
Sorted Set 用途 排行榜、優先佇列、滑動視窗限流(log N)
分散式鎖怎麼做、陷阱 SET NX EX;TTL 防死鎖、Redlock + fencing token 防失效
Cluster 怎麼路由 / 限制 16384 slot、client 端映射、MOVED 重導;預設僅單鍵操作
Pub/Sub vs Streams Pub/Sub at-most-once 不持久化;Streams 可重播 + consumer group
Hot key 緩解 本地快取 / key 分裂隨機化 / 讀副本擴容
限流兩種視窗 固定視窗 INCR+EXPIRE;滑動視窗 Sorted Set + Lua