主從復制概念與原理
核心概念
主節點(Master):唯一接受寫操作的節點,數據修改后異步復制到從節點。
從節點(Replica):復制主節點數據的節點,默認只讀(可配置為可寫但不推薦)。
所以,主從復制,是指將一臺Redis服務器的數據,復制到其他的Redis服務器。前者稱為主節點(master),后者稱為從節點(slave);數據的復制是單向的,只能由主節點到從節點。
主要價值
主從復制主要有能實現如下價值:
數據冗余:主從復制實現了數據的熱備份,是持久化之外的一種數據冗余方式。
故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗余。
負載均衡:在主從復制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis數據時應用連接主節點,讀Redis數據時應用連接從節點),分擔服務器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis服務器的并發量。
高可用基石:除了上述作用以外,主從復制還是哨兵和集群能夠實施的基礎,因此說主從復制是Redis高可用的基礎。
主從庫之間采用的是讀寫分離的方式,即:
讀操作:主庫、從庫都可以接收;
寫操作:首先到主庫執行,然后,主庫將寫操作同步給從庫
復制流程
- 從節點連接主節點發送 PSYNC 命令
- 主節點執行 BGSAVE 生成 RDB 快照
- RDB 文件傳輸到從節點
- 從節點加載 RDB 文件
- 主節點將期間的寫命令發送到從節點
- 持續增量同步
關鍵特性:
異步復制:主節點不等待從節點確認
全量復制:首次連接或復制中斷時間過長時觸發
部分復制:基于復制偏移量(offset)的增量同步
級聯復制:從節點可以作為其他節點的主節點
復制原理
- 確立主從關系
如有 redis01 (172.24.8.11)實例和 redis02 (172.24.8.12),在 redis02 上執行以下這個命令后,redis02 就變成了 redis01 的從庫,并從 redis01 上復制數據。
replicaof 172.24.8.11 6379
- 全量復制階段
第一階段:主從庫間建立連接、協商同步的過程,主要是為全量復制做準備。在這一步,從庫和主庫建立起連接,并告訴主庫即將進行同步,主庫確認回復后,主從庫間就可以開始同步了。
具體來說,從庫給主庫發送 psync 命令,表示要進行數據同步,主庫根據這個命令的參數來啟動復制。
psync 命令包含了主庫的 runID 和復制進度 offset 兩個參數。
runID 是每個 Redis 實例啟動時都會自動生成的一個隨機 ID,用來唯一標記這個實例。
當從庫和主庫第一次復制時,因為不知道主庫的 runID,所以將 runID 設為“?”。
offset,此時設為 -1,表示第一次復制。
主庫收到 psync 命令后,會用 FULLRESYNC 響應命令帶上兩個參數:主庫 runID 和主庫目前的復制進度 offset,返回給從庫。
從庫收到響應后,會記錄下這兩個參數。這里有個地方需要注意,FULLRESYNC 響應表示第一次復制采用的全量復制,也就是說,主庫會把當前所有的數據都復制給從庫。
第二階段,主庫將所有數據同步給從庫。從庫收到數據后,在本地完成數據加載。這個過程依賴于內存快照生成的 RDB 文件。
具體來說,主庫執行 bgsave 命令,生成 RDB 文件,接著將文件發給從庫。從庫接收到 RDB 文件后,會先清空當前數據庫,然后加載 RDB 文件。
這是因為從庫在通過 replicaof 命令開始和主庫同步前,可能保存了其他數據。
為了避免之前數據的影響,從庫需要先把當前數據庫清空。
在主庫將數據同步給從庫的過程中,主庫不會被阻塞,仍然可以正常接收請求。否則,Redis 的服務就被中斷了。
但是,這些請求中的寫操作并沒有記錄到剛剛生成的 RDB 文件中。為了保證主從庫的數據一致性,主庫會在內存中用專門的 replication buffer,記錄 RDB 文件生成后收到的所有寫操作。
第三個階段,主庫會把第二階段執行過程中新收到的寫命令,再發送給從庫。
具體的操作是,當主庫完成 RDB 文件發送后,就會把此時 replication buffer 中的修改操作發給從庫,從庫再重新執行這些操作。這樣一來,主從庫就實現同步了。
- 增量復制
如果主從庫在命令傳播時出現了網絡閃斷,那么,從庫就會和主庫重新進行一次全量復制,開銷非常大。因此如果出現類似異常之后,主從庫會采用增量復制的方式繼續同步。
提示:在進行主從復制設置時,強烈建議在主服務器上開啟持久化,主要是避免出現如下風險:
假設設置節點A為主服務器,關閉持久化,節點B和C從節點A復制數據。
這時出現了一個崩潰,但Redis具有自動重啟系統,重啟了進程,因為關閉了持久化,節點重啟后只有一個空的數據集。
節點B和C從節點A進行復制,現在節點A是空的,所以節點B和C上的復制數據也會被刪除。
讀寫分離概念與原理
讀寫分離說明
讀寫分離是在主從復制基礎上實現的,可以實現Redis的讀負載均衡:由主節點提供寫服務,由一個或多個從節點提供讀服務(多個從節點既可以提高數據冗余程度,也可以最大化讀負載能力);在讀負載較大的應用場景下,可以大大提高Redis服務器的并發量。
讀寫分離關注點
- 延遲與不一致問題
由于主從復制的命令傳播是異步的,延遲與數據的不一致不可避免。如果應用對數據不一致的接受程度程度較低,可能的優化措施包括:優化主從節點之間的網絡環境(如在同機房部署);監控主從節點延遲(通過offset)判斷,如果從節點延遲過大,通知應用不再通過該從節點讀取數據;使用集群同時擴展寫負載和讀負載等。
- 數據過期問題
在單機版Redis中,存在兩種刪除策略:
+ 惰性刪除:服務器不會主動刪除數據,只有當客戶端查詢某個數據時,服務器判斷該數據是否過期,如果過期則刪除。
+ 定期刪除:服務器執行定時任務刪除過期數據,但是考慮到內存和CPU的折中(刪除會釋放內存,但是頻繁的刪除操作對CPU不友好),該刪除的頻率和執行時間都受到了限制。
在主從復制場景下,為了主從節點的數據一致性,從節點不會主動刪除數據,而是由主節點控制從節點中過期數據的刪除。由于主節點的惰性刪除和定期刪除策略,都不能保證主節點及時對過期數據執行刪除操作,因此,當客戶端通過Redis從節點讀取數據時,很容易讀取到已經過期的數據。
提示:在Redis 3.2后,從節點在讀取數據時,增加了對數據是否過期的判斷:如果該數據已過期,則不返回給客戶端。
- 故障切換問題
在沒有使用哨兵的讀寫分離場景下,應用針對讀和寫分別連接不同的Redis節點;當主節點或從節點出現問題而發生更改時,需要及時修改應用程序讀寫Redis數據的連接;連接的切換可以手動進行,或者自己寫監控程序進行切換,但前者響應慢、容易出錯,后者實現復雜,成本較高。
- 總結
在使用讀寫分離之前,可以考慮其他方法增加Redis的讀負載能力:如盡量優化主節點(減少慢查詢、減少持久化等其他情況帶來的阻塞等)提高負載能力;使用Redis集群同時提高讀負載能力和寫負載能力等。如果使用讀寫分離,可以使用哨兵,使主從節點的故障切換盡可能自動化,并減少對應用程序的侵入。
參考: Redis進階 - 高可用:主從復制詳解
主從復制架構實踐
安全預設
根據實際情況,關閉防火墻和SELinux。
[root@redis01 ~]# systemctl disable firewalld --now
[root@redis01 ~]# sed -i 's/=enforcing/=disabled/g' /etc/selinux/config
注意:以上操作在所有Slave主機上也需要執行。
時鐘服務
主從架構建議保證時鐘服務正確,具體時鐘配置參考:NTP服務器
001.Chrony時間服務器
- ntpd服務查看
[root@redis01 ~]# ntpq -npremote refid st t when poll reach delay offset jitter
==============================================================================
*116.62.13.223 100.100.61.92 2 u 28 64 377 34.750 24.098 4.261
+203.107.6.88 100.107.25.114 2 u 13 64 377 47.745 18.287 8.121
- chrony服務查看
[root@redis01 ~]# chronyc sources -v
Redis 安裝
略,參考《001.Redis 簡介及安裝》。
提示:主從節點都需要安裝Redis。
Redis 主節點配置
[root@redis01 ~]# mkdir -p /var/lib/redis/redis01/ /var/log/redis/ #創建Redis RDB持久化文件保存路徑[root@redis01 ~]# vim /etc/redis/redis01_6379.conf
#……
# 核心配置項:
bind 0.0.0.0 -::1 # 允許所有IP連接
protected-mode no # 關閉保護模式
port 6379
daemonize yes
logfile /var/log/redis/redis01.log
dir /var/lib/redis/redis01# 主從復制相關
repl-backlog-size 64mb # 復制積壓緩沖區大小(建議1-2倍內存)
repl-backlog-ttl 3600 # 緩沖區保留時間(秒)
repl-diskless-sync yes # 啟用無盤復制(推薦)
repl-diskless-sync-delay 5 # 等待更多從節點加入(秒)
#requirepass "StrongPassword123!" [root@redis01 ~]# systemctl restart redis-server
Redis 從節點配置
[root@redis02 ~]# mkdir -p /var/lib/redis/redis01/ /var/log/redis/ #創建Redis RDB持久化文件保存路徑[root@redis02 ~]# vim /etc/redis/redis01_6379.conf
#……
# 核心配置項:
bind 0.0.0.0 -::1 # 允許所有IP連接
protected-mode no # 關閉保護模式
port 6379
daemonize yes
logfile /var/log/redis/redis01.log
dir /var/lib/redis/redis01# 主從復制配置
replicaof 172.24.8.11 6379 # 關鍵配置:指定主節點
replica-read-only yes # 默認,從節點只讀(推薦)
repl-diskless-load disabled # 默認,從節點禁用無盤加載(避免風險)
#masterauth "StrongPassword123!" # 如果主節點有密碼需配置[root@redis02 ~]# systemctl restart redis-server
主從復制驗證
- 狀態驗證
在主從節點上查看兩個節點的狀態。
[root@redisclient ~]# redis-cli -h redis01 -p 6379
redis01:6379> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.24.8.12,port=6379,state=online,offset=306,lag=0
master_failover_state:no-failover
master_replid:a8f80c6ab90acb062021f2f0e589191c165022c1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:306
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:67108864
repl_backlog_first_byte_offset:223
repl_backlog_histlen:84
redis01:6379> exit[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> INFO replication
# Replication
role:slave
master_host:172.24.8.11
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_read_repl_offset:320
slave_repl_offset:320
replica_full_sync_buffer_size:0
replica_full_sync_buffer_peak:0
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:a8f80c6ab90acb062021f2f0e589191c165022c1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:320
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:237
repl_backlog_histlen:84
- 數據驗證
[root@redisclient ~]# redis-cli -h redis01 -p 6379
redis01:6379> SET master_key "value_from_master"
OK
redis01:6379> exit[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> GET master_key
"value_from_master"
redis02:6379> exit
模擬宕機
- 停止主節點
[root@redis01 ~]# systemctl stop redis-server
- 2從節點查看狀態
[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> INFO replication
master_link_status:down # 顯示連接斷開
- 提升從節點為主節點(臨時方案)
[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> REPLICAOF NO ONE # 取消復制關系
節點切換后,對應的應用程序也需要修改數據庫IP。
提示;主從復制本身不提供自動故障切換,需要手動執行切換,然后程序需要手動重新指定IP。