Redis 複制詳解
- 1. 複制配置
-
- 1.1 建立複制
- 1.2 斷開複制
- 1.3 安全性
- 1.4 隻讀
- 1.5 傳輸延遲
- 2. 拓撲
-
- 2.1 一主一從結構
- 2.2 一主多從結構
- 2.3 樹狀主從結構
- 3. 複制原理
-
- 3.1 複制過程
- 3.2 資料同步
- 3.3 全量複制
- 3.4 部分複制
在分布式系統中為了解決單點問題,通常會把資料複制多個副本部署到其他機器,滿足故障恢複和負載均衡等需求。Redis 也是如此,它為我們提供了複制功能,實作了相同資料的多個 Redis 副本。
1. 複制配置
1.1 建立複制
參與複制的 Redis 執行個體劃分為主節點 (master) 和從節點 (slave)。預設情況下,Redis 都是主節點。每個從節點隻能有一個主節點,而主節點可以 同時具有多個從節點。複制的資料流是單向的,隻能由主節點複制到從節點。配置複制的方式有以下三種:
- 在配置檔案中加入 slaveof {masterHost} {masterPort} 随 Redis 啟動生效;
- 在 redis-server 啟動指令後加入 --slaveof {masterHost} {masterPort} 生效;
- 直接使用指令:slaveof {masterHost} {masterPort} 生效。
綜上所述,slaveof 指令在使用時,可以運作期動态配置,也可以提前寫到配置檔案中。
# 例如本地啟動兩個端口為6379和6380的Redis節點,在 127.0.0.1:6380執行如下指令:
127.0.0.1:6380>slaveof 127.0.0.1 6379
slaveof 配置都是在從節點發起,這時6379 作為主節點,6380 作為從節點。複制關系建立後執行如下指令測試:
127.0.0.1:6379>set hello redis
OK
127.0.0.1:6379>get hello
"redis"
127.0.0.1:6380>get hello
"redis"
從運作結果中看到複制已經工作了,針對主節點 6379 的任何修改都可以同步到從節點 6380中,複制過程如圖所示

slaveof 本身是異步指令,執行 slaveof 指令時,節點隻儲存主節點資訊後傳回,後續複制流程在節點内部異步執行,主從節點複制成功建立後,可以使用 info replication 指令檢視複制相關狀态,如下所示
# 主節點6379複制狀态資訊
127.0.0.1:6379>info replication
# Replication
role:master
connected_slaves:1 slave0:ip=127.0.0.1,port=6379,state=online,offset=43,lag=0 ....
# 從節點6380複制狀态資訊
127.0.0.1:6380>info replication
# Replication
role:slave master_host:127.0.0.1 master_port:6380 master_link_status:up
master_last_io_seconds_ago:4 master_sync_in_progress:0 ...
1.2 斷開複制
slaveof 指令不但可以建立複制,還可以在從節點執行 slaveof no one 來斷開與主節點複制關系。例如在 6380 節點上執行 slaveof no one 來斷開複制。
斷開複制主要流程:
- 斷開與主節點複制關系;
-
從節點晉升為主節點。
從節點斷開複制後并不會抛棄原有資料,隻是無法再擷取主節點上的資料變化。
通過 slaveof 指令還可以實作切主操作,所謂切主是指把目前從節點對主節點的複制切換到另一個主節點。執行 slaveof {newMasterIp} {newMasterPort} 指令即可,例如把 6380節點從原來的複制 6379節點變為複制 6381節點
切主操作流程如下:
- 斷開與舊主節點複制關系;
- 與新主節點建立複制關系;
- 删除從節點目前所有資料;
- 對新主節點進行複制操作。
切主後從節點會清空之前所有的資料,線上人工操作時小心 slaveof 在錯誤的節點上執行或者指向錯誤的主節點。
1.3 安全性
對于資料比較重要的節點,主節點會通過設定 requirepass 參數進行密碼驗證,這時所有的用戶端通路必須使用 auth 指令實行校驗。從節點與主節點的複制連接配接是通過一個特殊辨別的用戶端來完成,是以需要配置從節點的 masterauth 參數與主節點密碼保持一緻,這樣從節點才可以正确地連接配接到主節點并發起複制流程。
1.4 隻讀
預設情況下,從節點使用 slave-read-only=yes 配置為隻讀模式。由于複制隻能從主節點到從節點,對于從節點的任何修改主節點都無法感覺,修改從節點會造成主從資料不一緻。是以建議線上不要修改從節點的隻讀模式。
1.5 傳輸延遲
主從節點一般部署在不同機器上,複制時的網絡延遲就成為需要考慮的問題,Redis 為我們提供了 repl-disable-tcp-nodelay 參數用于控制是否關閉 TCP_NODELAY,預設關閉:
- 當關閉時,主節點産生的指令資料無論大小都會及時地發送給從節點,這樣主從之間延遲會變小,但增加了網絡帶寬的消耗。适用于主從之間的網絡環境良好的場景,如同機架或同機房部署;
- 當開啟時,主節點會合并較小的 TCP 資料包進而節省帶寬。預設發送時間間隔取決于 Linux的核心,一般預設為 40毫秒。這種配置節省了帶寬但增大主從之間的延遲。适用于主從網絡環境複雜或帶寬緊張的場景,如跨機房部署。
部署主從節點時需要考慮網絡延遲、帶寬使用率、防災級别等因素,
- 如要求低延遲時,建議同機架或同機房部署并關閉 repl-disable-tcp-nodelay;
- 如果考慮高容災性,可以同城跨機房部署并開啟 repl-disable-tcp-nodelay。
2. 拓撲
Redis 的複制拓撲結構可以支援單層或多層複制關系,根據拓撲複雜性可以分為以下三種:一主一從、一主多從、樹狀主從結構。
2.1 一主一從結構
一主一從結構是最簡單的複制拓撲結構,用于主節點出現當機時從節點提供故障轉移支援,
- 當應用寫指令并發量較高且需要持久化時,可以隻在從節點上開啟AOF,這樣既保證資料安全性同時也避免了持久化對主節點的性能幹擾;
- 但需要注意的是,當主節點關閉持久化功能時, 如果主節點脫機要避免自動重新開機操作。因為主節點之前沒有開啟持久化功能自動重新開機後資料集為空,這時從節點如果繼續複制主節點會導緻從節點資料也被清空的情況,喪失了持久化的意義;
- 安全的做法是在從節點上執行 slaveof no one 斷開與主節點的複制關系,再重新開機主節點進而避免這一問題。
2.2 一主多從結構
一主多從結構 (又稱為星形拓撲結構) 使得應用端可以利用多個從節點實作讀寫分離,
- 對于讀占比較大的場景,可以把讀指令發送到從節點來分擔主節點壓力;
- 同時在日常開發中如果需要執行一些比較耗時的讀指令,如:keys、sort等,可以在其中一台從節點上執行,防止慢查詢對主節點造成阻塞進而影響線上服務的穩定性;
- 對于寫并發量較高的場景,多個從節點會導緻主節點寫指令的多次發送進而過度消耗網絡帶寬,同時也加重了主節點的負載影響服務穩定性。
2.3 樹狀主從結構
樹狀主從結構 (又稱為樹狀拓撲結構) 使得從節點不但可以複制主節點資料,同時可以作為其他從節點的主節點繼續向下層複制
- 通過引入複制中間層,可以有效降低主節點負載和需要傳送給從節點的資料量。
3. 複制原理
3.1 複制過程
在從節點執行 slaveof 指令後,複制過程便開始運作,下面詳細介紹建立複制的完整流程,如圖所示:
- 儲存主節點(master)資訊:
- 8195;執行 slaveof 後從節點隻儲存主節點的位址資訊便直接傳回,這時建立複制流程還沒有開始。
- 從節點 (slave) 内部通過每秒運作的定時任務維護複制相關邏輯, 當定時任務發現存在新的主節點後,會嘗試與該節點建立網絡連接配接:
- 從節點會建立一個socket套接字,專門用于接受主節點發送的複制指令;
- 如果從節點無法建立連接配接,定時任務會無限重試直到連接配接成功或者執行 slaveof no one 取消複制;
- 發送 ping 指令:連接配接建立成功後從節點發送ping請求進行首次通信
- 如果發送 ping 指令後,從節點沒有收到主節點的 pong 回複或者逾時,比如網絡逾時或者主節點正在阻塞無法響應指令,從節點會斷開複制連接配接,下次定時任務會發起重連;
- 從節點發送的 ping 指令成功傳回,并繼續後續複制流程
- 權限驗證:
- 如果主節點設定了 requirepass 參數,則需要密碼驗證,從節點必須配置masterauth 參數保證與主節點相同的密碼才能通過驗證;
- 如果驗證失敗複制将終止,從節點重新發起複制流程
- 同步資料集:
- 主從複制連接配接正常通信後,對于首次建立複制的場景,主節點會把持有的資料全部發送給從節點,這部分操作是耗時最長的步驟;
- Redis 在 2.8版本以後采用新複制指令 psync 進行資料同步,原來的sync指令依然支援,保證新舊版本的相容性。新版同步劃分兩種情況:全量同步和 部分同步
- 指令持續複制:
- 當主節點把目前的資料同步給從節點後,便完成了複制的建立流程。接下來主節點會持續地把寫指令發送給從節點,保證主從資料一緻性。
3.2 資料同步
Redis 在 2.8及以上版本使用 psync 指令完成主從資料同步,同步過程分
為:全量複制和部分複制
- 全量複制:一般用于初次複制場景,Redis 早期支援的複制功能隻有全量複制,它會把主節點全部資料一次性發送給從節點,當資料量較大時,會對主從節點和網絡造成很大的開銷;
- 部分複制:用于處理在主從複制中因網絡閃斷等原因造成的資料丢失場景,當從節點再次連上主節點後,如果條件允許,主節點會補發丢失資料給從節點。因為補發的資料遠遠小于全量資料,可以有效避免全量複制的過高開銷。
psync 指令運作需要以下元件支援:
- 主從節點各自複制偏移量;
- 主節點複制積壓緩沖區;
- 主節點運作id。
-
複制偏移量
參與複制的主從節點都會維護自身複制偏移量。主節點 (master) 在處理完寫入指令後,會把指令的位元組長度做累加記錄,統計資訊在 info relication 中的 master_repl_offset 名額中
從節點 (slave) 每秒鐘上報自身的複制偏移量給主節點,是以主節點也會儲存從節點的複制偏移量。
2. 複制積壓緩沖區
複制積壓緩沖區是儲存在主節點上的一個固定長度的隊列,預設大小為 1MB,當主節點有連接配接的從節點 (slave) 時被建立,這時主節點 (master) 響應寫指令時,不但會把指令發送給從節點,還會寫入複制積壓緩沖區。
由于緩沖區本質上是先進先出的定長隊列,是以能實作儲存最近已複制資料的功能,用于部分複制和複制指令丢失的資料補救,複制緩沖區相關統計資訊儲存在主節點的 info replication 中
127.0.0.1:6379> info replication
# Replication
role:master
...
repl_backlog_active:1 // 開啟複制緩沖區
repl_backlog_size:1048576 // 緩沖區最大長度
repl_backlog_first_byte_offset:7479 // 起始偏移量,計算目前緩沖區可用範圍
repl_backlog_histlen:1048576 // 已儲存資料的有效長度。
-
主節點運作ID
每個 Redis 節點啟動後都會動态配置設定一個 40位的十六進制字元串作為運作 ID。運作 ID的主要作用是用來唯一識别 Redis節點,比如從節點儲存主節點的運作 ID識别自己正在複制的是哪個主節點。如果隻使用 ip+port 的方式識别主節點,那麼主節點重新開機變更了整體資料集 (如替換RDB/AOF檔案), 從節點再基于偏移量複制資料将是不安全的,是以當運作ID 變化後從節點将做全量複制。
需要注意的是Redis關閉再啟動後,運作ID會随之改變,如何在不改變運作ID的情況下重新開機呢?
- 可以使用 debug reload指令重新加載RDB并保持運作ID不變,進而有效避免不必要的全量複制。
- debug reload 指令會阻塞目前Redis節點主線程,阻塞期間會生成本地 RDB快照并清空資料之後再加載RDB檔案。是以對于大資料量的主節點和無 法容忍阻塞的應用場景,謹慎使用
-
psync指令
從節點使用 psync 指令完成部分複制和全量複制功能,指令格式:
psync {runId} {offset},參數含義如下:
- runId:從節點所複制主節點的運作id。
- offset:目前從節點已複制的資料偏移量
psync指令運作流程如圖:
流程說明:
- 從節點 (slave) 發送 psync 指令給主節點,參數 runId 是目前從節點儲存的主節點運作ID,如果沒有則預設值為空,參數 offset 是目前從節點儲存的複制偏移量,如果是第一次參與複制則預設值為 -1;
- 主節點 (master) 根據 psync 參數和自身資料情況決定響應結果:
- 如果回複 +FULLRESYNC {runId} {offset},那麼從節點将觸發全量複制;
- 如果回複 +CONTINUE,從節點将觸發部分複制流程;
- 如果回複 +ERR,說明主節點版本低于 Redis2.8,無法識别 psync 指令, 從節點将發送舊版的 sync 指令觸發全量複制流程。
3.3 全量複制
全量複制是 Redis 最早支援的複制方式,也是主從第一次建立複制時必須經曆的階段。觸發全量複制的指令是 sync 和 psync。
全量複制的完整運作流程如圖所示:
- 發送 psync 指令進行資料同步,由于是第一次進行複制,從節點沒有複制偏移量和主節點的運作ID,是以發送 psync-1;
- 主節點根據 psync-1 解析出目前為全量複制,回複 +FULLRESYNC 響應;
- 從節點接收主節點的響應資料儲存運作ID 和偏移量 offset;
- 主節點執行 bgsave 儲存 RDB 檔案到本地;
- 主節點發送 RDB 檔案給從節點,從節點把接收的 RDB 檔案儲存在本地并直接作為從節點的資料檔案;
需要注意,對于資料量較大的主節點,比如生成的 RDB 檔案超過 6GB 以上時要格外小心。傳輸檔案這一步操作非常耗時,速度取決于主從節點之間網絡帶寬;
如果傳輸總時間超過 repl-timeout 所配置的值 (預設60秒),從節點将放棄接受 RDB 檔案并清理已經下載下傳的臨時檔案,導緻全量複制失敗。
針對資料量較大的節點,建議調大 repl-timeout 參數防止出現全量同步資料逾時。
- 對于從節點開始接收 RDB 快照到接收完成期間,主節點仍然響應讀寫指令,是以主節點會把這期間寫指令資料儲存在複制用戶端緩沖區内,當從節點加載完 RDB 檔案後,主節點再把緩沖區内的資料發送給從節點,保證主從之間資料一緻性。如果主節點建立和傳輸 RDB 的時間過長,對于高流量寫入場景非常容易造成主節點複制用戶端緩沖區溢出。預設配置為 client- output-buffer-limit slave256MB 64MB 60,如果 60秒内緩沖區消耗持續大于 64MB 或者直接超過 256MB時,主節點将直接關閉複制用戶端連接配接,造成全量同步失敗。
- 從節點接收完主節點傳送來的全部資料後會清空自身舊資料。
- 從節點清空資料後開始加載 RDB 檔案,對于較大的 RDB 檔案,這一步操作依然比較耗時;
- 從節點成功加載完 RDB 後,如果目前節點開啟了 AOF 持久化功能, 它會立刻做bgrewriteaof 操作,為了保證全量複制後 AOF 持久化檔案立刻可用;
通過分析全量複制的所有流程,會發現全量複制是一個非常耗時費力的操作。它的時間開銷主要包括:
- 主節點 bgsave 時間;
- RDB檔案網絡傳輸時間。
- 從節點清空資料時間。
- 從節點加載RDB的時間。
- 可能的AOF重寫時間
3.4 部分複制
部分複制主要是 Redis 針對全量複制的過高開銷做出的一種優化措施, 使用psync{runId}{offset} 指令實作。當從節點 (slave) 正在複制主節點 (master) 時,如果出現網絡閃斷或者指令丢失等異常情況時,從節點會向 主節點要求補發丢失的指令資料,如果主節點的複制積壓緩沖區記憶體在這部 分資料則直接發送給從節點,這樣就可以保持主從節點複制的一緻性。補發 的這部分資料一般遠遠小于全量資料,是以開銷很小。
流程說明:
- 當主從節點之間網絡出現中斷時,如果超過 repl-timeout 時間,主節點會認為從節點故障并中斷複制連接配接;
- 主從連接配接中斷期間主節點依然響應指令,但因複制連接配接中斷指令無法發送給從節點,不過主節點内部存在的複制積壓緩沖區,依然可以儲存最近一段時間的寫指令資料,預設最大緩存1MB;
- 當主從節點網絡恢複後,從節點會再次連上主節點;
- 當主從連接配接恢複後,由于從節點之前儲存了自身已複制的偏移量和主節點的運作ID。是以會把它們當作 psync 參數發送給主節點,要求進行部分複制操作;
- 主節點接到 psync 指令後首先核對參數 runId 是否與自身一緻,如果一緻,說明之前複制的是目前主節點;之後根據參數 offset 在自身複制積壓緩沖區查找,如果偏移量之後的資料存在緩沖區中,則對從節點發送 +CONTINUE 響應,表示可以進行部分複制;
- 主節點根據偏移量把複制積壓緩沖區裡的資料發送給從節點,保證主從複制進入正常狀态;