為什麼單機 Redis 不夠#
單實例 Redis 的限制:
- 記憶體:實務上 64GB 是上限(更大則 fork 等操作會痛)
- QPS:10 萬級
- 可用性:單點,crash 即服務停
當業務超過這些線時,需要多節點方案。
三種多節點方案#
Master-Slave#
最基本:主寫從讀。
client → master ──replicate──→ slave 1, slave 2優點:簡單、提升讀吞吐、有災備。 缺點:寫仍單點、容量仍單機。
Sentinel#
在 Master-Slave 之上加哨兵節點,主掛時自動切換。
sentinel cluster ─monitor─→ master + slaves
─failover─→ promote slave to master優點:自動 HA。 缺點:仍單寫、仍受單機容量限。
Cluster#
真正的水平分片。
6 nodes:
master 1 ←→ slave 1
master 2 ←→ slave 2
master 3 ←→ slave 3
key 透過 CRC16 hash mod 16384 → slot → 對應 master每個 master 負責一段 slots,slave 是該 master 的副本。
Redis Cluster 的核心:Slot#
Redis Cluster 不直接 hash key 到 node,而是:
slot = CRC16(key) mod 16384
slot 0 ~ 5460 → master 1
slot 5461 ~ 10922 → master 2
slot 10923 ~ 16383 → master 3預先固定 16384 個 slot。擴容時把部分 slot 從舊 master 搬到新 master:
新增 master 4:
從 master 1, 2, 3 各搬 ~1365 slot 到 master 4slot 設計的好處:擴容時搬遷單位是 slot,可漸進完成;client 看到 MOVED 錯誤後更新 slot 表。
Redis Cluster 的限制#
不是萬靈丹:
1. 不能跨 slot 多 key 操作#
MGET key1 key2 key3如果這三個 key hash 到不同 slot → 報錯。
對策:hash tag
MGET {user:123}:cart {user:123}:profile {user:123}:tokens{user:123} 是 hash tag,只 hash 標籤部分。所有同 tag 的 key 落同 slot。
但濫用 hash tag 會造成 slot 不均(同用戶資料全擠在一片 slot)。
2. 不支援多 DB(select)#
叢集模式下只有 db 0。應用層加 prefix 區隔。
3. 用戶端複雜#
要懂 slot 對應、處理 MOVED/ASK 重導、connection pool 對應每個 master。多數語言的成熟 client(如 lettuce、go-redis、ioredis)已內建。
4. 上限約 1000 nodes#
cluster 的 gossip 協議在節點多時開銷大。Redis 官方建議 < 1000 master。
超大規模:proxy + 分片#
1000 萬 QPS、TB 級資料的場景,Redis Cluster 撐不住怎麼辦?
client → Codis / Twemproxy ─→ 多個 Redis 叢集
(每個叢集獨立、各 N TB)把流量分到多個 Redis Cluster。每個 cluster 仍 < 1000 nodes。proxy 層做 routing。
代表方案:
- Codis(豌豆莢):Go 寫的 proxy + ZK 設定
- Twemproxy / nutcracker(Twitter):早期經典
- Envoy + Redis filter:service mesh 路線
- AWS ElastiCache + 多叢集分片:商業方案
對中小公司不需要 ── 99% 的場景單一 Redis Cluster 已夠。
Cache 與 DB 不一致的根本解:Binlog 同步#
第 6 章提到 cache-aside 在罕見順序下仍會髒。最終解:以 DB 變更為事件源驅動 cache 失效。
MySQL → binlog → Canal / Debezium → Kafka → cache invalidator
↓
Redis DEL key寫 DB 後,binlog 自動產出變更事件,consumer 拿到後失效對應 cache。
優點:
- 業務代碼不需要管 cache 失效
- 失效保證至少一次(binlog 不會漏)
- 不會有「更新了 DB 忘記刪 cache」的人為錯誤
代價:
- 多一個 pipeline 要運維
- 失效有秒級延遲(多數場景可接受)
Binlog 系統的演進#
MySQL ─binlog─→ ?幾代工具:
- MySQL 原生 replication:給 MySQL 從庫用,不易消費
- Canal(阿里):偽裝成 MySQL 從庫,把 binlog 解析後 push 出去
- Debezium:基於 Kafka Connect 的 CDC 框架,支援 MySQL/PG/Mongo/…
- Maxwell / mysql-binlog-connector:輕量級用戶端解析
實務上 Debezium + Kafka 是業界標準。它的優點:
- 多資料庫支援
- exactly-once 保證(透過 Kafka transactions)
- schema evolution 處理
- 與 Kafka 生態深度整合
binlog → Redis 的具體流程#
def kafka_consumer():
on_message(msg):
table = msg["source"]["table"]
op = msg["op"] # 'c'=insert, 'u'=update, 'd'=delete
if table == "products":
product_id = msg["after"]["id"] if op != "d" else msg["before"]["id"]
redis.delete(f"product:{product_id}")
# 或主動更新:
# redis.setex(f"product:{product_id}", 3600, msg["after"])考慮:
- 失效還是更新?預設只失效不更新(避免雙寫複雜性)
- 多個 cache 的 key 怎麼處理:
product_list_by_category:*這種 pattern key 怎麼辦? - 結果性能:Kafka 延遲 + consumer lag → 整體秒級
Binlog 同步到多系統#
binlog 不只給 Redis 用:
MySQL → Kafka ──┬──→ Redis(cache 失效)
├──→ Elasticsearch(搜尋索引)
├──→ Hive / HDFS(資料倉儲)
├──→ ClickHouse(OLAP)
├──→ 推薦系統
└──→ 監控、報表一個 binlog → 多個 consumer,每個獨立。這是現代「資料中台」的核心模式。
一致性與順序#
binlog 是有序的(按事務提交順序)。但跨表變更可能出現:
事務 T1:UPDATE A; UPDATE B;
事務 T2:UPDATE A; UPDATE B;T1 與 T2 在 binlog 是按提交順序排列。consumer 依序處理 → 正確。
但:
- Kafka 中同一個 key 必須同一 partition 才能保證順序
- 若 consumer 並行處理 → 同 key 必須串行
實務:以 db.table.id 作為 Kafka partition key,同 row 變更必同 partition,consumer 在 partition 內單執行緒。
CDC 的延遲鏈#
MySQL commit → binlog write → Debezium poll → Kafka produce → consumer poll → Redis del
ms ms ms ms ms ms整體一般幾百 ms 到幾秒。對 cache invalidation 完全可接受。
延遲累積點:
- Debezium 輪詢 binlog 有間隔(可調,1ms ~ 1s)
- Kafka 生產的 batch(linger.ms 設定)
- consumer 的 batch 處理
優化:對極致延遲需求,每個環節都調小 batch、加大並發。但這是穩態與極致延遲的取捨。
發布訂閱:Redis pub/sub vs Kafka#
Cache 失效用 Kafka 不要用 Redis pub/sub:
| 方面 | Redis pub/sub | Kafka |
|---|---|---|
| 持久化 | 無 ── 消費者錯過就丟 | 有 ── 從任何 offset 重播 |
| 重啟 | 訊息丟 | 不丟 |
| 多 consumer | 廣播(不是 group) | group + offset 機制 |
| 規模 | 幾萬 QPS | 幾十萬 ~ 百萬 QPS |
Redis pub/sub 適合:在線通知(聊天 broadcast)。 Kafka:可靠資料管道、binlog 中繼。
監控 Redis 叢集#
關鍵指標:
INFO memory # used_memory, maxmemory, fragmentation_ratio
INFO stats # total_commands_processed, keyspace_hits/misses
INFO replication # 主從延遲
INFO clients # connected_clients
CLUSTER INFO # cluster_state, slots_ok
CLUSTER SLOTS # 每個 shard 負責的 slot報警閾值:
cluster_state不是ok→ 立刻警報used_memory / maxmemory> 80% → 擴容預警instantaneous_ops_per_sec突降 → 可能 master 卡了- 主從 offset 差距 > 100 MB → 複製異常
災備與資料丟失#
Redis 預設不保證 0 丟失。可選持久化:
| 模式 | 機制 | 丟失上限 |
|---|---|---|
| RDB | 定時 dump 全量 | 自上次 dump 以來的所有寫 |
| AOF | 每次寫追加 log | 取決於 fsync 策略 |
| AOF + RDB | 兩者並用 | 可控 |
AOF fsync 策略:
always:每寫 fsync ── 慢但不丟everysec:每秒 fsync ── 預設,最多丟 1 秒no:靠 OS ── 快但風險大
電商 cache 通常接受丟失(cache 失效後從 DB 重建即可)── 用 RDB 加速重啟即可。如果 Redis 是「主儲存」(如純 KV 業務),AOF + everysec。
成本對比#
Redis 記憶體成本不便宜:
- 雲端 cache.r6g.large(13 GB):~$120/月
- 32 GB instance:~$300/月
- 1 TB 叢集:~$10,000/月
對策:
- TTL 嚴格控制
- 用 hash 結構而非單 key(節省 metadata)
- value 壓縮(msgpack、protobuf)
- 不要 cache 不該 cache 的東西
小結#
- 規模演進:單機 → master-slave → sentinel → Cluster → 多 Cluster + proxy
- Cluster:16384 slots、CRC16、hash tag 控制 colocation
- Cluster 限制:跨 slot 多 key 操作、< 1000 nodes
- Cache 一致性的根本解:MySQL binlog → Kafka → Redis
- Debezium + Kafka 是業界標準 CDC pipeline
- Cache 失效用 Kafka 不用 Redis pub/sub
- 監控 cluster_state、memory、replication、ops
下章看不停機從一個資料庫換到另一個資料庫怎麼做。