Redis 哨兵模式

Redis哨兵模式

一、哨兵模式介绍:

        哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。


二、哨兵模式运行方式:

        1、通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

        2、当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

        3、一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

        4、故障切换:假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。


三、主观下线和客观下线

    1、主观下线:主观下线:单个sentinel节点对Redis节点通信失败的“偏见”。这是一种主观下线。因为在复杂的网络环境下,这个sentinel与这个master不通,但是如果master与其他的sentinel都是通的呢?所以是一种“偏见”。这是依靠的第三种定时:每秒去ping一下周围的sentinel和Redis。对于slave Redis,可以使用这个主观下线,因为他不需要进行故障转移;但是对于master Redis,必须使用客观下线。

    2、客观下线:客观下线:所有sentinel节点对master Redis节点失败“达成共识”(超过quorum个则统一,quorum可配置)。这是依靠的第二种定时:每两秒,sentinel之间进行“商量”(一个 sentinel 可以通过向另一个 sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线。)对于master redis的下线,必须要达成共识才可以,因为涉及故障转移,仅仅依靠一个sentinel判断是不够的.

    3、领导者的选举:当sentinel集群需要故障转移的时候会在集群中选出Leader执行故障转移操作。sentinel采用了Raft协议实现了sentinel间选举Leader的算法,不过也不完全跟论文描述的步骤一致。sentinel集群运行过程中故障转移完成,所有sentinel又会恢复平等。Leader仅仅是故障转移操作出现的角色。

    4、选举流程:

        1、某个sentinel认定master客观下线的节点后,该sentinel会先看看自己有没有投过票,如果自己已经投过票给其他sentinel了,在2倍故障转移的超时时间自己就不会成为Leader。相当于它是一个Follower。

        2、如果该sentinel还没投过票,那么它就成为Candidate。

        3、和Raft协议描述的一样,成为Candidate,sentinel需要完成几件事情
            3.1 更新故障转移状态为start
            3.2 当前epoch加1,相当于进入一个新term,在sentinel中epoch就是Raft协议中的term。
            3.3 更新自己的超时时间为当前时间随机加上一段时间,随机时间为1s内的随机毫秒数。
            3.4 向其他节点发送is-master-down-by-addr命令请求投票。命令会带上自己的epoch。
            3.5 给自己投一票,在sentinel中,投票的方式是把自己master结构体里的leader和leader_epoch改成投给的sentinel和它的epoch。

        4、其他sentinel会收到Candidate的is-master-down-by-addr命令。如果sentinel当前epoch和Candidate传给他的epoch一样,说明他已经把自己master结构体里的leader和leader_epoch改成其他Candidate,相当于把票投给了其他Candidate。投过票给别的sentinel后,在当前epoch内自己就只能成为Follower。

        5、Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配置的quorum(quorum可以参考《redis sentinel设计与实现》)。sentinel比Raft协议增加了quorum,这样一个sentinel能否当选Leader还取决于它配置的quorum。

        6、如果在一个选举时间内,Candidate没有获得超过一半且超过它配置的quorum的票数,自己的这次选举就失败了。

        7、如果在一个epoch内,没有一个Candidate获得更多的票数。那么等待超过2倍故障转移的超时时间后,Candidate增加epoch重新投票。

        8、如果某个Candidate获得超过一半且超过它配置的quorum的票数,那么它就成为了Leader。

        9、与Raft协议不同,Leader并不会把自己成为Leader的消息发给其他sentinel。其他sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下线的标识,从而不需要进入故障转移流程。


    5、故障转移过程:

        1、多个sentinel发现并确认了master有问题

        2、接着会选举出一个sentinel作为领导

        3、再选举出一个slave作为master

        4、通知其余的slave,新的master是谁

        5、通知客户端一个主从的变化

        6、最后,sentinel会等待旧的master复活,然后将新master成为slave

        

        那么,如何选择“合适”的slave节点呢?

        7选择slave-priority(slave节点优先级,人为配置)最高的slave节点,如果存在则返回,不存在则继续。

        8、其次会选择复制偏移量最大的slave节点(复制得最完整),如果存在则返回,不存在则继续

        9、最后会选择run_id最小的slave节点(启动最早的节点)


四、需要注意的问题:

        1、尽可能在不同物理机上和同一个网络部署Redis sentinel的所有节点

        2、Redis sentinel中的sentinel节点个数应该大于等于3且最好是奇数。(节点数多可以保证高可用)

        3、Redis sentinel中的数据节点和普通数据节点没有区别。每个sentinel节点在本质上还是一个Redis实例,只不过和Redis数据节点不同的是,其主要作用是监控Redis数据节点

        4、客户端初始化时连接的是sentinel节点集合,不再是具体的Redis节点,但sentinel只是配置中心不是代理。


五、Redis哨兵模式搭建:

    1、搭建一个一主多从的Redis集群,具体方式在 https://scarecrow.top/seeview?aid=112 这篇博文中有详细讲解。这里就模拟搭建3台服务器1主2从,他们的端口分别为 9001(主)、9002(从)、9003(从)。

    2、编辑配置文件在redis/sentinel.conf:

这里先来详细讲解一下配置文件:
# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
#  监控mymaster(可自定义-但只能包括A-z 0-9和”._-”),注意quorum只影响ODOWN的判断,但是不影响failover,发生failover的条件必须是半数sentinel认为老Master已经ODOWN。此参数建议设置为sentinel/2+1的数值,否则可能会产生脑裂。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
  sentinel monitor mymaster 127.0.0.1 6379 2
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
 
 
 
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
 sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

这里只需要改变这几项就可开始测试哨兵了:(复制多份配置文件,然后修改以下配置,每个配置文件的端口需要修改,如果没有密码就不需要配置密码)

# 禁止保护模式
protected-mode no
# 监听端口
port 9004
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456

 

接下来就启动主从服务之后,就依次启动三个哨兵,如果正常启动则哨兵模式就正常启动了。

当你手动关闭主服务器时,哨兵会选取一个新的服务当做主服务器,即使现在恢复之前的主服务器它也只是变为新的主服务器的从服务器。


六、PHP操作哨兵模式的方式:

phpredis扩展提供了连接并回去哨兵集群信息的方法:

//初始化redis对象
$redis = new Redis();
//连接sentinel服务 host为ip,port为端口,哨兵的ip和端口号,这里连接任意一哨兵节点
//都可以
$redis->connect('192.168.243.128', 9004);

//获取主库列表及其状态信息
$result = $redis->rawCommand('SENTINEL', 'masters');

//根据所配置的主库redis名称获取对应的信息
//master_name应该由运维告知(也可以由上一步的信息中获取)
$result = $redis->rawCommand('SENTINEL', 'master', $master_name);

//根据所配置的主库redis名称获取其对应从库列表及其信息
$result = $redis->rawCommand('SENTINEL', 'slaves', $master_name);

//获取特定名称的redis主库地址
$result = $redis->rawCommand('SENTINEL', 'get-master-addr-by-name', $master_name);


我根据官方提供的方法封装了一个快速写入、获取数据的方案:redisSentinel.php

注意:使用此方法必须保证外网可以访问所有的节点(包括sentinel节点、redis主节点、redis从节点)

<?php
class redisSentinel{

   protected $host = '';

   protected $port = 6379;

   protected $redisObj = '';

   public function __construct($host = '127.0.0.1', $port=6379)
   {
      $this->host = $host;
      $this->port = $port;
      $this->redisObj = new Redis();
      $this->getSentinelObj();
   }

   protected function getSentinelObj() {
      try {
         $this->redisObj->connect($this->host, $this->port);
         return $this->redisObj;
      } catch (Throwable $e) {
         die($e->getMessage());
      }
   }

   protected function getMasterServerInfo($obj = '') {
      if (!$obj) {
         $obj = $this->getSentinelObj();
      }
      $mastersList = $obj->rawCommand('SENTINEL', 'masters');
      $mastersObj = $mastersList[0] ?? false;
      if ($mastersObj === false) {
         var_dump("无法获取到主节点");
         die();
      }
      return (array)$mastersObj;
   }

   protected function getSlaveServerInfo($obj = '') {
      if (!$obj) {
         $obj = $this->getSentinelObj();
      }
      $slaveList = $obj->rawCommand('SENTINEL', 'slaves', 'mymaster');
      $iCnt = mt_rand(0, count($slaveList)-1);
      $slaveObj = $slaveList[$iCnt] ?? false;
      return $slaveObj;
   }

   public function getSetMasterObj() {
      $obj = $this->getMasterServerInfo();
      try {
         $this->redisObj->connect($obj[3], $obj[5]);
         return $this->redisObj;
      } catch (Throwable $e) {
         var_dump($e->getMessage());
         die();
      }
   }

   public function getGetSlaveObj() {
      $obj = $this->getSlaveServerInfo();
      if ($obj == false) {
         echo "从节点无法获取,使用主节点获取数据";
         return $this->getSetMasterObj();
      }
      try {
         $this->redisObj->connect($obj[3], $obj[5]);
         return $this->redisObj;
      } catch (Throwable $e) {
         var_dump($e->getMessage());
         die();
      }
   }
}

使用例子 redis.php

<?php
include "redisSentinel.php";
//注意这里传入的IP PORT是哨兵sentinel节点的地址不是redis服务器的地址
$m = new redisSentinel('192.168.243.128', 9004);
$a=$m->getSetMasterObj();
   $a->set('LOVE', 'PQSLOVEGQQ');
$b = $m->getGetSlaveObj();
echo $b->get('LOVE');


以上部分内容来自互联网,如有不正确之处,请联系我我会尽快修改,谢谢

阅读数:245
如有疑问请与我联系:点击与我联系