主從複製:讀寫分離與複製延遲的怪現象
· tech
📑 目錄
一台 Redis 再快也有記憶體與流量的上限,而且它一掛,資料就懸在半空。走向高可用的第一塊地基,就是主從複製(replication):一個 master 負責寫、若干 replica 各複製一份、分擔讀流量。這也是後面 Cluster 每個分片、以及 Sentinel 自動故障轉移的底層原型。看懂它,你其實是看懂了所有複製系統的通用骨架。
主從拓撲:一個寫、多個讀
設定起來很簡單,在 replica 上一句話就掛上去:
REPLICAOF 10.0.0.1 6379 # 叫這台當 10.0.0.1:6379 的 replica(舊名 SLAVEOF)
INFO replication # 看 role:master/slave、connected_slaves、各自的 offset 與 lag
非同步複製的代價,與那個怪現象
「非同步」是理解主從一切怪現象的鑰匙:master 寫完不等 replica,直接回你「成功」。這很快,但帶來兩個後果——其一,master 突然當掉時,那些還沒傳到 replica 的寫入就這樣丟了;其二,replica 永遠比 master 慢半拍,於是會出現這個經典場面:
x=1、拿到成功回覆,緊接著從 replica 讀 x——卻讀到舊值,因為複製還沒追上。這叫 read-your-writes 破功,不是 bug,是非同步複製的物理必然。解法不是消滅延遲(消不掉),而是分類你的讀:需要「寫完立刻讀得到」的關鍵讀走 master;能容忍稍舊的讀,才放心走 replica。這正是 DDIA 講的複製一致性層級要更強的保證,Redis 給了半套工具:WAIT 1 100 會讓寫入阻塞等到至少 1 個 replica 確認(或逾時);搭配 min-replicas-to-write,可以要求「沒有足夠 replica 跟上就拒絕寫入」。但這些都是拿延遲換安全,不是免費的——本質上你是在非同步的快、和同步的穩之間,自己挑一個點。
斷線重連不必從頭:PSYNC 部分重同步
replica 跟 master 的連線偶爾會斷一下(網路抖動)。早期版本一斷線重連就得整包重來——master 存一份 RDB、整個傳給 replica,大實例上非常痛。現在的 PSYNC 聰明多了:master 手上有一段複製積壓緩衝(replication backlog),replica 記著自己複製到哪個 offset。重連時:
- 斷得不久(缺的資料還在 backlog 裡)→ 部分重同步:master 只補那一小段 offset 之後的資料。
- 斷太久 / 第一次連 / offset 對不上(
FULLRESYNC)→ 才乖乖來一次全量:傳整份 RDB。
所以你在 INFO replication 看到的那個 master_repl_offset,就是主從進度的量尺;兩邊 offset 的差距,就是即時的複製延遲。
反思
非同步複製,是 Redis「要快」的性格在複製層的延伸
Redis 選擇非同步複製(而不是等 replica 都確認才回),跟它 持久化預設不 always fsync是同一種性格:它把「快」放在「零丟失」前面。這很符合它作為熱資料層的定位——多數場景,複製延遲幾毫秒、故障時丟掉最後幾筆寫入,是可以接受的代價,換來的是它招牌的低延遲。但這也提醒我:一個系統的預設值,藏著它的價值觀。用 Redis 前,得先認同它「速度優先」這個立場;如果你的資料一筆都不能丟,那要嘛用 WAIT 補、要嘛一開始就別把它當真相來源。工具的性格要跟你的需求對得上,勉強不來。
複製延遲不是 bug,是物理;工程師的活是「分類讀」
「寫完馬上讀不到」這件事,第一次遇到會以為是 Redis 壞了,其實它是分散式的物理定律——只要複製是非同步、只要資料要跨越距離,就一定有一個延遲窗口。想通這點後,我處理它的方式徹底變了:不再妄想消滅延遲,而是去分類我的每一種讀——哪些是「非得看到自己剛寫的」(下單後看訂單、改完密碼馬上登入),那就走 master;哪些「稍微舊一點無所謂」(看排行榜、逛商品列表),那就放心走 replica 擴展。這套「按一致性需求把讀分流」的思路,是 DDIA 給我最實用的一課,它適用於每一個做了讀寫分離的系統,不只 Redis。
主從是所有高可用系統的「原型骨架」
寫到這篇我更確定一件事:Redis 主從的這套骨架——一份主資料 + 若干複本 + 非同步同步 + 延遲取捨——幾乎是所有複製系統的通用原型。 Kafka 的 partition + ISR、Postgres 的主從、MySQL 的 binlog 複製、甚至 etcd 的 Raft(只是它同步、要過半),骨架都是同一副,差別只在「同步還是非同步、要不要過半、誰能當寫入點」這幾個旋鈕。所以我從不孤立地學一個系統的複製,而是把它掛回這副共通骨架上——看它在那幾個旋鈕上各選了什麼,就懂了它的取捨。學透 Redis 這一組最簡單的主從,再看 Kafka、看資料庫的複製,都是同一個故事的變奏。這也是為什麼我說:看懂它,你看懂的遠不只 Redis。