記憶體資料結構伺服器 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 仍會用到其他核。面試重點是:邏輯上一次只跑一條命令,所以 INCR、SADD 這類操作天生原子,不需要你自己加鎖。
耐久性是明確取捨
Redis 為速度而生,耐久性不如關聯式資料庫。兩種持久化機制:
| 機制 | 做什麼 | 取捨 |
|---|---|---|
| RDB(快照) | 定期把整個資料集 fork 出來寫成二進位快照 | 恢復快、檔案小;兩次快照之間當機會掉資料 |
| AOF(Append-Only File) | 把每條寫命令追加到日誌 | 掉資料少(可設 everysec/always);檔案大、恢復較慢 |
即使開 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/Sub 與 Streams,可部分取代 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 |
Cluster 把所有 key 透過 CRC16(key) % 16384 算出 hash slot,每個 slot 歸屬某個節點。Client 端維護一份 slot → node 的對照表,直接連到擁有該 key 的節點(不需中央 proxy)。
若 client 連錯節點,該節點回傳 MOVED <slot> <ip:port>,client 更新本地映射後重連正確節點(之後就直連,不再繞路)。節點間用 gossip 協定傳播彼此狀態。
跨鍵操作(如 MGET k1 k2)要求所有 key 在同一 slot —— 用 hash tag({user1}:profile、{user1}:cart 都對 user1 取 slot)強制同節點。
相比傳統分散式資料庫,Redis Cluster 刻意保持簡單(沒有跨分片交易),因為設計重點仍是性能。可對照 02-Distributed-Systems/04-Consistent-Hashing 與 05-Database-Advanced/02-Sharding。
效能:為什麼「反模式」在 Redis 變可行
- 單實例 O(100,000) 寫入/秒,讀取延遲微秒級。
- 在 SQL 裡發 100 次查詢讀一個清單是災難;在 Redis 裡這成本「相對可接受」。
- 但要小心單執行緒的反面:一條 O(N) 的大命令(
KEYS *、對大集合SMEMBERS、大SORT)會阻塞所有其他請求。生產環境用SCAN取代KEYS。
七大使用情境
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
- 每個 key/value 是一筆快取;要擴容就加 Cluster 節點。
- TTL 控制大小與新鮮度:
SETEX key 3600 value,到期自動移除,避免無限膨脹。 - Redis 本身不會替你解決所有快取問題(如 hot key),詳見 07-Caching-Storage/01-Caching 的失效與一致性策略,以及 10-Design-Patterns/01-Scaling-Reads。
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-Lock 與 12-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。
5. Proximity Search(鄰近搜尋)
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 # 訂閱者(連線開著就收得到)
訊息發布時若 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 當加速層放在 source-of-truth DB 前面,TTL 控制新鮮度;同時用它的 Sorted Set 做即時排行榜、SET NX EX 做分散式鎖避免搶票重複扣庫存。Redis 速度優先、耐久性是取捨,所以資料真實來源仍放在 06-Database-Tech/02-PostgreSQL,Redis 掛了也能從 DB 重建。」展示你知道 Redis 是多用途瑞士刀且清楚它的取捨。
常見情境:
- 熱門商品頁:Cache-aside + TTL,hot key 用本地快取 + key 分裂。
- 搶票 / 秒殺:
INCR原子扣庫存 +SET NX分散式鎖防超賣。 - 遊戲/貼文排行榜:Sorted Set,
ZADD寫、ZREVRANGE讀前 N。 - API 限流:固定視窗
INCR+EXPIRE,需精準用 Sorted Set 滑動視窗 + Lua。 - 聊天室在線狀態:Pub/Sub(允許離線漏訊息);要可靠投遞改 Streams。
常見 Deep Dive
因為全在記憶體 + 無鎖無 context switch。單執行緒避免了多執行緒的鎖競爭與快取一致性開銷,每條命令原子執行,CPU 多半不是瓶頸(瓶頸通常是網路與記憶體頻寬)。代價是一條慢命令卡全場,所以生產環境禁 KEYS *、避免對大集合做 O(N) 操作、用 SCAN 漸進掃描。
會。RDB 在兩次快照間掉資料;AOF everysec 最多掉 1 秒。設計上把 Redis 當可重建的快取/加速層,真實來源在持久化 DB。若一定要「速度 + 真耐久」(如金融計數),用 AWS MemoryDB(相容 Redis、多 AZ 交易日誌)。
因為 key 依 hash slot 分散在不同節點,跨節點交易代價高、會破壞性能目標,所以 Cluster 預設只保證單鍵原子性。要對多個 key 做原子操作(如 Lua script 同時改幾個 key),必須讓它們落在同一 slot —— 用 hash tag {tag} 把相關 key 綁在一起。
Streams 是輕量 append-only 日誌 + consumer group,跑在 Redis 裡,延遲低、部署簡單,適合中小吞吐的可靠佇列。Kafka 是專用的分散式日誌,保留期長、吞吐巨大、分區重播、生態完整,適合資料管線與事件溯源主幹。原則:已經有 Redis 且量不大 → Streams;獨立高吞吐事件主幹 → Kafka。對照 09-Messaging-Coord/03-Kafka 與 09-Messaging-Coord/01-Data-Pipeline。
「夠用但不是萬無一失」。單節點 SET NX EX 在大多數場景可接受;但 primary 故障轉移時,鎖可能在複製到 replica 前丟失 → 兩個 client 同時持鎖。嚴格場景用 Redlock(跨多獨立節點取多數鎖)+ fencing token(下游用單調序號拒絕舊持鎖者)。但 Redlock 在學界仍有爭議;真要強一致用 09-Messaging-Coord/05-Zookeeper / etcd(基於共識)。
自我測驗重點
| 問題 | 重點 |
|---|---|
| 為什麼 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 |
Related Notes
- 07-Caching-Storage/01-Caching — Redis 最常見就是當快取;失效、一致性、cache-aside 策略在這裡
- 07-Caching-Storage/03-Distributed-Cache — Redis 是分散式快取的代表實作;hot key 與分片擴容的延伸
- 02-Distributed-Systems/04-Consistent-Hashing — Redis Cluster 用 hash slot 分片,思路與一致性雜湊同源
- 05-Database-Advanced/02-Sharding — Cluster 即水平分片;跨鍵限制對應 cross-shard 操作難題
- 09-Messaging-Coord/04-Distributed-Lock —
SET NX EX/ Redlock 是分散式鎖的經典 Redis 實作 - 09-Messaging-Coord/05-Zookeeper — 嚴格一致的鎖/協調要用共識系統,對比 Redis 鎖的取捨
- 09-Messaging-Coord/03-Kafka — Redis Streams 是輕量版日誌,與 Kafka 在吞吐/保留上取捨
- 09-Messaging-Coord/02-Queue — Streams + consumer group 可當可靠工作佇列
- 10-Design-Patterns/01-Scaling-Reads — Redis 快取與讀副本是擴展讀取的主力手段
- 12-Ops-and-Reliability/01-Dealing-With-Contention — 分散式鎖是處理競爭的核心工具
- 12-Ops-and-Reliability/02-Overload-Protection — Redis 限流(固定/滑動視窗)是過載保護的一環
- 04-Database-Indexing/05-Geospatial-Index — Redis GEO 用 geohash,對應地理空間索引原理
- 06-Database-Tech/03-DynamoDB — 另一個天然 serverless 的 KV,對比 Redis 的記憶體加速定位