1. 程式人生 > 程式設計 >Redis主從複製的配置和實現原理

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.21192.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代替了slaveofgithub.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 中被刪除:

  1. 我們設定節點 A 為 master 並關閉它的持久化設定,節點 B 和 C 從 節點 A 複製資料。
  2. 節點 A 崩潰,但是他有一些自動重啟的系統可以重啟程式。但是由於持久化被關閉了,節點重啟後其資料集合為空。
  3. 節點 B 和 節點 C 會從節點 A 複製資料,但是節點 A 的資料集是空的,因此複製的結果是它們會銷燬自身之前的資料副本。

當 Redis Sentinel 被用於高可用並且 master 關閉持久化,這時如果允許自動重啟程式也是很危險的。例如, master 可以重啟的足夠快以致於 Sentinel 沒有探測到故障,因此上述的故障模式也會發生。

任何時候資料安全性都是很重要的,所以如果 master 使用複製功能的同時未配置持久化,那麼自動重啟程式這項應該被禁用

參考:《Redis設計與實現》、redis replication