用 docker 學習 redis 主從複製3 redis-sentinel(哨兵模式)

php_yt發表於2022-01-07

為什麼需要 redis-sentinel

redis 複製有一個問題,當主機(Master)當機時,怎麼辦?
我們需要迅速的將某個從節點切換為主節點,然後把其他從節點複製該節點,最後通知客戶端連線新的主節點。
如果這一切需要手動去做,那麼主從複製並沒有做到高可用。
如何解決呢?如果我們有一個監控程式能夠監控各個機器的狀態及時作出調整,將手動的操作變成自動的。
Sentinel 的出現就是為了解決這個問題。

Redis Sentinel 本身又是一個分散式架構,每個 Sentinel 節點不僅對 Redis 伺服器進行監控,而且還監控其餘的 Sentinel 節點,避免單節點故障。

故障轉移的流程

  1. 主節點出現故障,此時兩個從節點與主節點失去連線,主從複製失敗。
    用 docker 學習 redis 主從複製3 redis-sentinel(哨兵)

  2. 每個 Sentinel 節點通過定期監控發現主節點出現了故障
    用 docker 學習 redis 主從複製3 redis-sentinel(哨兵)

  3. 多個 Sentinel 節點對主節點的故障達成一致會選舉出其中一個節點作為領導者負責故障轉移。
    用 docker 學習 redis 主從複製3 redis-sentinel(哨兵)

  4. Sentinel 領導者節點執行了故障轉移,整個過程基本是跟我們手動調整一致的,只不過是自動化完成的。
    用 docker 學習 redis 主從複製3 redis-sentinel(哨兵)

  5. 故障轉移後整個 Redis Sentinel 的結構,重新選舉了新的主節點。並且將故障轉移的結果通知給應用方。
    用 docker 學習 redis 主從複製3 redis-sentinel(哨兵)

演示

這裡準備一主二從

docker run -itd --name master --net mynetwork --ip 172.10.0.5 -p 6385:6379 redis:n1
docker run -itd --name slave0 --net mynetwork --ip 172.10.0.2 -p 6382:6379 redis:n1
docker run -itd --name slave1 --net mynetwork --ip 172.10.0.4 -p 6384:6379 redis:n1

以及三臺redis-sentinel

docker run -itd --name sentinel1 --net mynetwork --ip 172.10.0.11 -p 22531:22531 redis:n1
docker run -itd --name sentinel2 --net mynetwork --ip 172.10.0.12 -p 22532:22532 redis:n1
docker run -itd --name sentinel3 --net mynetwork --ip 172.10.0.13 -p 22533:22533 redis:n1

用 docker 學習 redis 主從複製3 redis-sentinel(哨兵模式)

配置主從複製 master slave0 slave1

docker exec -it master bash
docker exec -it slave0 bash
docker exec -it slave1 bash
vi /etc/redis.conf
#
bind 0.0.0.0
# 不設定密碼,把保護模式關閉
protected-mode no

三個容器啟動
[root@794f7a0dfc70 /]# redis-server /etc/redis.conf &
然後在slave0 slave1 中執行
[root@794f7a0dfc70 /]# redis-cli
127.0.0.1:6379> slaveof 172.10.0.5 6379
OK Already connected to specified master
主節點檢查
[root@4f1cb20cc2b3 /]# redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.10.0.2,port=6379,state=online,offset=17532,lag=1
slave1:ip=172.10.0.4,port=6379,state=online,offset=17532,lag=1
master_replid:ac6c423f9d433e02f20fc2b20d500a12310c5554
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:17532
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:17532

配置 sentinel

docker exec -it sentinel2 bash
vi /etc/redis-sentinel.conf
# bind 127.0.0.1 192.168.1.1
bind 0.0.0.0

# 這裡不設定密碼,所以把保護模式關閉
# protected-mode no
protected-mode no

#監控主節點 主節點名稱 ip port
#最後一個2的意思是有幾臺sentinel節點發現有問題,就會發生故障轉移。
#例如配置為2,代表至少2個sentinel節點認為主節點不可達,那麼這個不可達的判定才是客觀的。
#值越小,對於下線的條件越寬鬆,反之越嚴格。一般設定為sentinel節點總數的一半+1
#172.10.0.5是主節點,mymaster是隨便起的名稱,因哨兵可監視多個master,為了區分
sentinel monitor mymaster 172.10.0.5 6379 2

#主節點的密碼,沒有設定不用更改
# sentinel auth-pass <master-name> <password>

三個 sentinel 容器全部如上配置,然後啟動

redis-sentinel /etc/redis-sentinel.conf &

檢視 sentinel 的日誌

[root@c562749c1042 redis]# cat /var/log/redis/sentinel.log 

# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
# Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=83, just started
# Configuration loaded
* Running mode=sentinel, port=26379.
# WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
# Sentinel ID is 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad
# +monitor master mymaster 172.10.0.5 6379 quorum 2
* +slave slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.5 6379
* +slave slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.5 6379
* +sentinel sentinel 0a794e05a58aa0b90a7badd3a79acbc17f878c02 172.10.0.12 26379 @ mymaster 172.10.0.5 6379
* +sentinel sentinel f88c2429ad02985495c4c7fd5929275e96e07b61 172.10.0.11 26379 @ mymaster 172.10.0.5 6379

重點是下面幾行,因為三個 sentinel 節點都監控 master 節點,所以 slave 從節點資訊、以及其他 sentinel 節點資訊可以通過 master 節點資訊共享。

測試

我們將 master 掛掉模擬主節點當機,看看哨兵是如何運作的

[root@4f1cb20cc2b3 /]# ps -ef | grep redis
root       105    78  0 14:25 pts/2    00:00:01 redis-server 0.0.0.0:6379
root       115    78  0 14:41 pts/2    00:00:00 grep --color=auto redis
[root@4f1cb20cc2b3 /]# kill 105

分別看各個 sentinel 的日誌
sentinel1 節點

# +sdown master mymaster 172.10.0.5 6379
主觀下線
# +new-epoch 1
# +vote-for-leader 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 1
投票給sentiel3做為領導者
# +config-update-from sentinel 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 172.10.0.13 26379 @ mymaster 172.10.0.5 6379
從領導者sentiel3那裡配置更新
# +switch-master mymaster 172.10.0.5 6379 172.10.0.4 6379
切換主節點資訊到slave1
* +slave slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.4 6379
其他從節點資訊
* +slave slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379
# +sdown slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379

發現主節點不可達,進行主觀下線(sdown subjective主觀的);投票給 sentinel3 節點(172.10.0.13)作為領導者;剩下的是故障轉移後從領導者那裡接收的資訊,其實從這裡也可以看出,master 節點進行了切換 172.0.0.5(原master) -> 172.0.0.4 (原slave1節點)

sentinel2 節點

# +sdown master mymaster 172.10.0.5 6379
主觀下線
# +odown m#quorum 2/2
達到法定人數2個及以上客觀下線
# +new-epoch 1
# +try-failover master mymaster 172.10.0.5 6379
開始嘗試故障轉移
# +vote-for-leader 0a794e05a58aa0b90a7badd3a79acbc17f878c02 1
選舉自己為領導者
# 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad voted for 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 1
sentinel3節點選舉了sentinel3
# f88c2429ad02985495c4c7fd5929275e96e07b61 voted for  4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 1
sentinel1選舉了sentinel3
因此sentinel3獲得了兩票,成為領導者,進行故障轉移的工作。
# +config-update-from sentinel 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 172.10.0.13 26379 @ mymaster 172.10.0.5 6379
從sentinel3領導者得到更新配置
# +switch-master mymaster 172.10.0.5 6379 172.10.0.4 6379
切換slave1為主節點
109:X 06 Jan 2022 14:42:10.024 * +slave slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.4 6379
同步其他從節點資訊
109:X 06 Jan 2022 14:42:10.024 * +slave slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379
# +sdown slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379

和 sentinel1 節點的日誌差不都,先是主觀下線,就是我認為它下線了,發現還有其他節點也認為它下線了,於是客觀上認為它下線了。

sentinel3 節點

# +try-failover master mymaster 172.10.0.5 6379
開始選舉領導者,嘗試故障轉移
# +vote-for-leader 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 1
自己投票給了自己
# 0a794e05a58aa0b90a7badd3a79acbc17f878c02 voted for 0a794e05a58aa0b90a7badd3a79acbc17f878c02 1
sentinel2節點投票給了sentinel2
# f88c2429ad02985495c4c7fd5929275e96e07b61 voted for 4c7ec628d2a2e1778dab9ba36d7e5109dd2601ad 1
sentinel1投票給了自己
# +elected-leader master mymaster 172.10.0.5 6379
因此自己當選領導人
# +failover-state-select-slave master mymaster 172.10.0.5 6379
故障切換選擇從機作為主節點
# +selected-slave slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.5 6379
選擇了slave1節點作為主節點
* +failover-state-send-slaveof-noone slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.5 6379
在slave1上執行 slaveof no one 命令,使之成為主節點
* +failover-state-wait-promotion slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.5 6379
故障轉移狀態等待晉升
# +promoted-slave slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.5 6379
晉升slave1為主節點
# +failover-state-reconf-slaves master mymaster 172.10.0.5 6379 
* +slave-reconf-sent slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.5 6379
# -odown master mymaster 172.10.0.5 6379
* +slave-reconf-inprog slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.5 6379
* +slave-reconf-done slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.5 6379
# +failover-end master mymaster 172.10.0.5 6379
嘗試故障轉移完畢
# +switch-master mymaster 172.10.0.5 6379 172.10.0.4 6379
切換主節點172.10.0.5->172.10.0.4
* +slave slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.4 6379
將slave0作為主節點的從節點
* +slave slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379
將原master作為主節點的從節點
# +sdown slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379
原master節點不可達,繼續監視

sentinel3 節點被選舉為領導者,完成故障轉移的工作,將172.10.0.4 (slave1)切換為主節點。

新的主節點 slave1

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.10.0.2,port=6379,state=online,offset=758977,lag=0
master_replid:ed72006b113f2ffe639f6e5f807acea6af3e15cc
master_replid2:ac6c423f9d433e02f20fc2b20d500a12310c5554
master_repl_offset:759113
second_repl_offset:115575
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:296
repl_backlog_histlen:758818

可以看到它的日誌

* MASTER <-> REPLICA sync started
# Error condition on socket for SYNC: Connection refused
* Connecting to MASTER 172.10.0.5:6379
* MASTER <-> REPLICA sync started
# Error condition on socket for SYNC: Connection refused
主節點已經當機,複製中斷

# Setting secondary replication ID to ac6c423f9d433e02f20fc2b20d500a12310c5554, valid up to offset: 115575. New replication ID is ed72006b113f2ffe639f6e5f807acea6af3e15cc
將輔助複製ID設定為xx,有效資料至偏移量:115575。新的複製ID是xx. 你可以對照下文章前面的故障轉移之前的replication ID

* Discarding previously cached master state.
丟棄以前快取的主節點狀態
* MASTER MODE enabled (user request from 'id=10 addr=172.10.0.13:37944 fd=13 name=sentinel-4c7ec628-cmd age=353 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=154 qbuf-free=32614 obl=36 oll=0 omem=0 events=r cmd=exec')
主模式已啟用(來自sentinel3哨兵的請求)

# CONFIG REWRITE executed with success. 
成功地執行了配置重寫
* Replica 172.10.0.2:6379 asks for synchronization
slave0節點請求複製
* Partial resynchronization request from 172.10.0.2:6379 accepted. Sending 159 bytes of backlog starting from offset 115575
172.10.0.2:6379的部分重新同步請求已接受。從偏移量115575開始傳送159位元組的積壓工作

日誌中的第二句,將輔助複製ID設定為ac6c423f9d433e02f20fc2b20d500a12310c5554,有效資料至偏移量:115575。New replication ID is ..

、將原master節點的 master_replid 作為了自己的 master_replid2 (輔助複製ID),也就是故障轉移的過程,保留了原 master 的一些原始資訊。

原 master 節點的資訊
用 docker 學習 redis 主從複製3 redis-sentinel(哨兵模式)

一個很重要的原因,就是避免切換主節點後發生全量複製,上方日誌中最後一行: 172.10.0.2:6379的部分重新同步請求已接受。從偏移量115575開始傳送159位元組的積壓工作 證明是部分複製。另外可以從另一個從節點(slave0)的日誌來看:

* Connecting to MASTER 172.10.0.5:6379
* MASTER <-> REPLICA sync started
# Error condition on socket for SYNC: Connection refused
主節點斷開復制
* REPLICAOF 172.10.0.4:6379 enabled (user request from 'id=10 addr=172.10.0.13:48156 fd=13 name=sentinel-4c7ec628-cmd age=353 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=286 qbuf-free=32482 obl=36 oll=0 omem=0 events=r cmd=exec')
執行了 replicaof 172.10.0.4 6379 (來自sentinel3的請求)

# CONFIG REWRITE executed with success.
配置更新成功
* Connecting to MASTER 172.10.0.4:6379
連線新的主節點
* MASTER <-> REPLICA sync started
開始複製
* Non blocking connect for SYNC fired the event.
* Master replied to PING, replication can continue...
* Trying a partial resynchronization (request ac6c423f9d433e02f20fc2b20d500a12310c5554:115575).
* Successful partial resynchronization with master. 
請求與主節點部分複製。注意:切換主節點後並不是全量複製,而是部分複製
# Master replication ID changed to ed72006b113f2ffe639f6e5f807acea6af3e15cc
主節點的 replication ID更換
* MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization
主節點接收部分複製請求

slave0 從 slave1(現在是新的主節點)進行部分複製(Partial Resynchronization),而不是全量複製。

重新啟動原 master 節點

之前將原 master 節點 172.10.0.5 主動掛掉,之後 sentinel 進行了故障轉移,將 172.10.0.4slave1 作為主節點,現在將原 master 節點重新啟動,會發生什麼呢?

通過在目前主節點 slave1 上檢視

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.10.0.2,port=6379,state=online,offset=1229694,lag=0
slave1:ip=172.10.0.5,port=6379,state=online,offset=1229694,lag=1
...

可以發現原來的主節點重新啟動後,自動變成了從節點。這個過程是怎樣的呢?
其中一個哨兵發現原主節點上線後,於是向它傳送了轉換為從節點的命令

+convert-to-slave slave 172.10.0.5:6379 172.10.0.5 6379 @ mymaster 172.10.0.4 6379 

而原主節點收到後

REPLICAOF 172.10.0.4:6379 enabled (user request from 'id=3 addr=172.10.0.12:47798 fd=7 name=sentinel-0a794e05-cmd age=10 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=150 qbuf-free=32618 obl=36 oll=0 omem=0 events=r cmd=exec')
這是sentinel2節點發來的請求,執行了 REPLICAOF 172.10.0.4 6379 命令 變成從節點
117:S 06 Jan 2022 16:10:20.154 # CONFIG REWRITE executed with success.
更新配置
117:S 06 Jan 2022 16:10:21.007 * Connecting to MASTER 172.10.0.4:6379
開始發起複製
117:S 06 Jan 2022 16:10:21.007 * MASTER <-> REPLICA sync started
...

通知客戶端故障轉移結果

sentinel 哨兵模式下客戶端(寫入方)並不是直接連線 redis 資料伺服器,而是通過哨兵連線。

寫不動了,未完待續…

本作品採用《CC 協議》,轉載必須註明作者和本文連結
focus

相關文章