Redis哨兵功能全面分析與總結(jié)[實(shí)戰(zhàn)操作篇]
文章簡介
本文將通過理論+實(shí)踐的方式從頭到尾總結(jié)Redis中的哨兵機(jī)制。文章內(nèi)容從主從復(fù)制的弊端、如何解決弊端、什么是哨兵、哨兵監(jiān)控的圖形結(jié)構(gòu)、哨兵監(jiān)控的原理、如何配置哨兵、哨兵與主從復(fù)制的關(guān)系等方面來演示。
文中涉及到的工具、書籍、設(shè)計(jì)圖,可以通過回復(fù)公眾號"Redis哨兵"獲取,注意大小寫。
主從復(fù)制弊端

上面的圖形結(jié)構(gòu),大致的可以理解為Redis的主從復(fù)制拓?fù)鋱D。
其中1個(gè)主節(jié)點(diǎn)負(fù)責(zé)應(yīng)用系統(tǒng)的寫入數(shù)據(jù),另外的4個(gè)從節(jié)點(diǎn)負(fù)責(zé)應(yīng)用系統(tǒng)的讀數(shù)據(jù)。
同時(shí)4個(gè)從節(jié)點(diǎn)向其中的1個(gè)一個(gè)主節(jié)點(diǎn)發(fā)起復(fù)制請求操作。
在Redis服務(wù)運(yùn)行正常的情況下,該拓?fù)浣Y(jié)結(jié)構(gòu)不會出現(xiàn)什么問題。試想一下這樣的一個(gè)場景。如果主節(jié)點(diǎn)服務(wù)發(fā)生了異常,不能正常處理服務(wù)(如寫入數(shù)據(jù)、主從復(fù)制操作)。這時(shí)候,Redis服務(wù)能正常響應(yīng)應(yīng)用系統(tǒng)的讀操作,但是沒法進(jìn)行寫操作。 出現(xiàn)該情況就會嚴(yán)重影響到系統(tǒng)的業(yè)務(wù)數(shù)據(jù)。那該如何解決呢?
可以大致想到下面的幾種情況來解決。
當(dāng)主節(jié)點(diǎn)發(fā)生異常情況時(shí),手動的從部分從節(jié)點(diǎn)中選擇一個(gè)節(jié)點(diǎn)作為主節(jié)點(diǎn)。然后改變其他從節(jié)點(diǎn)的主從復(fù)制關(guān)系。
我們也可以寫一套自動處理該情況的服務(wù),避免依賴于人為的操作。
上面的方案在一定程度上是能幫助我們解決問題。但是過多的人為干預(yù)。例如第1點(diǎn),我們需要考慮人工處理的實(shí)時(shí)性和正確性。第2點(diǎn),自動化處理是能夠很好的解決第1點(diǎn)中的問題,但是自動處理存在如何選擇新主節(jié)點(diǎn)的問題,因此這也是一個(gè)不好的地方。
通過上面大致的分析,我們不難得出Redis的哨兵機(jī)制就是針對種種問題出現(xiàn)的。
什么是哨兵
可以把Redis的哨兵理解為一種Redis分布式架構(gòu)。該架構(gòu)中主要存在兩種角色,一種是哨兵,另外一種是數(shù)據(jù)節(jié)點(diǎn)(主從復(fù)制節(jié)點(diǎn))。
哨兵主要負(fù)責(zé)的任務(wù)是:
每一個(gè)哨兵都會監(jiān)控?cái)?shù)據(jù)節(jié)點(diǎn)以及其他的哨兵節(jié)點(diǎn)。
當(dāng)其中的一個(gè)哨兵監(jiān)控到節(jié)點(diǎn)不可達(dá)是,會給對應(yīng)的節(jié)點(diǎn)做下線標(biāo)識。如果下線的節(jié)點(diǎn)為主節(jié)點(diǎn)。這時(shí)候會通知其他的哨兵節(jié)點(diǎn)。
哨兵節(jié)點(diǎn)通過“協(xié)商”推舉出從節(jié)點(diǎn)中的某一個(gè)節(jié)點(diǎn)為主節(jié)點(diǎn)。
接著將其他的從節(jié)點(diǎn)斷開與舊主節(jié)點(diǎn)的復(fù)制關(guān)系,將推舉出來的新主節(jié)點(diǎn)作為從節(jié)點(diǎn)的主節(jié)點(diǎn)。
將切換的結(jié)果通知給應(yīng)用系統(tǒng)。

配置哨兵
在演示環(huán)境中,配置了三臺數(shù)據(jù)節(jié)點(diǎn)(1主2從),三臺哨兵節(jié)點(diǎn)。演示中用到的Redis為6.0.8版本。
| 角色 | IP | 端口號 |
|---|---|---|
| (數(shù)據(jù)節(jié)點(diǎn))master | 127.0.0.1 | 8002 |
| (數(shù)據(jù)節(jié)點(diǎn))slave | 127.0.0.1 | 8003 |
| (數(shù)據(jù)節(jié)點(diǎn))slave | 127.0.0.1 | 8004 |
| 哨兵節(jié)點(diǎn) | 127.0.0.1 | 8005 |
| 哨兵節(jié)點(diǎn) | 127.0.0.1 | 8006 |
| 哨兵節(jié)點(diǎn) | 127.0.0.1 | 8007 |
(數(shù)據(jù)節(jié)點(diǎn))master配置。
# 服務(wù)配置
daemonize yes
# 端口號
port 8002
# 數(shù)據(jù)目錄
dir "/Users/kert/config/redis/8002"
# 日志文件名稱
logfile "8002.log"
# 設(shè)置密碼
bind 0.0.0.0
# requirepass 8002
# 多線程
# 1.開啟線程數(shù)。
io-threads 2
# 2.開啟讀線程。
io-threads-do-reads yes
# 持久化存儲(RDB)
# 1.每多少秒至少有多少個(gè)key發(fā)生變化,則執(zhí)行save命令。
save 10 1
save 20 1
save 30 1
# 2.當(dāng)bgsave命令發(fā)生錯(cuò)誤時(shí),停止寫入操作。
stop-writes-on-bgsave-error yes
# 3.是否開啟rbd文件壓縮
rdbcompression yes
(數(shù)據(jù)節(jié)點(diǎn))slave配置。
# 服務(wù)配置
daemonize yes
port 8004
dir "/Users/kert/config/redis/8004"
logfile "8004.log"
# 多線程
# 1.開啟線程數(shù)。
io-threads 2
# 2.開啟讀線程。
io-threads-do-reads yes
# 持久化存儲(RDB)
# 1.每多少秒至少有多少個(gè)key發(fā)生變化,則執(zhí)行save命令。
save 10 1
save 20 1
save 30 1
# 2.當(dāng)bgsave命令發(fā)生錯(cuò)誤時(shí),停止寫入操作。
stop-writes-on-bgsave-error yes
# 3.是否開啟rbd文件壓縮
rdbcompression yes
# 配置主節(jié)點(diǎn)信息
replicaof 127.0.0.1 8002
哨兵節(jié)點(diǎn)配置。
# 端口號
port 8006
# 運(yùn)行模式
daemonize yes
# 數(shù)據(jù)目錄
dir "/Users/kert/config/redis/sentinel/8006"
# 日志文件
logfile "8006.log"
# 監(jiān)聽數(shù)據(jù)節(jié)點(diǎn)
sentinel monitor mymaster 127.0.0.1 8002 2(判定主節(jié)點(diǎn)下線狀態(tài)的票數(shù))
# 設(shè)置主節(jié)點(diǎn)連接權(quán)限信息
sentinel auth-pass mymaster 8002
# 判斷數(shù)據(jù)節(jié)點(diǎn)和sentinel節(jié)點(diǎn)多少毫秒數(shù)內(nèi)沒有響應(yīng)ping,則處理為下線狀態(tài)
sentinel down-after-milliseconds mymaster 30000
# 主節(jié)點(diǎn)下線后,從節(jié)點(diǎn)向新的主節(jié)點(diǎn)發(fā)起復(fù)制的個(gè)數(shù)限制(指的一次同時(shí)允許幾個(gè)從節(jié)點(diǎn))。
sentinel parallel-syncs mymaster 1
# 故障轉(zhuǎn)移超時(shí)時(shí)間
sentinel failover-timeout mymaster 180000
所有的哨兵節(jié)點(diǎn)直接將port、dir和logfile修改為對應(yīng)的具體哨兵信息即可。
接著啟動對應(yīng)的服務(wù)Redis服務(wù)。
// 啟動master節(jié)點(diǎn)
kert@kertdeMacBook-Pro-2 ~/config/redis/8002 redis-server ./redis.conf
// 啟動slave節(jié)點(diǎn)
kert@kertdeMacBook-Pro-2 ~/config/redis/8003 redis-server ./redis.conf
kert@kertdeMacBook-Pro-2 ~/config/redis/8004 redis-server ./redis.conf
// 啟動哨兵節(jié)點(diǎn)
kert@kertdeMacBook-Pro-2 ~/config/redis/sentinel redis-sentinel 8007.conf
kert@kertdeMacBook-Pro-2 ~/config/redis/sentinel redis-sentinel 8006.conf
kert@kertdeMacBook-Pro-2 ~/config/redis/sentinel redis-sentinel 8005.conf
哨兵啟動,需要用到Redis安裝完之后自帶的 redis-sentinel命令。
查看Redis服務(wù)運(yùn)行狀態(tài)。
kert@kertdeMacBook-Pro-2 ~/config/redis/sentinel ps -ef | grep redis
501 99742 1 0 3:53PM ?? 0:00.47 redis-server 0.0.0.0:8002
501 99776 1 0 3:53PM ?? 0:00.36 redis-server 0.0.0.0:8003
501 99799 1 0 3:53PM ?? 0:00.10 redis-server *:8004
501 99849 1 0 3:53PM ?? 0:00.06 redis-sentinel *:8007 [sentinel]
501 99858 1 0 3:53PM ?? 0:00.04 redis-sentinel *:8006 [sentinel]
501 99867 1 0 3:53PM ?? 0:00.03 redis-sentinel *:8005 [sentinel]
看到上面的結(jié)果,則表示我們的Redis服務(wù)已經(jīng)正常啟動。
演示故障切換
我們先打開三個(gè)終端,分配時(shí)master節(jié)點(diǎn)和兩個(gè)slave節(jié)點(diǎn)。檢測是否能夠正常進(jìn)行主從復(fù)制。
我們在主節(jié)點(diǎn)任意寫入一些數(shù)據(jù),然后在從節(jié)點(diǎn)進(jìn)行查詢數(shù)據(jù)。為了方便,后面將master稱作1號終端,兩個(gè)slave分配叫做2號和3號終端。
我們在1號終端寫入數(shù)據(jù)。
127.0.0.1:8002> set name tony
OK
127.0.0.1:8002> set age 1
OK
127.0.0.1:8002> set socre 1
OK
127.0.0.1:8002>
接著在2號和3號終端下面執(zhí)行如下的查詢操作。
127.0.0.1:8003> get name
"tony"
127.0.0.1:8003> get age
"1"
127.0.0.1:8003> get socre
"1"
事實(shí)證明我們的主從復(fù)制是成功的,接下來我們就停掉master節(jié)點(diǎn)的服務(wù)。
我們實(shí)現(xiàn)查看一下哨兵節(jié)點(diǎn)的一個(gè)狀態(tài)信息。
查看哨兵端口為8005的節(jié)點(diǎn)。
kert@kertdeMacBook-Pro-2 ~ redis-cli -p 8005 info
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:8002,slaves=2,sentinels=3
查看哨兵端口為8006的節(jié)點(diǎn)。
kert@kertdeMacBook-Pro-2 ~ redis-cli -p 8006 info
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:8002,slaves=2,sentinels=3
查看哨兵端口為8007的節(jié)點(diǎn)。
kert@kertdeMacBook-Pro-2 ~ redis-cli -p 8007 info
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:8002,slaves=2,sentinels=3
通過上面的幾個(gè)狀態(tài)信息,我們可以看到哨兵檢測的主節(jié)點(diǎn)信息,主節(jié)點(diǎn)下面有幾個(gè)從節(jié)點(diǎn),同時(shí)哨兵節(jié)點(diǎn)有幾個(gè)。
我們殺掉master的進(jìn)程??梢钥吹?號端口自動斷開了連接。

接著我們通過哨兵機(jī)制查看一下數(shù)據(jù)節(jié)點(diǎn)狀態(tài)信息。
kert@kertdeMacBook-Pro-2 ~ redis-cli -p 8005 info
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:8004,slaves=2,sentinels=3
通過上面的查詢結(jié)果,我們可以看到address的值編程了8004端口了,其他的信息沒有發(fā)生改變,說明哨兵已經(jīng)完成切換工作。
接下來我們在新的主節(jié)點(diǎn)執(zhí)行操作命令,查看在從節(jié)點(diǎn)是否能夠完成主從復(fù)制。
在3號端口(新的master)執(zhí)行一個(gè)del命令。
127.0.0.1:8004> del age
(integer) 1
127.0.0.1:8004> keys *
1) "name"
2) "socre"
在2號端口執(zhí)行讀命令。
127.0.0.1:8003> keys *
1) "socre"
2) "name"
此時(shí)可以發(fā)現(xiàn)我們的主從復(fù)制也是正常的。
啟動舊的master,并執(zhí)行讀命令。
kert@kertdeMacBook-Pro-2 ~/config/redis/8002 redis-server ./redis.conf
kert@kertdeMacBook-Pro-2 ~/config/redis/8002 redis-cli -p 8002
127.0.0.1:8002> keys *
1) "name"
2) "socre"
此時(shí)你也會發(fā)現(xiàn),原來的master節(jié)點(diǎn)變成了slave節(jié)點(diǎn),并且能夠正常復(fù)制新master節(jié)點(diǎn)的數(shù)據(jù)。
配置文件對比
在我們啟動了哨兵模式之后,我們的哨兵配置文件和數(shù)據(jù)節(jié)點(diǎn)配置文件的內(nèi)容都會自動的生成一個(gè)特定的內(nèi)容。
數(shù)據(jù)節(jié)點(diǎn)(master距離)。
變化前
# 服務(wù)配置
daemonize yes
# 端口號
port 8002
# 數(shù)據(jù)目錄
dir "/Users/kert/config/redis/8002"
# 日志文件名稱
logfile "8002.log"
# 設(shè)置密碼
bind 0.0.0.0
# requirepass 8002
# 多線程
# 1.開啟線程數(shù)。
io-threads 2
# 2.開啟讀線程。
io-threads-do-reads yes
# 持久化存儲(RDB)
# 1.每多少秒至少有多少個(gè)key發(fā)生變化,則執(zhí)行save命令。
save 10 1
save 20 1
save 30 1
# 2.當(dāng)bgsave命令發(fā)生錯(cuò)誤時(shí),停止寫入操作。
stop-writes-on-bgsave-error yes
# 3.是否開啟rbd文件壓縮
rdbcompression yes
變化后
# 服務(wù)配置
daemonize yes
# 端口號
port 8002
# 數(shù)據(jù)目錄
dir "/Users/kert/config/redis/8002"
# 日志文件名稱
logfile "8002.log"
# 設(shè)置密碼
bind 0.0.0.0
# requirepass 8002
# 多線程
# 1.開啟線程數(shù)。
io-threads 2
# 2.開啟讀線程。
io-threads-do-reads yes
# 持久化存儲(RDB)
# 1.每多少秒至少有多少個(gè)key發(fā)生變化,則執(zhí)行save命令。
save 10 1
save 20 1
save 30 1
# 2.當(dāng)bgsave命令發(fā)生錯(cuò)誤時(shí),停止寫入操作。
stop-writes-on-bgsave-error yes
# 3.是否開啟rbd文件壓縮
rdbcompression yes
# Generated by CONFIG REWRITE
pidfile "/var/run/redis.pid"
user default on nopass ~* +@all
replicaof 127.0.0.1 8004
哨兵節(jié)點(diǎn)
變化前
# 端口號
port 8006
# 運(yùn)行模式
daemonize yes
# 數(shù)據(jù)目錄
dir "/Users/kert/config/redis/sentinel/8006"
# 日志文件
logfile "8006.log"
# 監(jiān)聽數(shù)據(jù)節(jié)點(diǎn)
sentinel monitor mymaster 127.0.0.1 8002 2(判定主節(jié)點(diǎn)下線狀態(tài)的票數(shù))
# 設(shè)置主節(jié)點(diǎn)連接權(quán)限信息
sentinel auth-pass mymaster 8002
# 判斷數(shù)據(jù)節(jié)點(diǎn)和sentinel節(jié)點(diǎn)多少毫秒數(shù)內(nèi)沒有響應(yīng)ping,則處理為下線狀態(tài)
sentinel down-after-milliseconds mymaster 30000
# 主節(jié)點(diǎn)下線后,從節(jié)點(diǎn)向新的主節(jié)點(diǎn)發(fā)起復(fù)制的個(gè)數(shù)限制(指的一次同時(shí)允許幾個(gè)從節(jié)點(diǎn))。
sentinel parallel-syncs mymaster 1
# 故障轉(zhuǎn)移超時(shí)時(shí)間
sentinel failover-timeout mymaster 180000
變化后
# 端口號
port 8005
# 運(yùn)行模式
daemonize yes
# 數(shù)據(jù)目錄
dir "/Users/kert/config/redis/sentinel/8005"
# 日志文件
logfile "8005.log"
# 監(jiān)聽數(shù)據(jù)節(jié)點(diǎn)
sentinel myid 5724fd60af87e728e6f8f03ded693960c983e156
# 判斷數(shù)據(jù)節(jié)點(diǎn)和sentinel節(jié)點(diǎn)多少毫秒數(shù)內(nèi)沒有響應(yīng)ping,則處理為下線狀態(tài)
sentinel deny-scripts-reconfig yes
# 主節(jié)點(diǎn)下線后,從節(jié)點(diǎn)向新的主節(jié)點(diǎn)發(fā)起復(fù)制的個(gè)數(shù)限制(指的一次同時(shí)允許幾個(gè)從節(jié)點(diǎn))。
sentinel monitor mymaster 127.0.0.1 8004 2
# 故障轉(zhuǎn)移超時(shí)時(shí)間
sentinel config-epoch mymaster 3
# Generated by CONFIG REWRITE
protected-mode no
user default on nopass ~* +@all
sentinel leader-epoch mymaster 3
sentinel known-replica mymaster 127.0.0.1 8002
sentinel known-replica mymaster 127.0.0.1 8003
sentinel known-sentinel mymaster 127.0.0.1 8006 8fbd2cce642c881f752775afee9b3591e0d90dc6
sentinel known-sentinel mymaster 127.0.0.1 8007 69530c74791e5f32db1c2a006c826a6463bc6496
sentinel current-epoch 3
pidfile "/var/run/redis.pid"
實(shí)戰(zhàn)代碼
這里我們使用PHP原生類操作Redis哨兵,首先我們創(chuàng)建一個(gè)Redis操作類,類中代碼如下:
class OperationRedis
{
private $redis;
private $requestParams;
private $redisHandler;
/**
* 本機(jī)IP地址
* @var string
*/
private $redisHost = '192.168.2.102';
public function __construct()
{
$this->requestParams = Request::instance()->all();
$this->redisHandler = new \Redis();
$this->redis = $this->redisHandler->connect($this->redisHost, 8005);
}
/**
* 獲取Redis哨兵信息
* @author kert
*/
public function getRedisNode()
{
/** @var array $masterLists 通過sentinel節(jié)點(diǎn)獲取所有主節(jié)點(diǎn) */
$masterLists = $this->redisHandler->rawCommand('SENTINEL', 'masters');
dump('master列表配置信息', $masterLists);
foreach ($masterLists as $value) {
/** @var array $masterInfo 主節(jié)點(diǎn)信息 */
$masterInfo = $this->redisHandler->rawCommand('SENTINEL', 'master', $value[1]);
dump('master節(jié)點(diǎn)信息', $masterInfo);
// 向主節(jié)點(diǎn)插入數(shù)據(jù)
$insertNumber = $this->insertInfoByMaster((string)$this->redisHost, (int)$value[5]);
dump('Redis隊(duì)列數(shù)量', $insertNumber);
/** @var array $slaveLists 主節(jié)點(diǎn)下面的從節(jié)點(diǎn)信息 */
$slaveLists = $this->redisHandler->rawCommand('SENTINEL', 'slaves', $value[1]);
dump('master下的slave節(jié)點(diǎn)信息', $slaveLists);
foreach ($slaveLists as $v) {
$this->getInfoBySlave((string)$this->redisHost, (int)$v[5]);
}
}
}
/**
* 向主節(jié)點(diǎn)插入數(shù)據(jù)
* @param string $masterIp
* @param int $port
* @return int
* @author kert
*/
private function insertInfoByMaster(string $masterIp, int $port): int
{
$masterRedis = new \Redis();
$masterRedis->connect($masterIp, $port);
return $masterRedis->lPush('sentinel', time());
}
/**
* 向所有的從節(jié)點(diǎn)獲取數(shù)據(jù)
* @param string $slaveIp
* @param int $port
* @author kert
*/
private function getInfoBySlave(string $slaveIp, int $port)
{
$slaveRedis = new \Redis();
$slaveRedis->connect($slaveIp, $port);
$array = $slaveRedis->lRange('sentinel', 0, 1000);
echo "從節(jié)點(diǎn){$slaveIp},端口號{$port}獲取到的對應(yīng)數(shù)據(jù)為:" . PHP_EOL;
dump($array);
}
}
通過訪問該代碼,得到如下結(jié)果:

改代碼在實(shí)際的生產(chǎn)中,肯定使用時(shí)不對的,這里只是為了演示代碼如何操作哨兵。
其中的操作邏輯大致如下圖:
Laravel框架配置哨兵
Laravel框架自帶Redis操作類。我們只需要簡單配置即可。找到config/database.php文件。設(shè)置如下配置信息即可:
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'predis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [
'tcp://192.168.2.102:8005',
'tcp://192.168.2.102:8006',
'tcp://192.168.2.102:8007', //這3個(gè)都是sentinel節(jié)點(diǎn)的地址
'options' => [
'replication' => 'sentinel',
'service' => env('REDIS_SENTINEL_SERVICE', 'mymaster'), //sentinel
'parameters' => [
'password' => env('REDIS_PASSWORD', null), //redis的密碼,沒有時(shí)寫null
'database' => 0,
],
],
'database' => env('REDIS_DB', 0),
],
],
接下來就可以直接操作Redis數(shù)據(jù)了。
public function laravelRedis()
{
var_dump(Redis::connection()->set(time(), time()));
}
// output
object(Predis\Response\Status)#237 (1) {
["payload":"Predis\Response\Status":private]=>
string(2) "OK"
}
推薦閱讀
Redis數(shù)據(jù)類型應(yīng)用場景總結(jié)
相關(guān)推薦
