一文掌握Redis主從複製、哨兵、Cluster三種叢集模式

空山新雨的技術空間發表於2020-03-19

在開發測試環境中,我們一般搭建Redis的單例項來應對開發測試需求,但是在生產環境,如果對可用性、可靠性要求較高,則需要引入Redis的叢集方案。雖然現在各大雲平臺有提供快取服務可以直接使用,但瞭解一下其背後的實現與原理總還是有些必要(比如面試), 本文就一起來學習一下Redis的幾種叢集方案。

Redis支援三種叢集方案

  • 主從複製模式
  • Sentinel(哨兵)模式
  • Cluster模式

主從複製模式

1. 基本原理

主從複製模式中包含一個主資料庫例項(master)與一個或多個從資料庫例項(slave),如下圖

redis-master-slave

客戶端可對主資料庫進行讀寫操作,對從資料庫進行讀操作,主資料庫寫入的資料會實時自動同步給從資料庫。

具體工作機制為:

  1. slave啟動後,向master傳送SYNC命令,master接收到SYNC命令後通過bgsave儲存快照(即上文所介紹的RDB持久化),並使用緩衝區記錄儲存快照這段時間內執行的寫命令
  2. master將儲存的快照檔案傳送給slave,並繼續記錄執行的寫命令
  3. slave接收到快照檔案後,載入快照檔案,載入資料
  4. master快照傳送完後開始向slave傳送緩衝區的寫命令,slave接收命令並執行,完成複製初始化
  5. 此後master每次執行一個寫命令都會同步傳送給slave,保持master與slave之間資料的一致性

2. 部署示例

本示例基於Redis 5.0.3版。

redis.conf的主要配置

###網路相關###
# bind 127.0.0.1 # 繫結監聽的網路卡IP,註釋掉或配置成0.0.0.0可使任意IP均可訪問
protected-mode no # 關閉保護模式,使用密碼訪問
port 6379  # 設定監聽埠,建議生產環境均使用自定義埠
timeout 30 # 客戶端連線空閒多久後斷開連線,單位秒,0表示禁用

###通用配置###
daemonize yes # 在後臺執行
pidfile /var/run/redis_6379.pid  # pid程式檔名
logfile /usr/local/redis/logs/redis.log # 日誌檔案的位置

###RDB持久化配置###
save 900 1 # 900s內至少一次寫操作則執行bgsave進行RDB持久化
save 300 10
save 60 10000 
# 如果禁用RDB持久化,可在這裡新增 save ""
rdbcompression yes #是否對RDB檔案進行壓縮,建議設定為no,以(磁碟)空間換(CPU)時間
dbfilename dump.rdb # RDB檔名稱
dir /usr/local/redis/datas # RDB檔案儲存路徑,AOF檔案也儲存在這裡

###AOF配置###
appendonly yes # 預設值是no,表示不使用AOF增量持久化的方式,使用RDB全量持久化的方式
appendfsync everysec # 可選值 always, everysec,no,建議設定為everysec

###設定密碼###
requirepass 123456 # 設定複雜一點的密碼
複製程式碼

部署主從複製模式只需稍微調整slave的配置,在redis.conf中新增

replicaof 127.0.0.1 6379 # master的ip,port
masterauth 123456 # master的密碼
replica-serve-stale-data no # 如果slave無法與master同步,設定成slave不可讀,方便監控指令碼發現問題
複製程式碼

本示例在單臺伺服器上配置master埠6379,兩個slave埠分別為7001,7002,啟動master,再啟動兩個slave

[root@dev-server-1 master-slave]# redis-server master.conf
[root@dev-server-1 master-slave]# redis-server slave1.conf
[root@dev-server-1 master-slave]# redis-server slave2.conf
複製程式碼

進入master資料庫,寫入一個資料,再進入一個slave資料庫,立即便可訪問剛才寫入master資料庫的資料。如下所示

[root@dev-server-1 master-slave]# redis-cli 
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set site blog.jboost
OK
127.0.0.1:6379> get site
"blog.jboost"
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7001,state=online,offset=13364738,lag=1
slave1:ip=127.0.0.1,port=7002,state=online,offset=13364738,lag=0
...
127.0.0.1:6379> exit

[root@dev-server-1 master-slave]# redis-cli -p 7001
127.0.0.1:7001> auth 123456
OK
127.0.0.1:7001> get site
"blog.jboost"
複製程式碼

執行info replication命令可以檢視連線該資料庫的其它庫的資訊,如上可看到有兩個slave連線到master

3. 主從複製的優缺點

優點:

  1. master能自動將資料同步到slave,可以進行讀寫分離,分擔master的讀壓力
  2. master、slave之間的同步是以非阻塞的方式進行的,同步期間,客戶端仍然可以提交查詢或更新請求

缺點:

  1. 不具備自動容錯與恢復功能,master或slave的當機都可能導致客戶端請求失敗,需要等待機器重啟或手動切換客戶端IP才能恢復
  2. master當機,如果當機前資料沒有同步完,則切換IP後會存在資料不一致的問題
  3. 難以支援線上擴容,Redis的容量受限於單機配置

Sentinel(哨兵)模式

1. 基本原理

哨兵模式基於主從複製模式,只是引入了哨兵來監控與自動處理故障。如圖

redis-sentinel

哨兵顧名思義,就是來為Redis叢集站哨的,一旦發現問題能做出相應的應對處理。其功能包括

  1. 監控master、slave是否正常執行
  2. 當master出現故障時,能自動將一個slave轉換為master(大哥掛了,選一個小弟上位)
  3. 多個哨兵可以監控同一個Redis,哨兵之間也會自動監控

哨兵模式的具體工作機制:

在配置檔案中通過 sentinel monitor <master-name> <ip> <redis-port> <quorum> 來定位master的IP、埠,一個哨兵可以監控多個master資料庫,只需要提供多個該配置項即可。哨兵啟動後,會與要監控的master建立兩條連線:

  1. 一條連線用來訂閱master的_sentinel_:hello頻道與獲取其他監控該master的哨兵節點資訊
  2. 另一條連線定期向master傳送INFO等命令獲取master本身的資訊

與master建立連線後,哨兵會執行三個操作:

  1. 定期(一般10s一次,當master被標記為主觀下線時,改為1s一次)向master和slave傳送INFO命令
  2. 定期向master和slave的_sentinel_:hello頻道傳送自己的資訊
  3. 定期(1s一次)向master、slave和其他哨兵傳送PING命令

傳送INFO命令可以獲取當前資料庫的相關資訊從而實現新節點的自動發現。所以說哨兵只需要配置master資料庫資訊就可以自動發現其slave資訊。獲取到slave資訊後,哨兵也會與slave建立兩條連線執行監控。通過INFO命令,哨兵可以獲取主從資料庫的最新資訊,並進行相應的操作,比如角色變更等。

接下來哨兵向主從資料庫的_sentinel_:hello頻道傳送資訊與同樣監控這些資料庫的哨兵共享自己的資訊,傳送內容為哨兵的ip埠、執行id、配置版本、master名字、master的ip埠還有master的配置版本。這些資訊有以下用處:

  1. 其他哨兵可以通過該資訊判斷髮送者是否是新發現的哨兵,如果是的話會建立一個到該哨兵的連線用於傳送PING命令。
  2. 其他哨兵通過該資訊可以判斷master的版本,如果該版本高於直接記錄的版本,將會更新
  3. 當實現了自動發現slave和其他哨兵節點後,哨兵就可以通過定期傳送PING命令定時監控這些資料庫和節點有沒有停止服務。

如果被PING的資料庫或者節點超時(通過 sentinel down-after-milliseconds master-name milliseconds 配置)未回覆,哨兵認為其主觀下線(sdown,s就是Subjectively —— 主觀地)。如果下線的是master,哨兵會向其它哨兵傳送命令詢問它們是否也認為該master主觀下線,如果達到一定數目(即配置檔案中的quorum)投票,哨兵會認為該master已經客觀下線(odown,o就是Objectively —— 客觀地),並選舉領頭的哨兵節點對主從系統發起故障恢復。若沒有足夠的sentinel程式同意master下線,master的客觀下線狀態會被移除,若master重新向sentinel程式傳送的PING命令返回有效回覆,master的主觀下線狀態就會被移除

哨兵認為master客觀下線後,故障恢復的操作需要由選舉的領頭哨兵來執行,選舉採用Raft演算法:

  1. 發現master下線的哨兵節點(我們稱他為A)向每個哨兵傳送命令,要求對方選自己為領頭哨兵
  2. 如果目標哨兵節點沒有選過其他人,則會同意選舉A為領頭哨兵
  3. 如果有超過一半的哨兵同意選舉A為領頭,則A當選
  4. 如果有多個哨兵節點同時參選領頭,此時有可能存在一輪投票無競選者勝出,此時每個參選的節點等待一個隨機時間後再次發起參選請求,進行下一輪投票競選,直至選舉出領頭哨兵

選出領頭哨兵後,領頭者開始對系統進行故障恢復,從出現故障的master的從資料庫中挑選一個來當選新的master,選擇規則如下:

  1. 所有線上的slave中選擇優先順序最高的,優先順序可以通過slave-priority配置
  2. 如果有多個最高優先順序的slave,則選取複製偏移量最大(即複製越完整)的當選
  3. 如果以上條件都一樣,選取id最小的slave

挑選出需要繼任的slave後,領頭哨兵向該資料庫傳送命令使其升格為master,然後再向其他slave傳送命令接受新的master,最後更新資料。將已經停止的舊的master更新為新的master的從資料庫,使其恢復服務後以slave的身份繼續執行。

2. 部署演示

本示例基於Redis 5.0.3版。

哨兵模式基於前文的主從複製模式。哨兵的配置檔案為sentinel.conf,在檔案中新增

sentinel monitor mymaster 127.0.0.1 6379 1 # mymaster定義一個master資料庫的名稱,後面是master的ip, port,1表示至少需要一個Sentinel程式同意才能將master判斷為失效,如果不滿足這個條件,則自動故障轉移(failover)不會執行
sentinel auth-pass mymaster 123456 # master的密碼

sentinel down-after-milliseconds mymaster 5000 # 5s未回覆PING,則認為master主觀下線,預設為30s
sentinel parallel-syncs mymaster 2  # 指定在執行故障轉移時,最多可以有多少個slave例項在同步新的master例項,在slave例項較多的情況下這個數字越小,同步的時間越長,完成故障轉移所需的時間就越長
sentinel failover-timeout mymaster 300000 # 如果在該時間(ms)內未能完成故障轉移操作,則認為故障轉移失敗,生產環境需要根據資料量設定該值
複製程式碼

一個哨兵可以監控多個master資料庫,只需按上述配置新增多套

分別以26379,36379,46379埠啟動三個sentinel

[root@dev-server-1 sentinel]# redis-server sentinel1.conf --sentinel
[root@dev-server-1 sentinel]# redis-server sentinel2.conf --sentinel
[root@dev-server-1 sentinel]# redis-server sentinel3.conf --sentinel
複製程式碼

也可以使用redis-sentinel sentinel1.conf 命令啟動。此時叢集包含一個master、兩個slave、三個sentinel,如圖,

redis-cluster-instance

我們來模擬master掛掉的場景,執行 kill -9 3017 將master程式幹掉,進入slave中執行 info replication檢視,

[root@dev-server-1 sentinel]# redis-cli -p 7001
127.0.0.1:7001> auth 123456
OK
127.0.0.1:7001> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7002
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
# 省略
127.0.0.1:7001> exit
[root@dev-server-1 sentinel]# redis-cli -p 7002
127.0.0.1:7002> auth 123456
OK
127.0.0.1:7002> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7001,state=online,offset=13642721,lag=1
# 省略
複製程式碼

可以看到slave 7002已經成功上位晉升為master(role:master),接收一個slave 7001的連線。此時檢視slave2.conf配置檔案,發現replicaof的配置已經被移除了,slave1.conf的配置檔案裡replicaof 127.0.0.1 6379 被改為 replicaof 127.0.0.1 7002。重新啟動master,也可以看到master.conf配置檔案中新增了replicaof 127.0.0.1 7002的配置項,可見大哥(master)下位後,再出來混就只能噹噹小弟(slave)了,三十年河東三十年河西。

3. 哨兵模式的優缺點

優點:

  1. 哨兵模式基於主從複製模式,所以主從複製模式有的優點,哨兵模式也有
  2. 哨兵模式下,master掛掉可以自動進行切換,系統可用性更高

缺點:

  1. 同樣也繼承了主從模式難以線上擴容的缺點,Redis的容量受限於單機配置
  2. 需要額外的資源來啟動sentinel程式,實現相對複雜一點,同時slave節點作為備份節點不提供服務

Cluster模式

1. 基本原理

哨兵模式解決了主從複製不能自動故障轉移,達不到高可用的問題,但還是存在難以線上擴容,Redis容量受限於單機配置的問題。Cluster模式實現了Redis的分散式儲存,即每臺節點儲存不同的內容,來解決線上擴容的問題。如圖

redis-cluster

Cluster採用無中心結構,它的特點如下:

  1. 所有的redis節點彼此互聯(PING-PONG機制),內部使用二進位制協議優化傳輸速度和頻寬
  2. 節點的fail是通過叢集中超過半數的節點檢測失效時才生效
  3. 客戶端與redis節點直連,不需要中間代理層.客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可

Cluster模式的具體工作機制:

  1. 在Redis的每個節點上,都有一個插槽(slot),取值範圍為0-16383
  2. 當我們存取key的時候,Redis會根據CRC16的演算法得出一個結果,然後把結果對16384求餘數,這樣每個key都會對應一個編號在0-16383之間的雜湊槽,通過這個值,去找到對應的插槽所對應的節點,然後直接自動跳轉到這個對應的節點上進行存取操作
  3. 為了保證高可用,Cluster模式也引入主從複製模式,一個主節點對應一個或者多個從節點,當主節點當機的時候,就會啟用從節點
  4. 當其它主節點ping一個主節點A時,如果半數以上的主節點與A通訊超時,那麼認為主節點A當機了。如果主節點A和它的從節點都當機了,那麼該叢集就無法再提供服務了

Cluster模式叢集節點最小配置6個節點(3主3從,因為需要半數以上),其中主節點提供讀寫操作,從節點作為備用節點,不提供請求,只作為故障轉移使用。

2. 部署演示

本示例基於Redis 5.0.3版。

Cluster模式的部署比較簡單,首先在redis.conf中

port 7100 # 本示例6個節點埠分別為7100,7200,7300,7400,7500,7600 
daemonize yes # r後臺執行 
pidfile /var/run/redis_7100.pid # pidfile檔案對應7100,7200,7300,7400,7500,7600 
cluster-enabled yes # 開啟叢集模式 
masterauth passw0rd # 如果設定了密碼,需要指定master密碼
cluster-config-file nodes_7100.conf # 叢集的配置檔案,同樣對應7100,7200等六個節點
cluster-node-timeout 15000 # 請求超時 預設15秒,可自行設定 
複製程式碼

分別以埠7100,7200,7300,7400,7500,7600 啟動六個例項(如果是每個伺服器一個例項則配置可一樣)

[root@dev-server-1 cluster]# redis-server redis_7100.conf
[root@dev-server-1 cluster]# redis-server redis_7200.conf
...
複製程式碼

然後通過命令將這個6個例項組成一個3主節點3從節點的叢集,

redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7100 127.0.0.1:7200 127.0.0.1:7300 127.0.0.1:7400 127.0.0.1:7500 127.0.0.1:7600 -a passw0rd
複製程式碼

執行結果如圖

redis-cluster-deploy

可以看到 7100, 7200, 7300 作為3個主節點,分配的slot分別為 0-5460, 5461-10922, 10923-16383, 7600作為7100的slave, 7500作為7300的slave,7400作為7200的slave。

我們連線7100設定一個值

[root@dev-server-1 cluster]# redis-cli -p 7100 -c -a passw0rd
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:7100> set site blog.jboost
-> Redirected to slot [9421] located at 127.0.0.1:7200
OK
127.0.0.1:7200> get site
"blog.jboost"
127.0.0.1:7200>
複製程式碼

注意新增 -c 參數列示以叢集模式,否則報 (error) MOVED 9421 127.0.0.1:7200 錯誤, 以 -a 引數指定密碼,否則報(error) NOAUTH Authentication required錯誤。

從上面命令看到key為site算出的slot為9421,落在7200節點上,所以有Redirected to slot [9421] located at 127.0.0.1:7200,叢集會自動進行跳轉。因此客戶端可以連線任何一個節點來進行資料的存取。

通過cluster nodes可檢視叢集的節點資訊

127.0.0.1:7200> cluster nodes
eb28aaf090ed1b6b05033335e3d90a202b422d6c 127.0.0.1:7500@17500 slave c1047de2a1b5d5fa4666d554376ca8960895a955 0 1584165266071 5 connected
4cc0463878ae00e5dcf0b36c4345182e021932bc 127.0.0.1:7400@17400 slave 5544aa5ff20f14c4c3665476de6e537d76316b4a 0 1584165267074 4 connected
dbbb6420d64db22f35a9b6fa460b0878c172a2fb 127.0.0.1:7100@17100 master - 0 1584165266000 1 connected 0-5460
d4b434f5829e73e7e779147e905eea6247ffa5a2 127.0.0.1:7600@17600 slave dbbb6420d64db22f35a9b6fa460b0878c172a2fb 0 1584165265000 6 connected
5544aa5ff20f14c4c3665476de6e537d76316b4a 127.0.0.1:7200@17200 myself,master - 0 1584165267000 2 connected 5461-10922
c1047de2a1b5d5fa4666d554376ca8960895a955 127.0.0.1:7300@17300 master - 0 1584165268076 3 connected 10923-16383
複製程式碼

我們將7200通過 kill -9 pid殺死程式來驗證叢集的高可用,重新進入叢集執行cluster nodes可以看到7200 fail了,但是7400成了master,重新啟動7200,可以看到此時7200已經變成了slave。

3. Cluster模式的優缺點

優點:

  1. 無中心架構,資料按照slot分佈在多個節點。
  2. 叢集中的每個節點都是平等的關係,每個節點都儲存各自的資料和整個叢集的狀態。每個節點都和其他所有節點連線,而且這些連線保持活躍,這樣就保證了我們只需要連線叢集中的任意一個節點,就可以獲取到其他節點的資料。
  3. 可線性擴充套件到1000多個節點,節點可動態新增或刪除
  4. 能夠實現自動故障轉移,節點之間通過gossip協議交換狀態資訊,用投票機制完成slave到master的角色轉換

缺點:

  1. 客戶端實現複雜,驅動要求實現Smart Client,快取slots mapping資訊並及時更新,提高了開發難度。目前僅JedisCluster相對成熟,異常處理還不完善,比如常見的“max redirect exception”
  2. 節點會因為某些原因發生阻塞(阻塞時間大於 cluster-node-timeout)被判斷下線,這種failover是沒有必要的
  3. 資料通過非同步複製,不保證資料的強一致性
  4. slave充當“冷備”,不能緩解讀壓力
  5. 批量操作限制,目前只支援具有相同slot值的key執行批量操作,對mset、mget、sunion等操作支援不友好
  6. key事務操作支援有線,只支援多key在同一節點的事務操作,多key分佈不同節點時無法使用事務功能
  7. 不支援多資料庫空間,單機redis可以支援16個db,叢集模式下只能使用一個,即db 0

Redis Cluster模式不建議使用pipeline和multi-keys操作,減少max redirect產生的場景。

總結

本文介紹了Redis叢集方案的三種模式,其中主從複製模式能實現讀寫分離,但是不能自動故障轉移;哨兵模式基於主從複製模式,能實現自動故障轉移,達到高可用,但與主從複製模式一樣,不能線上擴容,容量受限於單機的配置;Cluster模式通過無中心化架構,實現分散式儲存,可進行線性擴充套件,也能高可用,但對於像批量操作、事務操作等的支援性不夠好。三種模式各有優缺點,可根據實際場景進行選擇。

參考:

  1. blog.csdn.net/q649381130/…
  2. www.cnblogs.com/51life/p/10…
  3. www.cnblogs.com/chensuqian/…
  4. stor.51cto.com/art/201910/…

作者:空山新雨,一枚仍在學習路上的大齡碼農
近期作者寫了幾十篇技術部落格,內容包括Java、Spring Boot、Spring Cloud、Docker,技術管理心得等
歡迎關注作者微信公眾號:空山新雨的技術空間,一起學習成長

微信公眾號

相關文章