文章简介
本文将通过实践+实际的形式从头到尾总结Redis中的哨兵机制。文章内容从主从复制的弊病、如何解决弊病、什么是哨兵、哨兵监控的图形构造、哨兵监控的原理、如何配置哨兵、哨兵与主从复制的关系等方面来演示。
文中相干材料下载地址:链接: https://pan.baidu.com/s/1cDV9… 明码: mv86
主从复制弊病
下面的图形构造,大抵的能够了解为Redis的主从复制拓扑图。
- 其中1个主节点负责利用零碎的写入数据,另外的4个从节点负责利用零碎的读数据。
- 同时4个从节点向其中的1个一个主节点发动复制申请操作。
在Redis服务运行失常的状况下,该拓扑结构造不会呈现什么问题。试想一下这样的一个场景。如果主节点服务产生了异样,不能失常解决服务(如写入数据、主从复制操作)。这时候,Redis服务能失常响应利用零碎的读操作,然而没法进行写操作。 呈现该状况就会重大影响到零碎的业务数据。那该如何解决呢?
能够大抵想到上面的几种状况来解决。
- 当主节点产生异常情况时,手动的从局部从节点中抉择一个节点作为主节点。而后扭转其余从节点的主从复制关系。
- 咱们也能够写一套主动解决该状况的服务,防止依赖于人为的操作。
下面的计划在肯定水平上是能帮忙咱们解决问题。然而过多的人为干涉。例如第1点,咱们须要思考人工解决的实时性和正确性。第2点,自动化解决是可能很好的解决第1点中的问题,然而主动解决存在如何抉择新主节点的问题,因而这也是一个不好的中央。
通过下面大抵的剖析,咱们不难得出Redis的哨兵机制就是针对种种问题呈现的。
什么是哨兵
能够把Redis的哨兵了解为一种Redis分布式架构。 该架构中次要存在两种角色,一种是哨兵,另外一种是数据节点(主从复制节点)。
哨兵次要负责的工作是:
- 每一个哨兵都会监控数据节点以及其余的哨兵节点。
- 当其中的一个哨兵监控到节点不可达是,会给对应的节点做下线标识。如果下线的节点为主节点。这时候会告诉其余的哨兵节点。
- 哨兵节点通过“协商”推举出从节点中的某一个节点为主节点。
- 接着将其余的从节点断开与旧主节点的复制关系,将推举进去的新主节点作为从节点的主节点。
- 将切换的后果告诉给利用零碎。
配置哨兵
在演示环境中,配置了三台数据节点(1主2从),三台哨兵节点。演示中用到的Redis为6.0.8版本。
角色 | IP | 端口号 |
---|---|---|
(数据节点)master | 127.0.0.1 | 8002 |
(数据节点)slave | 127.0.0.1 | 8003 |
(数据节点)slave | 127.0.0.1 | 8004 |
哨兵节点 | 127.0.0.1 | 8005 |
哨兵节点 | 127.0.0.1 | 8006 |
哨兵节点 | 127.0.0.1 | 8007 |
- (数据节点)master配置。
# 服务配置
daemonize yes
# 端口号
port 8002
# 数据目录
dir "/Users/kert/config/redis/8002"
# 日志文件名称
logfile "8002.log"
# 设置明码
bind 0.0.0.0
# requirepass 8002
# 多线程
# 1.开启线程数。
io-threads 2
# 2.开启读线程。
io-threads-do-reads yes
# 长久化存储(RDB)
# 1.每多少秒至多有多少个key发生变化,则执行save命令。
save 10 1
save 20 1
save 30 1
# 2.当bgsave命令产生谬误时,进行写入操作。
stop-writes-on-bgsave-error yes
# 3.是否开启rbd文件压缩
rdbcompression yes
- (数据节点)slave配置。
# 服务配置
daemonize yes
port 8004
dir "/Users/kert/config/redis/8004"
logfile "8004.log"
# 多线程
# 1.开启线程数。
io-threads 2
# 2.开启读线程。
io-threads-do-reads yes
# 长久化存储(RDB)
# 1.每多少秒至多有多少个key发生变化,则执行save命令。
save 10 1
save 20 1
save 30 1
# 2.当bgsave命令产生谬误时,进行写入操作。
stop-writes-on-bgsave-error yes
# 3.是否开启rbd文件压缩
rdbcompression yes
# 配置主节点信息
replicaof 127.0.0.1 8002
- 哨兵节点配置。
# 端口号
port 8006
# 运行模式
daemonize yes
# 数据目录
dir "/Users/kert/config/redis/sentinel/8006"
# 日志文件
logfile "8006.log"
# 监听数据节点
sentinel monitor mymaster 127.0.0.1 8002 2(断定主节点下线状态的票数)
# 设置主节点连贯权限信息
sentinel auth-pass mymaster 8002
# 判断数据节点和sentinel节点多少毫秒数内没有响应ping,则解决为下线状态
sentinel down-after-milliseconds mymaster 30000
# 主节点下线后,从节点向新的主节点发动复制的个数限度(指的一次同时容许几个从节点)。
sentinel parallel-syncs mymaster 1
# 故障转移超时工夫
sentinel failover-timeout mymaster 180000
所有的哨兵节点间接将port、dir和logfile批改为对应的具体哨兵信息即可。
接着启动对应的服务Redis服务。
// 启动master节点
kert@kertdeMacBook-Pro-2 ~/config/redis/8002 redis-server ./redis.conf
// 启动slave节点
kert@kertdeMacBook-Pro-2 ~/config/redis/8003 redis-server ./redis.conf
kert@kertdeMacBook-Pro-2 ~/config/redis/8004 redis-server ./redis.conf
// 启动哨兵节点
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服务运行状态。
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]
看到下面的后果,则示意咱们的Redis服务曾经失常启动。
演示故障切换
咱们先关上三个终端,调配时master节点和两个slave节点。检测是否可能失常进行主从复制。
咱们在主节点任意写入一些数据,而后在从节点进行查问数据。为了不便,前面将master称作1号终端,两个slave调配叫做2号和3号终端。
- 咱们在1号终端写入数据。
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号终端上面执行如下的查问操作。
127.0.0.1:8003> get name
"tony"
127.0.0.1:8003> get age
"1"
127.0.0.1:8003> get socre
"1"
事实证明咱们的主从复制是胜利的,接下来咱们就停掉master节点的服务。
咱们实现查看一下哨兵节点的一个状态信息。
- 查看哨兵端口为8005的节点。
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的节点。
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的节点。
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
通过下面的几个状态信息,咱们能够看到哨兵检测的主节点信息,主节点上面有几个从节点,同时哨兵节点有几个。
咱们杀掉master的过程。能够看到1号端口主动断开了连贯。
接着咱们通过哨兵机制查看一下数据节点状态信息。
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
通过下面的查问后果,咱们能够看到address的值编程了8004端口了,其余的信息没有产生扭转,阐明哨兵曾经实现切换工作。
接下来咱们在新的主节点执行操作命令,查看在从节点是否可能实现主从复制。
- 在3号端口(新的master)执行一个del命令。
127.0.0.1:8004> del age
(integer) 1
127.0.0.1:8004> keys *
1) "name"
2) "socre"
- 在2号端口执行读命令。
127.0.0.1:8003> keys *
1) "socre"
2) "name"
此时能够发现咱们的主从复制也是失常的。
- 启动旧的master,并执行读命令。
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"
此时你也会发现,原来的master节点变成了slave节点,并且可能失常复制新master节点的数据。
配置文件比照
在咱们启动了哨兵模式之后,咱们的哨兵配置文件和数据节点配置文件的内容都会主动的生成一个特定的内容。
- 数据节点(master间隔)。
变动前
# 服务配置
daemonize yes
# 端口号
port 8002
# 数据目录
dir "/Users/kert/config/redis/8002"
# 日志文件名称
logfile "8002.log"
# 设置明码
bind 0.0.0.0
# requirepass 8002
# 多线程
# 1.开启线程数。
io-threads 2
# 2.开启读线程。
io-threads-do-reads yes
# 长久化存储(RDB)
# 1.每多少秒至多有多少个key发生变化,则执行save命令。
save 10 1
save 20 1
save 30 1
# 2.当bgsave命令产生谬误时,进行写入操作。
stop-writes-on-bgsave-error yes
# 3.是否开启rbd文件压缩
rdbcompression yes
变动后
# 服务配置
daemonize yes
# 端口号
port 8002
# 数据目录
dir "/Users/kert/config/redis/8002"
# 日志文件名称
logfile "8002.log"
# 设置明码
bind 0.0.0.0
# requirepass 8002
# 多线程
# 1.开启线程数。
io-threads 2
# 2.开启读线程。
io-threads-do-reads yes
# 长久化存储(RDB)
# 1.每多少秒至多有多少个key发生变化,则执行save命令。
save 10 1
save 20 1
save 30 1
# 2.当bgsave命令产生谬误时,进行写入操作。
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
- 哨兵节点
变动前
# 端口号
port 8006
# 运行模式
daemonize yes
# 数据目录
dir "/Users/kert/config/redis/sentinel/8006"
# 日志文件
logfile "8006.log"
# 监听数据节点
sentinel monitor mymaster 127.0.0.1 8002 2(断定主节点下线状态的票数)
# 设置主节点连贯权限信息
sentinel auth-pass mymaster 8002
# 判断数据节点和sentinel节点多少毫秒数内没有响应ping,则解决为下线状态
sentinel down-after-milliseconds mymaster 30000
# 主节点下线后,从节点向新的主节点发动复制的个数限度(指的一次同时容许几个从节点)。
sentinel parallel-syncs mymaster 1
# 故障转移超时工夫
sentinel failover-timeout mymaster 180000
变动后
# 端口号
port 8005
# 运行模式
daemonize yes
# 数据目录
dir "/Users/kert/config/redis/sentinel/8005"
# 日志文件
logfile "8005.log"
# 监听数据节点
sentinel myid 5724fd60af87e728e6f8f03ded693960c983e156
# 判断数据节点和sentinel节点多少毫秒数内没有响应ping,则解决为下线状态
sentinel deny-scripts-reconfig yes
# 主节点下线后,从节点向新的主节点发动复制的个数限度(指的一次同时容许几个从节点)。
sentinel monitor mymaster 127.0.0.1 8004 2
# 故障转移超时工夫
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"
实战代码
这里咱们应用PHP原生类操作Redis哨兵,首先咱们创立一个Redis操作类,类中代码如下:
class OperationRedis
{
private $redis;
private $requestParams;
private $redisHandler;
/**
* 本机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节点获取所有主节点 */
$masterLists = $this->redisHandler->rawCommand('SENTINEL', 'masters');
dump('master列表配置信息', $masterLists);
foreach ($masterLists as $value) {
/** @var array $masterInfo 主节点信息 */
$masterInfo = $this->redisHandler->rawCommand('SENTINEL', 'master', $value[1]);
dump('master节点信息', $masterInfo);
// 向主节点插入数据
$insertNumber = $this->insertInfoByMaster((string)$this->redisHost, (int)$value[5]);
dump('Redis队列数量', $insertNumber);
/** @var array $slaveLists 主节点上面的从节点信息 */
$slaveLists = $this->redisHandler->rawCommand('SENTINEL', 'slaves', $value[1]);
dump('master下的slave节点信息', $slaveLists);
foreach ($slaveLists as $v) {
$this->getInfoBySlave((string)$this->redisHost, (int)$v[5]);
}
}
}
/**
* 向主节点插入数据
* @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());
}
/**
* 向所有的从节点获取数据
* @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 "从节点{$slaveIp},端口号{$port}获取到的对应数据为:" . PHP_EOL;
dump($array);
}
}
通过拜访该代码,失去如下后果:
改代码在理论的生产中,必定应用时不对的,这里只是为了演示代码如何操作哨兵。
其中的操作逻辑大抵如下图:
Laravel框架配置哨兵
Laravel框架自带Redis操作类。咱们只须要简略配置即可。找到config/database.php文件。设置如下配置信息即可:
'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个都是sentinel节点的地址
'options' => [
'replication' => 'sentinel',
'service' => env('REDIS_SENTINEL_SERVICE', 'mymaster'), //sentinel
'parameters' => [
'password' => env('REDIS_PASSWORD', null), //redis的明码,没有时写null
'database' => 0,
],
],
'database' => env('REDIS_DB', 0),
],
],
接下来就能够间接操作Redis数据了。
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"
}
发表回复