Redis 哨兵Sentinel实现的机制与原理?部署方法?

1.哨兵Sentinel概述

1.概述

Redis Sentinel是一个分布式系统,为Redis提供高可用性解决方案。可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来 接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故 障迁移, 以及选择哪个从服务器作为新的主服务器。

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance) 该系统执行以下三个任务:

  • 监控(Monitoring): Sentinel 会不断地定期检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automaticfailover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中 一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客 户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主 服务器代替失效服务器。

2. 哨兵Sentinel 工作原理分析

(1)哨兵文件详解

配置一:sentinel monitor <master-name> <ip> <port> <quorum>

 这个配置表达的是 哨兵节点定期监控 名字叫做 <master-name>  并且 IP 为 <ip> 端口号为 <port> 的主节点。<quorum> 表示的是哨兵判断主节点是否发生故障的票数。也就是说如果我们将<quorum>设置为2就代表至少要有两个哨兵认为主节点故障了,才算这个主节点是客观下线的了,一般是设置为sentinel节点数的一半加一。

配置二:sentinel down-after-milliseconds <master-name> <times>

 每个哨兵节点会定期发送ping命令来判断Redis节点和其余的哨兵节点是否是可达的,如果超过了配置的<times>时间没有收到pong回复,就主观判断节点是不可达的,<times>的单位为毫秒。

配置三:sentinel parallel-syncs <master-name> <nums>

 当哨兵节点都认为主节点故障时,哨兵投票选出的leader会进行故障转移,选出新的主节点,原来的从节点们会向新的主节点发起复制,这个配置就是控制在故障转移之后,每次可以向新的主节点发起复制的节点的个数,最多为<nums>个,因为如果不加控制会对主节点的网络和磁盘IO资源很大的开销。

配置四:sentinel failover-timeout <master-name>  <times>

 这个代表哨兵进行故障转移时如果超过了配置的<times>时间就表示故障转移超时失败。

配置五: sentinel auth-pass <master-name> <password>

 如果主节点设置了密码,则需要这个配置,否则哨兵无法对主节点进行监控。

(2)为什么要用到哨兵

哨兵(Sentinel)主要是为了解决在主从复制架构中出现宕机的情况,主要分为两种情况:

1).从Redis宕机

这个相对而言比较简单,在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据。在Redis2.8版本后,主从断线后恢复
的情况下实现增量复制。

2).主Redis宕机

这个相对而言就会复杂一些,需要以下2步才能完成
a. 在从数据库中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务
b. 第二步,将主库重新启动后,执行SLAVEOF命令,将其设置为其他库的从库,这时数据就能更新回来

由于这个手动完成恢复的过程其实是比较麻烦的并且容易出错,所以Redis提供的哨兵(sentinel)的功能来解决

(3)哨兵机制(sentinel)的高可用

Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

(4)哨兵的定时监控

任务1:每个哨兵节点每10秒会向主节点和从节点发送info命令获取最拓扑结构图,哨兵配置时只要配置对主节点的监控即可,通过向主节点发送info,获取从节点的信息,并当有新的从节点加入时可以马上感知到

任务2:每个哨兵节点每隔2秒会向redis数据节点的指定频道上发送该哨兵节点对于主节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解其它哨兵节点的信息及对主节点的判断,其实就是通过消息publish和subscribe来完成的

任务3:每隔1秒每个哨兵会向主节点、从节点及其余哨兵节点发送一次ping命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据

主观下线:所谓主观下线,就是单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。

 

sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复,还是无效回复来判断实例时候在线(对该sentinel来说是“主观在线”)。

 

sentinel配置文件中的down-after-milliseconds设置了判断主观下线的时间长度,如果实例在down-after-milliseconds毫秒内,返回的都是无效回复,那么sentinel回认为该实例已(主观)下线,修改其flags状态为SRI_S_DOWN。如果多个sentinel监视一个服务,有可能存在多个sentinel的down-after-milliseconds配置不同,这个在实际生产中要注意。

客观下线:当主观下线的节点是主节点时,此时该哨兵3节点会通过指令sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的判断,如果其他的哨兵也认为主节点主观线下了,则当认为主观下线的票数超过了quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线

(5)哨兵lerder选举流程

如果主节点被判定为客观下线之后,就要选取一个哨兵节点来完成后面的故障转移工作,选举出一个leader的流程如下:

a)每个在线的哨兵节点都可以成为领导者,当它确认(比如哨兵3)主节点下线时,会向其它哨兵发is-master-down-by-addr命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移;
b)当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;
c)如果哨兵3发现自己在选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举…………

(6)自动故障转移机制

在从节点中选择新的主节点

sentinel状态数据结构中保存了主服务的所有从服务信息,领头sentinel按照如下的规则从从服务列表中挑选出新的主服务

  1. 过滤掉主观下线的节点 
  2. 选择slave-priority最高的节点,如果由则返回没有就继续选择
  3. 选择出复制偏移量最大的系节点,因为复制便宜量越大则数据复制的越完整,如果由就返回了,没有就继续
  4. 选择run_id最小的节点

更新主从状态

通过slaveof no one命令,让选出来的从节点成为主节点;并通过slaveof命令让其他节点成为其从节点。

 将已下线的主节点设置成新的主节点的从节点,当其回复正常时,复制新的主节点,变成新的主节点的从节点

同理,当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主的从

3.Sentinel获取服务器信息

(1) Sentinel获取主服务器信息

Sentinel默认会以每10秒一次的频率,通过命令连接向主服务器发送info命令,通过分析info命令的回复来获取主服务器的当前信息,就像在上篇讲到的复制功能,在客户端输入info replication 命令一样,Sentinel可以获取以下两方面的信息:

(1) 关于主服务器本身的信息,包括服务器run_id,role的服务器角色。

(2) 关于所有从服务器的信息,每个从服务器都由一个slave字符串开头的行记录,记录了从服务器IP和端口(主服务器中有从库的配置信息)。

(2)Sentinel获取从服务器信息

当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构(sentinelRedisInstance)之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。Sentinel默认会以每10秒一次的频率通过命令连接从服务器发送info命令,通过分析info命令的回复来获取从服务器的当前信息。包括:从服务器运行run_ID、从服务器角色role、主服务器的ip及端口、主从服务器的连接状态master_link_status、从服务器的优先级slave_priority。

 (3)Sentinel向主从服务器发送信息

     在默认情况下, Sentinel会以每2秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:

这条命令向服务器的_sentinel_:hello频道发送了一条信息,信息的内容由多个参数组成:

(1) 以s_开头以参数记录的是sentinel本身的信息。

(2) 而m_开头的参数记录的则是主服务器的信息,如果sentinel正在监视的是主服务器,那么这些参数就是主服务器的信息,如果sentinel正在监视的是从服务器,那么这些参数记录就是从服务器正在复制的主服务器的信息。

 

参数

描述

S_ip

Sentinel的ip地址

S_port

Sentinel的端口号

S_runid

Sentinel的运行ID

S_epoch

Sentinel 的当前配置纪元

m_name

主服务器的名字

M_ip

主服务器的IP地址

M_port

主服务器的端口号

M_epoch

主服务器的当前配置纪元

 

以下是一条sentinel通过publish命令向主服务器发送的信息示例:

 

这个示例中sentinel的ip地址为172.0.0.1端口号为26379, 运行ID为后面一串,当前纪元为0。主服务器的名字为mymaster,ip地址为127.0.0.1,端口号为6379, 当前纪元为0。

 (4)sentinel接收来自主服务器和从服务器的频道信息

当sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:subscribe_sentinel_:hello 。对于每个与Sentinel连接的服务器,Sentinel既通过命令连向服务器的_sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_:hello频道接收信息。

    当有三个sentinel,分别是sentinel1、sentinel2 、sentinel3。三个sentinel在监视同一个服务器,那么当sentinel1向服务器的_sentinel_:hello频道发送一条信息时,所有订阅了_sentinel_:hello频道的sentinel(包括sentinel1自己在内)都会收到这条信息。

    当一个sentinel从_sentinel_:hello频道收到一条信息时,sentinel会对这条信息进行分析,提取出信息中sentinel 的 ip 、port、runID等8个参数,并进行以下检查:

(1) 如果信息中记录的sentinel运行ID和接收信息的sentinel运行ID相同,那么说明这条信息是sentinel自己发送的,sentinel将丢弃这条信息,不做进一步处理。

(2) 相反地,如果信息中记录的sentinel运行ID和接收信息的sentinel运行ID不相同,那说明这条信息监视同一个服务器的其它sentinel发来的,接收信息的sentinel将根据信息中的参数,对相应主服务器的实例结构进行更新。

 (5)sentinel更新自己的sentinels字典

sentinel为主服务器创建实例结构中的sentinels字典,保存了sentinel本身,还监视这个主服务器的其他sentinel的资料。当一个sentinel接收到其他sentinels发来的信息时,接收的sentinel会从信息中分析并提取出两方面参数:

(1)与sentinel有关的参数,包括sentinel的ip、port、runid、配置纪元。

(2)与主服务器有关的参数, 包括监视主服务器的ip、port、runid、配置纪元。

    假设分别有三个sentinel: 127.0.0.1:26379、127.0.0.1:26380、127.0.0.1:26381。三个sentinel正在监视主服务器127.0.0.1:6379, 那么当127.0.0.1:26379这个sentinel接收到以下消息时:

这个sentinel将执行以下动作:

(1) 第一条信息发送者为自己,信息忽略。

(2) 第二条信息发送者为26381, sentinel会根据信息提取出内容,对sentinels字典中26381对应的实例结构进行更新。

(3) 第三条信息发送者为23680,同样更新字典中的23680对应的实例结构。

    每个sentinel都有自己的一个sentinels字典, 对于26379的sentinel它的sentinels字典信息保存了26380和26381两个sentinel信息。其它sentinel也一样。

 (6)sentinel创建连向其他sentinel的命令连接

当sentinel通过频道信息发现一个新的sentinel时,不仅更新sentinels字典,还会创建一个连向sentinel命令连接,而新的sentinel也会创建连向这个sentinel的命令连接,最终监视同一个主服务器的多个sentinel将形成相互连接的网络。如下图所示:

 

4.Sentinel的工作原理总结

1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。

 2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。 

 3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。 

 4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。

 5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。

 6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。

 7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。 

 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

5.配置Sentinel 

1):开启端口:

查看端口开启情况:firewall-cmd --list-ports

开启端口:

firewall-cmd --zone=public --add-port=26379/tcp --permanent

firewall-cmd --zone=public --add-port=26381/tcp --permanent

firewall-cmd --zone=public --add-port=26382/tcp --permanent

重启防火墙:systemctl restart firewalld.service

2):修改sentinel.conf

修改另外一个26379哨兵的配置,

进入目录:cd /usr/local/redis-6379

编辑配置文件:vim /usr/local/redis-6379/sentinel.conf

修改如下:

port 26379

daemonize yes

pidfile /usr/local/redis-6379/run/redis-sentinel.pid

sentinel monitor mymaster 127.0.0.1 6379 2

其它的参数默认即可。

修改另外一个26381哨兵的配置

vim /usr/local/redis-6381/sentinel.conf

修改如下:

port 26381

daemonize yes

pidfile /usr/local/redis-6381/run/redis-sentinel.pid

sentinel monitor mymaster 127.0.0.1 6379 2

修改另外一个26382哨兵的配置

vim /usr/local/redis-6382/sentinel.conf

修改如下:

port 26382

daemonize yes(也可以先改为no进行调试,调试好之后改为yes)

pidfile /usr/local/redis-6382/run/redis-sentinel.pid

sentinel monitor mymaster 127.0.0.1 6379 2

3):启动sentinel

/usr/local/redis-6379/src/redis-sentinel /usr/local/redis-6379/sentinel.conf

/usr/local/redis-6381/src/redis-sentinel /usr/local/redis-6381/sentinel.conf

/usr/local/redis-6382/src/redis-sentinel /usr/local/redis-6382/sentinel.conf

警告:The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

解决:

vim /etc/sysctl.conf

加入:

net.core.somaxconn = 1024

执行:

sysctl -p

4):测试哨兵是否正常监控:

手动关闭master6379:

127.0.0.1:6379> shutdown

等待一段时间(大概一分钟),sentinel会重新选举Master节点。

6379 原来的Master节点重启重启后变成Slaver节点

重启6379:

/usr/local/redis-6379/src/redis-server /usr/local/redis-6379/redis.conf

客户端查看变化信息:

info replication

6.总结

  1. Master可读可写,Slaver只能读,不能写
  2. Master可以对应多个Slaver,但是数量越多压力越大,延迟就可能越严重
  3. Master写入后立即返回,几乎同时将写入异步同步到各个Slaver,所以基本上延迟可以忽略
  4. 可以通过slaveof no one命令将Slaver升级为Master(当Master挂掉时,手动将某个Slaver变为Master)
  5. 可以通过sentinel哨兵模式监控Master,当Master挂掉时自动选举Slaver变为Master,其它Slaver自动重连新的Master

7.参考资料

来源:https://www.cnblogs.com/Eugene-Jin/p/10819601.html

https://www.cnblogs.com/Eugene-Jin/p/10819601.html

https://blog.csdn.net/nuomizhende45/article/details/82831966

https://my.oschina.net/u/172871/blog/596976

https://www.cnblogs.com/qinghe123/p/9547884.html

https://www.cnblogs.com/jaycekon/p/6237562.html

https://blog.csdn.net/RobertoHuang/article/details/70768922

https://www.cnblogs.com/leeSmall/p/8398401.html

https://blog.csdn.net/u012240455/article/details/81843714

https://www.cnblogs.com/MrHSR/p/10119843.html

8、代码实现连接主库:

思路:把哨兵集群配置存放在数组里,再遍历这个数组去做连接试探,得到一个可用的哨兵连接再通过$redis->rawCommand('SENTINEL', 'get-master-addr-by-name', $master_name) 得到主库的ip和端口,即可

目前来说这个是适应性最强的方案,通过 轮询哨兵,获取主库,然后返回redis连接实例。在写实例代码的时候,最好做下本地文件缓存(分布式系统中一样实用),缓存下当前主库地址,连接的时候先从 本地文件缓存中读取配置信息,先ping下,看通不通,不通的时候再去轮询哨兵,更新新的文件缓存

  1. 所有哨兵地址 写到 .env 中,
  2. 封装redis 类,不要直接 new redis();
  3. 封装类执行流程如下:
  4. 检查本地缓存的 master ip port, 有则connect 下,失败则去轮询哨兵查询正式的主库地址,
//初始化redis对象
$redis = new \Redis();

//连接sentinel服务列表 host为哨兵的ip,port为哨兵的端口,采用轮询方式
$sentinel = array(
    array('host' => '192.168.1.221', 'port' => 26379),
    array('host' => '192.168.1.221', 'port' => 26381),
    array('host' => '192.168.1.221', 'port' => 26382)
);
foreach ($sentinel as $v) {
    $rel = $redis->connect($v['host'], $v['port']);
    if ($rel) {
        break;
    }
}

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


//根据所配置的主库redis名称获取对应的信息
//master_name应该由运维告知(也可以由上一步的信息中获取)
$master_name = 'mymaster';
$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);

$redis->connect($result[0], $result[1]);
var_dump($redis->get('name'));
//$redis->set('name', 'lldd123');
exit;
//以上部分可以获取到主库的ip和对应端口,程序可以直接像链接单台redis一样链接操作使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值