Redis-淺談主從同步
主從庫集群
Redis 提供了主從庫模式,以保證數據副本的一致,在從庫執行一下命令可以建立主從庫關係:
replicaof <dst ip> <dst port>
Redis 的主從庫之間採用的是讀寫分離的方式:
- 讀操作:主庫、從庫都可以接收;
- 寫操作:到主庫執行,然後將寫操作同步給從庫。
寫操作只在主庫執行,主要是為了避免多實例寫導致的數據一致性問題,減少多實例之間數據一致的協商開銷。
主從同步是如何進行的
下圖是主從第一次同步的流程:
第一階段
第一階段,從庫會給主庫發送 psync 命令,類似 tcp 的握手,該命令有兩個參數:主庫的 runid 和複製進度 offset。
- runid 是每個 Redis 實例啟動時都會自動生成的一個隨機 ID,用來標記這個實例。從庫第一次和主庫同步時,不知道主庫的 runid ,所以將 runid 設置為 「?」;
- offset 是從庫到目前為止處理的偏移量,第一次同步的時候會傳 -1 。
收到 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 之間的數據同步。