為什麼單機 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 4

slot 設計的好處:擴容時搬遷單位是 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─→ ?

幾代工具:

  1. MySQL 原生 replication:給 MySQL 從庫用,不易消費
  2. Canal(阿里):偽裝成 MySQL 從庫,把 binlog 解析後 push 出去
  3. Debezium:基於 Kafka Connect 的 CDC 框架,支援 MySQL/PG/Mongo/…
  4. 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/subKafka
持久化無 ── 消費者錯過就丟有 ── 從任何 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

下章看不停機從一個資料庫換到另一個資料庫怎麼做。