Redis 提供了主從庫模式,以保證資料副本的一致,在從庫執行一下命令可以建立主從庫關係:
replicaof <dst ip> <dst port>
Redis 的主從庫之間採用的是讀寫分離的方式:
寫操作只在主庫執行,主要是為了避免多範例寫導致的資料一致性問題,減少多範例之間資料一致的協商開銷。
下圖是主從第一次同步的流程:
第一階段,從庫會給主庫傳送 psync 命令,類似 tcp 的握手,該命令有兩個引數:主庫的 runid 和複製進度 offset。
收到 psync 命令後,主庫會用 FULLRESYNC 響應帶上主庫 id 和當前的複製進度 offset 返回給從庫,從庫會記錄這兩個引數。
主庫在收到 psync 後,會執行 bgsave 命令,生成 RDB 檔案,然後將檔案傳送給從庫。從庫收到檔案後,會先情況當前的資料,然後載入 RDB 檔案。
在從庫處理完 RDB 檔案時,主庫會將期間處理的寫操作放在 replication buffer 中,等到從庫處理完 RDB 檔案後,主庫會將修改操作都傳送給從庫執行,從而完成主從同步。
主從庫的連線建立成功,並且完成第一次的全量同步之後,主從庫之間會維持一個長連結,主庫會將之後接收到的寫操作同步給從庫。
在使用過程中,可能會出現主從庫之間網路閃斷的情況,如果恢復連線後採用全量同步的方式,必然會有很大的開銷。Redis 2.8 之後,採用增量同步的方式來完成這個操作。
當主從斷連之後,主庫會把期間收到的寫操作命令寫入 replication buffer,同時也會把這些操作命令也寫入 repl_backlog_buffer 這個緩衝區。
repl_backlog_buffer 是一個環形緩衝區,主庫會記錄自己的偏移量 master_repl_offset,從庫會記錄自己的偏移量 slave_repl_offset。用命令 info Replication
可以檢視對應的 offset。
主從庫恢復連線後,從庫會用 psync 傳送自己的 slave_repl_offset 給主庫,主庫對比自己的 master_repl_offset ,將兩個 offset 之間的寫操作同步給從庫。
因為 repl_backlog_buffer 是一個環形佇列,所以,如果從庫的讀取速度比較慢,就有可能導致從庫還未讀取的操作被主庫新寫的操作覆蓋,如果主庫接收從庫的 psync 時發現從庫的 offset 已經被覆蓋,為了不丟失資料那麼就會發起全量同步。為了避免全量同步,這時候就需要增加 repl_backlog_size 的值,這個值和緩衝空間大小有關,快取空間大小 = 主庫寫入命令速度 * 操作大小 - 主從庫間網路傳輸命令速度 * 操作大小
。考慮到突發壓力,通常 repl_backlog_size 會設定為 計算結果的 2 到 4 倍。
如果一個主庫下有很多的從庫,這些從庫都要和主庫進行全量同步的時候,主庫的壓力會非常大,忙於 fork 子程序生成 RDB 檔案,影響主執行緒處理使用者端請求。這是可以將主從結構改成主→ 從 → 從
的聯機結構,緩解主庫的壓力。
我們可以用 docker-compose 實驗 Redis 主從同步:
version: "3"
services:
redis-master:
image: redis:7
ports:
- "16379:6379"
container_name: "redis-master"
command: redis-server
networks:
- redis-replica
redis-slave-1:
image: redis:7
ports:
- "6380:6379"
container_name: "redis-slave-1"
command: redis-server --replicaof redis-master 6379
depends_on:
- redis-master
networks:
- redis-replica
redis-slave-2:
image: redis:7
ports:
- "6381:6379"
container_name: "redis-slave-2"
command: redis-server --replicaof redis-slave-1 6379
depends_on:
- redis-slave-1
networks:
- redis-replica
networks:
redis-replica:
這裡定義了一個主庫 redis-master 以及兩個從庫 redis-slave-1、redis-slave-2。它們是一個級聯的主從關係 redis-master ← redis-slave-1 ← redis-slave-2
我預先在 master 中插入了一些資料之後在設定了兩個 slave 節點。啟動之後,可以看到 slave2 先向 salve1 發起了同步請求,但是 slave1 還沒和 master 完成同步,所以 salve2 一直在重試,直到 salve1 和 master 完成同步後才開始 slave2 和 salve1 之間的資料同步。