Redis主從複製的配置和實現原理
Redis的持久化功能在一定程度上保證了資料的安全性,即便是伺服器宕機的情況下,也可以保證資料的丟失非常少。通常,為了避免服務的單點故障,會把資料複製到多個副本放在不同的伺服器上,且這些擁有資料副本的伺服器可以用於處理客戶端的讀請求,擴充套件整體的效能
下面會介紹Redis的主從複製配置和實現原理,後續還會有Redis的高可用方案:哨兵機制(Sentinel)、分割槽叢集(Cluster)
什麼是主從複製
我們可以通過slaveof <host> <port>
命令,或者通過配置slaveof
選項,來使當前的伺服器(slave)複製指定伺服器(master)的內容,被複制的伺服器稱為主伺服器(master),對主伺服器進行復制操作的為從伺服器(slave)
主伺服器master可以進行讀寫操作,當主伺服器的資料發生變化,master會發出命令流來保持對salve的更新,而從伺服器slave通常是隻讀的(可以通過slave-read-only
指定),在主從複製模式下,即便master宕機了,slave是不能變為主伺服器進行寫操作的
一個master可以有多個slave,即一主多從;而slave也可以接受其他slave的連線,形成“主從鏈”層疊狀結構(cascading-like structure),自 Redis 4.0 起,所有的sub-slave也會從master收到完全一樣的複製流。如下圖
主從複製的好處:
- 資料冗餘,實現資料的熱備份
- 故障恢復,避免單點故障帶來的服務不可用
- 讀寫分離,負載均衡。主節點負載讀寫,從節點負責讀,提高伺服器併發量
- 高可用基礎,是哨兵機制和叢集實現的基礎
主從複製的配置
使用和配置主從複製是比較簡單的,在從伺服器slave的配置檔案中設定slaveof
選項,或者直接使用slaveof <masterip> <masterport>
命令
這裡我使用3臺虛擬機器器來搭建一下,主伺服器的ip為192.168.249.20
,兩個從伺服器的ip分別為192.168.249.21
和192.168.249.21
,埠號都為6379
,具體的配置如下
主伺服器並不需要額外多配置什麼,這裡我們先把三臺伺服器的都需要改的地方列一下
# 設定為後臺執行
daemonize yes
# 儲存pid的檔案,如果是在一臺機器搭建主從,需要區分一下
pidfile /var/run/redis_6379.pid
# 繫結的主機地址,這裡註釋掉,開放ip連線
#bind 127.0.0.1
# 指定日誌檔案
logfile "6379.log"
複製程式碼
在從伺服器中新增配置slaveof <masterport> <masterport>
選項,在5.0版本中使用了replicaof
代替了slaveof
(github.com/antirez/red…),slaveof
還可以繼續使用,不過建議使用replicaof
。如果是使用命令列來複制的話,重啟之後會無效
# replicaof <masterip> <masterport>
replicaof 192.168.249.20 6379
複製程式碼
配置好redis.conf
之後,我們分別啟動3臺伺服器,可以使用者命令info replication
檢視複製資訊
192.168.249.20:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.249.22,port=6379,state=online,offset=700,lag=0
slave1:ip=192.168.249.21,lag=0
master_replid:b80a4720c0001efb62940f5ad6abaf9cdaf7a813
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:700
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:700
192.168.249.21:6379> info replication
# Replication
role:slave
master_host:192.168.249.20
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:854
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:b80a4720c0001efb62940f5ad6abaf9cdaf7a813
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:854
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:57
repl_backlog_histlen:798
192.168.249.22:6379> info replication
# Replication
role:slave
master_host:192.168.249.20
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:854
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:b80a4720c0001efb62940f5ad6abaf9cdaf7a813
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:854
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:854
複製程式碼
接下來我們可以在主伺服器中寫入資料,然後可以在其他的從伺服器中讀取資料
192.168.249.20:6379> set test 'Hello World'
OK
192.168.249.21:6379> get test
"Hello World"
192.168.249.22:6379> get test
"Hello World"
複製程式碼
然後我們試著在從伺服器中寫入資料,會提示不能在只讀的從伺服器中寫入資料
192.168.249.21:6379> set test2 hello
(error) READONLY You can't write against a read only replica.
複製程式碼
如果我們需要slave對master的複製進行驗證,可以在master中配置
requirepass <password>
選項設定密碼那麼需要在從伺服器中使用該密碼,可以使用命令
config set masterauth <password>
,或者在配置檔案中設定masterauth <password>
主從複製的實現原理
主從複製的配置還是比較簡單的,下面來瞭解下主從複製的實現原理
Redis的主從複製過程大體上分3個階段:建立連線、資料同步、命令傳播
建立連線
這個階段主要是從伺服器發出slaveof
命令之後,與主伺服器如何建立連線,為資料同步做準備的過程。
1)在slaveof
命令執行之後,從伺服器根據設定的master的ip地址和埠,建立連向主伺服器的socket套接字連線,連線成功後,從伺服器會為這個套接字關聯一個專門的處理器,用於處理後續的複製工作
2)建立連線之後,從伺服器會向主伺服器傳送ping
命令,確認主伺服器是否可用,以及當前是否可用接受處理命令。如果收到主伺服器的pong
回覆說明是可用的,否則有可能是網路超時或主伺服器阻塞,從伺服器會斷開連線發起重連
3)身份驗證。如果主伺服器設定了requirepass
選項,那麼從伺服器必須配置masterauth
選項,且保證密碼一致才能通過驗證
4)身份驗證完成之後,從伺服器會傳送自己的監聽埠,主伺服器會儲存下來
192.168.249.20:6379> info replication
...
slave0:ip=192.168.249.22,lag=0
...
複製程式碼
資料同步
在主從伺服器建立連線確認各自身份之後,就開始資料同步,從伺服器向主伺服器傳送PSYNC
命令,執行同步操作,並把自己的資料庫狀態更新至主伺服器的資料庫狀態
Redis的主從同步分為:完整重同步(full resynchronization)和部分重同步(partial resynchronization)
完整重同步
有兩種情況下是完整重同步,一是slave連線上master第一次複製的時候;二是如果當主從斷線,重新連線複製的時候有可能是完整重同步,這個在後面說
下面是完整重同步的步驟
- 從伺服器連線主伺服器,傳送SYNC命令
- 主伺服器接收到SYNC命名後,開始執行
bgsave
命令生成RDB檔案並使用緩衝區記錄此後執行的所有寫命令 - 主伺服器
basave
執行完後,向所有從伺服器傳送快照檔案,並在傳送期間繼續記錄被執行的寫命令 - 從伺服器收到快照檔案後丟棄所有舊資料,載入收到的快照
- 主伺服器快照傳送完畢後開始向從伺服器傳送緩衝區中的寫命令
- 從伺服器完成對快照的載入,開始接收命令請求,並執行來自主伺服器緩衝區的寫命令
部分重同步
部分重同步是用於處理斷線後重複製的情況,先介紹幾個用於部分重同步的部分
-
runid
(replication ID),主伺服器執行id,Redis例項在啟動時,隨機生成一個長度40的唯一字串來標識當前節點 -
offset
,複製偏移量。主伺服器和從伺服器各自維護一個複製偏移量,記錄傳輸的位元組數。當主節點向從節點傳送N個位元組資料時,主節點的offset增加N,從節點收到主節點傳來的N個位元組資料時,從節點的offset增加N -
replication backlog buffer
,複製積壓緩衝區。是一個固定長度的FIFO佇列,大小由配置引數repl-backlog-size
指定,預設大小1MB。需要注意的是該緩衝區由master維護並且有且只有一個,所有slave共享此緩衝區,其作用在於備份最近主庫傳送給從庫的資料
當slave連線到master,會執行PSYNC <runid> <offset>
傳送記錄舊的master的runid
(replication ID)和偏移量offset
,這樣master能夠只傳送slave所缺的增量部分。但是如果master的複製積壓快取區沒有足夠的命令記錄,或者slave傳的runid
(replication ID)不對,就會進行完整重同步,即slave會獲得一個完整的資料集副本
PSYNC命令執行完整重同步和部分重同步的流程圖
命令傳播
當完成資料同步之後,主從伺服器的資料暫時達到一致狀態,當主伺服器執行了客戶端的寫命令之後,主從的資料便不再一致。為了能夠使主從伺服器的資料保持一致性,主伺服器會對從伺服器執行命令傳播操作,即每執行一個寫命令就會向從伺服器傳送同樣的寫命令
在命令傳播階段,從伺服器會預設以每秒一次的頻率向主伺服器傳送心跳檢測
REPLCONF ACK <replication_offset>
複製程式碼
其中replication_offset
是當前從伺服器的複製偏移量,該命令的作用有三個
- 檢測主從伺服器的網路連線狀態
- 輔助實現
min-slaves
選項 - 檢測命令丟失
關閉持久化時,複製的安全性
關於關閉持久化時,複製的安全性問題,我這裡摘入官網的一段描述www.redis.cn/topics/repl…
在使用Redis複製功能時的設定中,強烈建議在master和在slave中啟用持久化。當不可能啟用時,例如由於非常慢的磁碟效能而導致的延遲問題,應該配置例項來避免重置後自動重啟。
為了更好地理解為什麼關閉了持久化並配置了自動重啟的 master 是危險的,檢查以下故障模式,這些故障模式中資料會從 master 和所有 slave 中被刪除:
- 我們設定節點 A 為 master 並關閉它的持久化設定,節點 B 和 C 從 節點 A 複製資料。
- 節點 A 崩潰,但是他有一些自動重啟的系統可以重啟程式。但是由於持久化被關閉了,節點重啟後其資料集合為空。
- 節點 B 和 節點 C 會從節點 A 複製資料,但是節點 A 的資料集是空的,因此複製的結果是它們會銷燬自身之前的資料副本。
當 Redis Sentinel 被用於高可用並且 master 關閉持久化,這時如果允許自動重啟程式也是很危險的。例如, master 可以重啟的足夠快以致於 Sentinel 沒有探測到故障,因此上述的故障模式也會發生。
任何時候資料安全性都是很重要的,所以如果 master 使用複製功能的同時未配置持久化,那麼自動重啟程式這項應該被禁用
參考:《Redis設計與實現》、redis replication