文章简介

本文将通过实践+实际的形式从头到尾总结Redis中的哨兵机制。文章内容从主从复制的弊病如何解决弊病什么是哨兵哨兵监控的图形构造哨兵监控的原理如何配置哨兵哨兵与主从复制的关系等方面来演示。

文中相干材料下载地址:链接: https://pan.baidu.com/s/1cDV9... 明码: mv86

主从复制弊病

下面的图形构造,大抵的能够了解为Redis的主从复制拓扑图。

  1. 其中1个主节点负责利用零碎的写入数据,另外的4个从节点负责利用零碎的读数据。
  2. 同时4个从节点向其中的1个一个主节点发动复制申请操作。

在Redis服务运行失常的状况下,该拓扑结构造不会呈现什么问题。试想一下这样的一个场景。如果主节点服务产生了异样,不能失常解决服务(如写入数据、主从复制操作)。这时候,Redis服务能失常响应利用零碎的读操作,然而没法进行写操作。 呈现该状况就会重大影响到零碎的业务数据。那该如何解决呢?

能够大抵想到上面的几种状况来解决。

  1. 当主节点产生异常情况时,手动的从局部从节点中抉择一个节点作为主节点。而后扭转其余从节点的主从复制关系。
  2. 咱们也能够写一套主动解决该状况的服务,防止依赖于人为的操作。

下面的计划在肯定水平上是能帮忙咱们解决问题。然而过多的人为干涉。例如第1点,咱们须要思考人工解决的实时性和正确性。第2点,自动化解决是可能很好的解决第1点中的问题,然而主动解决存在如何抉择新主节点的问题,因而这也是一个不好的中央。

通过下面大抵的剖析,咱们不难得出Redis的哨兵机制就是针对种种问题呈现的。

什么是哨兵

能够把Redis的哨兵了解为一种Redis分布式架构。 该架构中次要存在两种角色,一种是哨兵,另外一种是数据节点(主从复制节点)。

哨兵次要负责的工作是:

  1. 每一个哨兵都会监控数据节点以及其余的哨兵节点。
  2. 当其中的一个哨兵监控到节点不可达是,会给对应的节点做下线标识。如果下线的节点为主节点。这时候会告诉其余的哨兵节点。
  3. 哨兵节点通过“协商”推举出从节点中的某一个节点为主节点。
  4. 接着将其余的从节点断开与旧主节点的复制关系,将推举进去的新主节点作为从节点的主节点。
  5. 将切换的后果告诉给利用零碎。

配置哨兵

在演示环境中,配置了三台数据节点(1主2从),三台哨兵节点。演示中用到的Redis为6.0.8版本。

角色IP端口号
(数据节点)master127.0.0.18002
(数据节点)slave127.0.0.18003
(数据节点)slave127.0.0.18004
哨兵节点127.0.0.18005
哨兵节点127.0.0.18006
哨兵节点127.0.0.18007
  1. (数据节点)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 1save 20 1save 30 1# 2.当bgsave命令产生谬误时,进行写入操作。stop-writes-on-bgsave-error yes# 3.是否开启rbd文件压缩rdbcompression yes
  1. (数据节点)slave配置。
# 服务配置daemonize yesport 8004dir "/Users/kert/config/redis/8004"logfile "8004.log"# 多线程# 1.开启线程数。io-threads 2# 2.开启读线程。io-threads-do-reads yes# 长久化存储(RDB)# 1.每多少秒至多有多少个key发生变化,则执行save命令。save 10 1save 20 1save 30 1# 2.当bgsave命令产生谬误时,进行写入操作。stop-writes-on-bgsave-error yes# 3.是否开启rbd文件压缩rdbcompression yes# 配置主节点信息replicaof 127.0.0.1 8002
  1. 哨兵节点配置。
# 端口号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.confkert@kertdeMacBook-Pro-2  ~/config/redis/8004  redis-server ./redis.conf// 启动哨兵节点kert@kertdeMacBook-Pro-2  ~/config/redis/sentinel  redis-sentinel 8007.confkert@kertdeMacBook-Pro-2  ~/config/redis/sentinel  redis-sentinel 8006.confkert@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. 咱们在1号终端写入数据。
127.0.0.1:8002> set name tonyOK127.0.0.1:8002> set age 1OK127.0.0.1:8002> set socre 1OK127.0.0.1:8002>
  1. 接着在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节点的服务。

咱们实现查看一下哨兵节点的一个状态信息。

  1. 查看哨兵端口为8005的节点。
kert@kertdeMacBook-Pro-2  ~  redis-cli -p 8005 info# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0:name=mymaster,status=ok,address=127.0.0.1:8002,slaves=2,sentinels=3
  1. 查看哨兵端口为8006的节点。
kert@kertdeMacBook-Pro-2  ~  redis-cli -p 8006 info# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0:name=mymaster,status=ok,address=127.0.0.1:8002,slaves=2,sentinels=3
  1. 查看哨兵端口为8007的节点。
kert@kertdeMacBook-Pro-2  ~  redis-cli -p 8007 info# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0: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# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0:name=mymaster,status=ok,address=127.0.0.1:8004,slaves=2,sentinels=3
通过下面的查问后果,咱们能够看到address的值编程了8004端口了,其余的信息没有产生扭转,阐明哨兵曾经实现切换工作。

接下来咱们在新的主节点执行操作命令,查看在从节点是否可能实现主从复制。

  1. 在3号端口(新的master)执行一个del命令。
127.0.0.1:8004> del age(integer) 1127.0.0.1:8004> keys *1) "name"2) "socre"
  1. 在2号端口执行读命令。
127.0.0.1:8003> keys *1) "socre"2) "name"
此时能够发现咱们的主从复制也是失常的。
  1. 启动旧的master,并执行读命令。
 kert@kertdeMacBook-Pro-2  ~/config/redis/8002  redis-server ./redis.conf kert@kertdeMacBook-Pro-2  ~/config/redis/8002  redis-cli -p 8002127.0.0.1:8002> keys *1) "name"2) "socre"
此时你也会发现,原来的master节点变成了slave节点,并且可能失常复制新master节点的数据。

配置文件比照

在咱们启动了哨兵模式之后,咱们的哨兵配置文件和数据节点配置文件的内容都会主动的生成一个特定的内容。

  1. 数据节点(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 1save 20 1save 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 1save 20 1save 30 1# 2.当bgsave命令产生谬误时,进行写入操作。stop-writes-on-bgsave-error yes# 3.是否开启rbd文件压缩rdbcompression yes# Generated by CONFIG REWRITEpidfile "/var/run/redis.pid"user default on nopass ~* +@allreplicaof 127.0.0.1 8004
  1. 哨兵节点

变动前

# 端口号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 REWRITEprotected-mode nouser default on nopass ~* +@allsentinel leader-epoch mymaster 3sentinel known-replica mymaster 127.0.0.1 8002sentinel known-replica mymaster 127.0.0.1 8003sentinel known-sentinel mymaster 127.0.0.1 8006 8fbd2cce642c881f752775afee9b3591e0d90dc6sentinel known-sentinel mymaster 127.0.0.1 8007 69530c74791e5f32db1c2a006c826a6463bc6496sentinel current-epoch 3pidfile "/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()));}
// outputobject(Predis\Response\Status)#237 (1) {  ["payload":"Predis\Response\Status":private]=>  string(2) "OK"}