一、環境概述
在分布式集群系統中為了解決服務單點故障問題,通常會把數據復制出多個副本部署到不同的機器中,滿足故障恢復和負載均衡等需求。Redis也是如此,它為我們提供了復制功能,實現了相同數據的多個Redis副本。復制功能是高可用Redis的基礎,Redis的哨兵和集群(Cluster)模式都是在主從復制模式的基礎上實現的。復制也是Redis日常運維的常見維護點。因此深刻理解復制的工作原理與使用技巧對日常的運維非常有幫助。
二、Redis主從結構
2.1、一主一從結構
一主一從結構是Redis最簡單的復制拓撲結構,用于主節點出現宕機時從節點來提供故障轉移支持。當應用寫命令并發量較高且需要持久化時,可以只在從節點上開啟AOF,這樣既保證數據安全性同時,也避免了持久化對主節點的性能壓力。
讀寫流向與節點角色說明:
┌─────────────┐ ┌─────────────┐
│ Master │───────?│ Slave │
│ (主節點) │ 數據同步 │ (從節點) │
└─────────────┘ └─────────────┘│ ▲├─ 處理寫請求 ├─ 處理讀請求├─ 不開啟持久化 ├─ 開啟AOF持久化└─ 接收客戶端寫操作 └─ 提供數據備份
2.2、一主多從結構
一主多從結構(又稱為星型拓撲結構)使得應用端可以利用多個從節點實現讀寫分離方案。對于讀占比較大的場景,可以把讀命令發送到多個從節點來分擔主節點壓力。同時在日常開發中如果需要執行一些比較耗時的讀命令,可以在其中一臺從節點上執行,防止慢查詢對主節點造成阻塞從而影響線上服務的穩定性。對于寫并發量較高的場景,多個從節點會導致主節點寫命令的多次發送從而過度消耗網絡帶寬,同時也加重了主節點的負載影響服務穩定性。
讀寫流向與節點角色分布:┌─────────────┐│ Master ││ (主節點) │└──────┬──────┘│ 寫請求 ↑ 數據同步┌─────────────────┴───────┼─────────────────┐│ │ │
┌─────────┴────────┐ ┌─────────┴────────┐ ┌─────────┴────────┐
│ Slave1 │ │ Slave2 │ │ Slave3 │
│ (從節點-只讀) │?─────┤ (從節點-只讀) │?─────┤ (從節點-只讀) │
└─────────┬────────┘ └─────────┬────────┘ └─────────┬────────┘│ 讀請求 讀請求 │ 讀請求 │▼ ▼ ▼客戶端群組1 客戶端群組2 客戶端群組3
2.3、樹狀主從結構
樹狀結構(級聯結構)使得從節點不但可以復制主節點數據,同時可以作為其他從節點的主節點繼續向下層復制。通過引入中間復制層,可以有效降低主節點負載和需要傳送給從節點的數據量。當主節點需要掛載多個從節點時為了避免對主節點的性能干擾,可以采用樹狀主從結構降低主節點壓力。
樹狀主從結構拓撲圖(級聯復制):┌─────────────┐│ Master ││ (主節點) │└──────┬──────┘│ 寫請求 ↑ 數據同步┌─────────────▼─────────────┐│ Intermediate Slave1 ││ (中間層從節點-兼主節點) ││ ? 復制主節點數據 ││ ? 作為子節點的主節點 │└───────┬────────────┬──────┘│ 數據同步 │ 數據同步┌─────────────▼─┐ ┌─▼─────────────┐│ Leaf Slave1 │ │ Leaf Slave2 ││ (葉子從節點) │ │ (葉子從節點) │└───────────────┘ └───────────────┘
三、主從復制原理
3.1、復制流程
在從節點執行slaveof命令后,復制過程便開始運作,大致分為6個過程:
1.保存主節點信息
執行slaveof命令后從節點只保存主節點的地址信息便直接返回,這時建立復制流程還沒有開始,在從節點執行info replication命令可以看到主節點的相關信息,并在日志中記錄復制啟動信息。2.主從建立socket連接
從節點內部通過每秒運行的定時任務維護復制相關邏輯,當定時任務發現存在新的主節點后,會嘗試與該節點建立網絡連接。
從節點會建立一個socket套接字連接,專門用于接收主節點發送的復制命令。如果從節點無法建立連接,定時任務會無限重試,直到連接成功或者執行slaveof no one命令取消復制。3.發送ping命令
連接建立成功后,從節點發送ping請求進行首次通信,目的在于:
檢測主從之間網絡套接字是否可用。
檢測主節點當前是否可接受處理命令。
如果發送ping命令后,從節點沒有收到主節點回復pong或者超時未回復,從節點會斷開復制連接,等待下次定時任務發起重連。4.權限驗證
如果主節點設置了requirepass參數,則需要密碼驗證,從節點必須配置masterauth參數保證與主節點相同的密碼才能通過驗證。如果驗證失敗復制將終止,從節點重新發起復制流程。5.同步數據集
主從復制連接正常通信后,對于首次建立復制的場景,主節點會把所有的數據全部發送給從節點,這部分操作是耗時最長的步驟。在同步過程中會分為兩種情況:全量同步和部分同步。6.命令持續復制
當主節點把當前的數據同步給從節點后,便完成了復制建立的流程。接下來主節點會持續地把寫命令發送給從節點,保證數據的一致性。
# Redis主從復制六步流程圖解
┌─────────────┐ ┌─────────────┐
│ Master │ │ Slave │
└──────┬──────┘ └──────┬──────┘│ 1. 從節點保存主節點信息 ││ ? 執行`slaveof <master_ip> <port>` ││ ? 記錄主節點地址到內存(未建立連接) ││<───────────────────────────────────────────┤│ 2. 建立Socket連接 ││ ? 從節點定時任務發起TCP連接(默認1秒/次) ││ ? 專用socket通道用于復制 ││<─┐ ││ ├─連接失敗則無限重試(除非`slaveof no one`) │├─┘ ││ 3. 發送Ping命令 ││ ? 檢測網絡可用性 ││ ? 驗證主節點響應能力 ││<─┐ ││ ├─未收到Pong響應則斷開連接并重試 │├─┘ ││ 4. 權限驗證 ││ ? 主節點`requirepass` vs 從節點`masterauth`││<─┐ ││ ├─密碼不匹配則終止流程并記錄錯誤日志 │├─┘ ││ 5. 數據同步(核心階段) ││ ├─場景1:全量同步(首次復制) ││ │ a) 主節點執行`BGSAVE`生成RDB ││ │ b) 傳輸RDB文件(清空從節點舊數據) ││ │ c) 發送復制緩沖區積壓命令 ││ ├─場景2:部分同步(斷線重連) ││ │ a) 從節點發送`PSYNC <replid> <offset>`││ │ b) 主節點校驗復制積壓緩沖區(repl_backlog)││ │ c) 發送offset后的增量命令 ││<───────────────────────────────────────────┤│ 6. 命令持續復制 ││ ? 主節點實時推送寫命令(如SET/DEL) ││ ? 異步傳輸:主節點本地執行后立即返回 ││ ? 心跳機制: ││ - 主從互發`PING/PONG`(默認10秒/次) ││ - 從節點上報偏移量(`REPLCONF ACK <offset>`)│└───────────────────────────────────────────?│
3.2、復制類型
Redis在2.8及以上版本使用psync命令完成主從數據同步,過程分為:全量復制和部分復制。全量復制:一般用于初次復制的場景,Redis早期支持的復制功能只有全量復制,他會把主節點全部數據一次性發送給從節點,當數據量較大時,會對主從節點和網絡造成很大的開銷。部分復制:用于處理在主從復制中因網絡閃斷等原因造成的數據丟失場景,當從節點再次連上主節點后,如果條件允許,主節點會補發丟失數據給從節點。而補發的數據量遠遠小于全量數據,可以有效避免全量復制的過高開銷。
部分復制是對老版復制的重大優化,有效避免了不必要的全量復制操作。因此當使用復制功能時,盡量采用2.8以上版本的Redis。psync命令運行時需要以下組件的支持:1.復制偏移量
參與復制的主從節點都會維護自身復制偏移量。主節點在處理完寫入命令后,會把命令的字節長度做累加記錄,統計信息在info replication中的master_repl_offset指標中。
從節點每秒鐘上報自身的復制偏移量給主節點,因此主節點也會保存從節點復制偏移量。并且從節點在收到主節點發送的命令后,也會累加記錄自身的偏移量,記錄在自己的統計信息中。2.復制積壓緩沖區
復制積壓緩沖區是保存在主節點上的一個固定長度的隊列,默認大小為1MB,當主節點進行主從復制時,寫命令不但會發送給從節點,還會寫入復制積壓緩沖區中。
緩沖區本質上是一個先進先出的定長隊列,可以實現保存最近已復制數據的功能,用于部分復制和復制命令丟失的數據補救。統計信息記錄與info replication中repl_backlog_active:1 //開啟復制緩沖區repl_backlog_size:1048576 //緩沖區最大大小repl_backlog_first_byte_offset:7479 //起始偏移量repl_backlog_histlen:1048576 //已保存數據的有效長度
3.主節點運行ID每個Redis節點啟動后,都會動態分配一個運行ID,用來唯一識別一個Redis節點。從節點通過保存主節點的運行ID,來判斷自己正在向誰進行復制,當主的運行ID改變后,從節點將進行全量復制。注意:Redis重啟時,運行ID會隨之改變
若不希望從節點對主節點進行全量復制,則需要保持主節點ID不變,而當修改配置文件需要重啟操作時,可以對主節點使用redis-cli debug reload命令來進行對配置文件的重新加載,而不用關閉Redis。但是,debug reload命令會阻塞Redis主進程,阻塞期間會生成本地RDB快照并清空數據之后再加載RDB文件。因此對于無法容忍阻塞的場景,謹慎使用
4.psync命令命令格式:psync { runID } { offset }runID:主節點的運行id。offset:當前從節點已復制的數據偏移量。
流程說明:從節點發送psync命令給主節點,如果沒有runID,默認值為?,如果是第一次復制,則offset默認值為-1.主節點根據psync參數和自身數據情況決定響應結果。如果回復+FULLRESYNC {runID} {offset},從節點開始全量復制。如果回復+CONTINUE,從節點開始部分復制。如果回復+ERR,說明主節點版本低于2.8,不支持psync,從節點會發送sync命令進行全量復制。
3.2.1、全量復制
# Redis全量復制流程結構圖
┌─────────────┐ ┌─────────────┐
│ Master │ │ Slave │
└──────┬──────┘ └──────┬──────┘│ 1. 從節點發起同步請求 ││ ? 發送`PSYNC ? -1`(首次復制)[1,4,5](@ref)││<─────────────────────────────────────────────┤│ 2. 主節點響應全量復制 ││ ? 返回`+FULLRESYNC <runid> <offset>`[1,4](@ref)│├─────────────────────────────────────────────>││ 3. 主節點執行BGSAVE ││ ? fork子進程生成RDB快照[1,2,7](@ref) ││ ? 同步期間寫命令存入復制緩沖區[1,5,7](@ref) ││<─┐ ││ ├─若超時(默認60秒)則復制失敗[2,7](@ref) │├─┘ ││ 4. 傳輸RDB文件 ││ ? 網絡傳輸(或啟用無盤復制)[5,7](@ref) ││<─┐ ││ ├─大文件易超時(>6GB需警惕)[2,7](@ref) │├─┘ ││ 5. 發送復制緩沖區命令 ││ ? 補發RDB生成期間的寫操作[1,4,5](@ref) ││<─┐ ││ ├─緩沖區溢出會導致復制失敗[2](@ref) ││ │ (需調整`client-output-buffer-limit`[2,3](@ref))├─┘ ││ 6. 從節點加載數據 ││ ? 清空舊數據 → 加載RDB[1,4,7](@ref) ││ ? 若開啟AOF則觸發重寫[1,2](@ref) ││<─┐ ││ ├─阻塞期間拒絕讀請求(需配置`slave-serve-stale-data`)[2,3](@ref)└─┘ │
全量復制流程說明:從節點發送psync命令給主節點,psync ? -1主節點根據命令解析出當前進行全量復制,回復+FULLRESYNC相應。從節點接收主節點的相應數據保存運行ID和偏移量offset。主節點執行bgsave保存RDB文件(持久化)到本地。主節點發送RDB文件給從節點,從節點把接收的RDB文件保存在本地并直接作為從節點的數據文件。
注意:
對于數據量較大的主節點,生成的RDB文件超過6GB以上時要格外小心。如果傳輸時間超過repl-timeout所配置的值(默認60秒),從節點將放棄接受RDB文件并清理已經下載的臨時文件,導致全量復制失敗。從節點開始接收RDB文件到完成期間,主節點仍然響應讀寫命令,所以在RDB文件傳輸期間,主節點會將修改操作保存在緩沖區中。當從節點加載完RDB文件后,主節點再把緩沖區中的數據發送給從節點,保證數據一致。注意:高流量寫入場景可能會導致主節點復制客戶端緩沖區溢出,默認配置為client-output-buffer-limit slave 256MB 64MB 60,如果60秒內緩沖區消耗持續大于64MB或者直接超過256MB時,主節點將直接關閉復制客戶端連接,造成復制失敗。對于主節點,當發送完所有的數據后就認為全量復制完成,但是對于從節點全量復制還有后續步驟要處理。1、從節點接收完主節點傳送來的全部數據后,會清空自身舊數據。2、加載RDB文件。3、加載完畢后,如果當前節點開啟了AOF持久化功能,會立刻進行bgrewriteaof操作,為了保證全量復制后AOF持久化文件可立刻可用。
3.2.2、部分復制
當主從復制過程中,如果出現網絡閃斷或者命令丟失等異常情況時,從節點會向主節點要求補發丟失的命令數據,如果主節點的復制積壓緩沖區內存在這部分數據則會直接發給從節點。在減少開銷的同時,保證了數據的一致性。流程如下:
部分復制流程說明:當主從網絡斷開,超過repl-timeout設置的時間,主會認為從故障而中斷數據連接。連接中斷期間,主節點依然會響應客戶端命令,但無法發送給從節點,不過主節點內部存在復制加壓緩沖區,所以可以保存最近一段時間的寫命令數據,默認最大1MB。當網絡恢復,從節點會重新連接到主節點。連接恢復后,從節點會將之前保存的自己已復制的偏移量和運行ID用psync命令發送給主節點,要求進行部分復制操作。主節點接到psync命令后,核對信息。之后根據offset在自身復制積壓緩沖區查找,如果偏移量之后的數據存在緩沖區中,則對從節點發送+CONTINUE響應,表示可以進行部分復制。主節點根據偏移量把復制積壓緩沖區里的數據發給從節點,保證主從復制進入正常狀態。
3.3、心跳
主從節點在建立復制后,他們之間維護著長連接并彼此發送心跳命令。
心跳的關鍵機制如下
# Redis主從心跳交互流程
┌─────────────┐ ┌─────────────┐
│ Master │ │ Slave │
└──────┬──────┘ └──────┬──────┘│ 1. 主節點每10秒發送PING ││ ? 檢測從節點存活性 ││──────────────────────────────────────────────────>││ 2. 從節點回復PONG ││ ? 確認連接正常 ││<──────────────────────────────────────────────────┤│ 3. 從節點每1秒發送REPLCONF ACK <offset> ││ ? 上報當前復制偏移量 ││ ? 主節點校驗數據連續性 ││──────────────────────────────────────────────────>││ 4. 主節點響應處理 ││ ├─ 若偏移量不連續:重發丟失命令 ││ ├─ 若超時(>60秒):斷開連接并標記下線 ││ └─ 更新lag值(INFO replication) ││<──────────────────────────────────────────────────┤
1、主從都有心跳檢測機制,各自模擬成對方的客戶端進行通信,通過 client list 命令查看復制相關客戶端信息,主節點的連接狀態為 flags = M,從節點的連接狀態是 flags = S。2、主節點默認每隔 10 秒對從節點發送 ping 命令,可修改配置repl-ping-slave-period 控制發送頻率。3、從節點在主線程每隔一秒發送 replconf ack{offset}命令,給主節點上報自身當前的復制偏移量。4、主節點收到 replconf 信息后,判斷從節點超時時間,如果超過repl-timeout 60 秒,則判斷節點下線。注意:為了降低主從延遲,一般把 redis主從節點部署在相同的機房/同城機房,避免網絡延遲帶來的網絡分區造成的心跳中斷等情況
四、實驗
一主一從:
修改192.168.40.159的配置文件
輸入主的IP和端口號指定192.168.72.163主機為主
192.168.72.164設置為從
從? 設置密碼
關閉安全模式
啟動redis
[root@ding ~]# systemctl start redis
從登錄redis 驗證數據同步
[root@slave1 ~]# redis-cli -h 192.168.40.159
192.168.40.159:6379> keys *
1) "e"
2) "fff"
3) "rrr"###切換到主,數據庫數據已經復制到從上!
[root@master ~]# redis-cli -h 192.168.40.160 -a 123.com
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.40.160:6379> keys *
1) "e"
2) "fff"
3) "rrr"###主創建新鍵值對
192.168.40.159:6379> set ffff 1111
OK###從數據同步
192.168.40.160:6379> keys *
1) "ffff"
2) "e"
3) "fff"
4) "rrr"
###從 查看日志可以查看到主從復制的流程!
[root@slave1 ~]# cd /var/log/redis/
[root@slave1 redis]# ls
redis.log
[root@slave1 redis]# cat redis.log
此時在從上修改密碼
從重啟服務
[root@slave1 ~]# systemctl restart redis
此時從再查看redis日志,開始報錯顯示認證錯誤過程
[root@slave1 ~]# tail -f /var/log/redis/redis.log