Redis哨兵模式(Sentinel、1主2从3哨兵6台服务器配置实战、客户端调用、日志解析、主观下线、客观下线、仲裁、脑裂问题、哨兵长与从节点投票选举过程与原理)

哨兵模式

  • 官方文档:https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel
  • 关联博客:Redis主从复制(下文能用到)
  • 极简概括:自动监控Redis主节点是否故障的一种方案,若主节点故障,则Redis会根据投票数自动将从库切换为主库(这个过程,叫仲裁)。
  • 解决问题:在主从复制的架构模式下,Redis主节点挂掉后,从节点无任何补偿操作,无人工干预的情况下导致整个缓存链路的写功能丧失。而哨兵模式有哨兵看守机制,可以做到主机的检测与自动切换从机为主机的功能。
  • 适用场景:对于需要7 * 24高可用,且公司原意投资相关运维成本的服务端应用。毕竟作为哨兵节点的Redis实例,将无法使用缓存服务,只能作为哨兵,而且要求哨兵数量往往是奇数个。
  • 优点:
    • 降低运维成本:强大的高可用机制,适当降低运维成本。
  • 缺点:
    • 场景限制:小型公司Redis都可以不用,中型公司也不一定能用到Redis主从,更何况是哨兵这么严谨的运维策略。
    • 资金问题:Redis需要堆多个服务器,对公司而言是不小的支出,有一定门槛。
    • 延迟问题:主节点挂掉时,虽然可以做到自动切换,但是多个哨兵认为Redis能够客观下线时,这个过程需要时间的,虽然可以调整,但是这个时间内的Redis写操作是失效的,因此有了集群策略。
    • 数据丢失问题:Redis主从是异步复制,哨兵只是增加了自动化的切换功能,没有像MySQL的redo log机制,无法保证数据100%不丢失。
    • 会引发脑裂问题(下文有说)。
  • 误区:哨兵是哨兵,集群是集群,两者无关。哨兵是主从复制架构的高可用优化方案,不是集群部署的高可用的方案。
  • 访问流程:由原先的编程语言客户端访问Redis主节点或从节点,变成了客户端访问哨兵节点(通常不止一个哨兵,有奇数个哨兵组成一个哨兵集群,奇数好投票),由哨兵节点告诉客户端访问那个主节点或从节点,从而区分读写操作。

实操(1主+2从+3哨)

  • 三哨理由:一个小区也不只一个保安,至少2个保安轮班倒。但是哨兵有类似投票机制,最好用奇数个哨兵。
  • 环境决策:部署3个哨兵+1个Master+2个Slave,共6台服务器。
  • Docker方案:当内存扛不住的时候可以用这个,但是拉镜像时发现被墙了(国内混蛋专家搞的混蛋规则),镜像拉不下来,本地也没有,弃用了这条路线。
  • 运行环境:CentOS 7.6,每个系统分配256M内存,Linux可轻松启动,共占用内存1.5GB,设备能承受,但磁盘占用挺高(特别是开机时),每个Redis实例都配置好了远程连接功能(防火墙、远程连接权限、保护模式,都配置到位)。
  • IP分配:192.168.0.180(主)、192.168.0.181(从1)、192.168.0.182(从2)、192.168.0.183(哨1)、192.168.0.184(哨2)、192.168.0.185(哨3),如图
                  	 192.168.0.183                           192.168.0.181
                  /                \                      /
              	/                    \                  /
               /                      \               /
192.168.0.xxx  -->-> 192.168.0.184 ---> 192.168.0.180
               \                      /               \
                 \                   /                  \
                   \                /                     \  
                     192.168.0.185                           192.168.0.182
  • 主要配置说明:
配置3台哨兵:
高版本Redis自带sentinel.conf文件,可直接用。
目前用的5,没有独立的配置文件,可在redis.conf中配置如下:
sentinel monitor 主节点名  主节点IP 主节点端口 确认客观下线的最少哨兵数量。    // 配置链接,用于定位主机网络位置。
sentinel auth-pass 主节点名  主节点密码        //配置主机密码
sentinel down-after-milliseconds 主节点名 毫秒 //配置心跳失去连接多少毫秒后,让哨兵认为主机宕机
sentinel failover-timeout 主节点名 毫秒        //配置执行故障转移所需的超时时间
sentinel parallel-syncs 主节点名 数量          //切换新的主节点之后,可同时同步其余从节点的个数,数字越小性能越低。
其它配置不常用,可参考官方手册。

对于sentinel monitor 配置项,“确认客观下线的最少哨兵数量”参数的介绍:
至少有N个哨兵节点,认为主节点有故障,才会将其下线。
由于哨兵节点自身负载问题,或网络链路有抖动问题,这会直接影响Redis哨兵检测主节点的心跳,误认为主节点是宕机。
但是容易有误判,所以设定了指定的容错额度,减少误判率。
  • 实操:
第一步,先配置1主(不用配置)2从(需要在192.168.0.181和192.168.0.182上):
我之前写过完整文章,可参考https://blog.csdn.net/weixin_42100387/article/details/139667697

vim /usr/local/redis/etc/redis.conf
#配置主节点IP和端口
replicaof 192.168.0.180 6379
#配置主节点密码
masterauth 123456

之后重启redis

需要注意,必须把每个从机的密码设置一致,方便哨兵模式主从切换实例,避免整个缓存模块故障。



第二步,配置3哨兵:
以Redis 5为例,3台哨兵节点(192.168.0.183~185)配置以下主要内容,其余配置可按需添加(因为不配置也有默认值,不会导致错误):
补充:一个哨兵是可以监控多个master的,但是这对于开发几乎用不上。
firewall-cmd --add-port=26379/tcp --zone=public --permanent && systemctl restart firewalld
vim /usr/local/redis/etc/sentinel.conf
bind 0.0.0.0
#哨兵默认端口
port 26379
daemonize yes
protected-mode no
pidfile /usr/local/redis/redis_26379.pid
logfile /usr/local/redis/redis_26379.log
sentinel monitor zs_master 192.168.0.180 6379 2
sentinel auth-pass zs_master 123456

之后启动哨兵,用以下二选一命令的方式
/usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel.conf
/usr/local/redis/bin/redis-server   /usr/local/redis/etc/sentinel.conf --sentinel

启动后,redis 会为sentinel.conf额外增加如下配置:
sentinel myid 3feb02e7fd5a96152e217b76f11e72236c23668c
sentinel deny-scripts-reconfig yes
# Generated by CONFIG REWRITE
dir "/usr/local/redis/etc"
sentinel config-epoch zs_master 0
sentinel leader-epoch zs_master 0
sentinel known-replica zs_master 192.168.0.182 6379
sentinel known-replica zs_master 192.168.0.181 6379
sentinel current-epoch 0

它们的含义分别是:
sentinel myid:起个唯一标识,方便定位哨兵,类比MySQL表id。
sentinel deny-scripts-reconfig yes: 拒绝通过脚本进行配置更改,这有助于提高安全性,防止非授权的更改。
dir "/usr/local/redis/etc": 指定rdb文件的位置。
sentinel config-epoch zs_master 0: 记录主节点zs_master的情况,用于判断哨兵配置的更新情况。
sentinel leader-epoch zs_master 0: 记录主节点zs_master的情况,用于判断哨兵的领导者。
sentinel known-replica zs_master 192.168.0.182 6379: 记录已知的从节点,这里指定了IP地址和端口号。
sentinel current-epoch 0: 记录当前的纪元(直译就这意思),用于标识哨兵配置的更新情况。



第三步,检测1主2从是否能正常使用
可在主节点Redis会话中执行 set a 123,从节点执行 get a 判断主从是否正常复制。



第四步,检测3台哨兵是否启动(3台设备分别执行,逐一检测):
ps aux | grep redis
root      81041  0.4  0.5 153996  2788 ?        Ssl  00:34   0:00 /usr/local/redis/bin/redis-sentinel 0.0.0.0:26379 [sentinel]
root      81084  0.0  0.2 112828   992 pts/0    R+   00:35   0:00 grep --color=auto redis

netstat -nlp | grep redis
tcp        0      0 0.0.0.0:26379           0.0.0.0:*               LISTEN      81041/redis-sentin

模拟主节点故障

经过实测:

  • 主节点执行shutdown模拟宕机,半分钟内,Redis哨兵自动完成了主节点的下线,并将某个从节点切换为主节点。
  • 从节点升级为主节点的节点,以及其它从节点,的配置文件都会被强制更改,作用到配置文件,是持久化的。
  • 哨兵切换流程执行完毕后原主节点恢复访问,那么原主节点会变成从节点,不会因为本身的恢复,就再次改为主节点。
  • 一些情情况下,当挂掉的主节点再次启动时,可成功启动,但是redis的机制会把master-auth配置项干丢掉,造成启动之后(变为从节点)连不上主节点的情况。

哨兵对应日志说明

  • 由于Redis哨兵模式趋向于自动化运维,所以日常日志的排查七分重要,故在此说明其中1个哨兵(192.168.0.184)的日志记录概况(最起码要看懂【何时主节点挂】、【让谁做主节点】)。
  • 速查方法(根据关键字搜索,若搜索出多个值,最后一个为最新):
    • 查询何时主节点挂:odown master 原主节点名 原主节点IP 原主节点端口 #quorum
    • 查询让谁做主节点:+switch-master 原主节点名 原主节点IP 原主节点端口(后面紧跟新主节点)
  • 哨兵启动时:
Redis哨兵初始化
99136:X 19 Jun 2024 05:58:54.697 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
99136:X 19 Jun 2024 05:58:54.697 # Redis version=5.0.9, bits=64, commit=00000000, modified=0, pid=99136, just started
99136:X 19 Jun 2024 05:58:54.697 # Configuration loaded
99137:X 19 Jun 2024 05:58:54.701 * Running mode=sentinel, port=26379.
99137:X 19 Jun 2024 05:58:54.701 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
99137:X 19 Jun 2024 05:58:54.702 # Sentinel ID is 0da5ef8126894b04e2572c4f2f9e1e636543ceee
主节点从节点信息获取
99137:X 19 Jun 2024 05:58:54.702 # +monitor master zs_master 192.168.0.180 6379 quorum 2
99137:X 19 Jun 2024 05:58:54.720 * +slave slave 192.168.0.181:6379 192.168.0.181 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:58:54.722 * +slave slave 192.168.0.182:6379 192.168.0.182 6379 @ zs_master 192.168.0.180 6379
添加了两个哨兵
99137:X 19 Jun 2024 05:58:56.059 * +sentinel sentinel 91e7adffd0d7fc08fc1eb622001eb78b7c230901 192.168.0.183 26379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:58:59.359 * +sentinel sentinel cadd108977e5634179172b6a2f84f4712f2c9185 192.168.0.185 26379 @ zs_master 192.168.0.180 6379
  • 主节点宕机时:
当前哨兵认为主机已下线,主观下线。
99137:X 19 Jun 2024 05:59:59.572 # +sdown master zs_master 192.168.0.180 6379
其它哨兵也认为主机已下线,并达到了sentinel monitor设定的阈值,主观下线升级为客观下线
99137:X 19 Jun 2024 05:59:59.627 # +odown master zs_master 192.168.0.180 6379 #quorum 2/2
故障切换,当前切换,让0da5ef8126894b04e2572c4f2f9e1e636543ceee 哨兵说了算,其余哨兵投票。
99137:X 19 Jun 2024 05:59:59.627 # +new-epoch 1
99137:X 19 Jun 2024 05:59:59.627 # +try-failover master zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:59:59.628 # +vote-for-leader 0da5ef8126894b04e2572c4f2f9e1e636543ceee 1
99137:X 19 Jun 2024 05:59:59.630 # cadd108977e5634179172b6a2f84f4712f2c9185 voted for 0da5ef8126894b04e2572c4f2f9e1e636543ceee 1
99137:X 19 Jun 2024 05:59:59.630 # 91e7adffd0d7fc08fc1eb622001eb78b7c230901 voted for 0da5ef8126894b04e2572c4f2f9e1e636543ceee 1
选择192.168.0.182作为主节点,客观下线老主节点,并将180的主节点转移为182的主节点,换人和交换权利的动作不重复。
99137:X 19 Jun 2024 05:59:59.732 # +elected-leader master zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:59:59.732 # +failover-state-select-slave master zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:59:59.837 # +selected-slave slave 192.168.0.182:6379 192.168.0.182 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:59:59.837 * +failover-state-send-slaveof-noone slave 192.168.0.182:6379 192.168.0.182 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 05:59:59.907 * +failover-state-wait-promotion slave 192.168.0.182:6379 192.168.0.182 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:00.239 # +promoted-slave slave 192.168.0.182:6379 192.168.0.182 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:00.239 # +failover-state-reconf-slaves master zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:00.298 * +slave-reconf-sent slave 192.168.0.181:6379 192.168.0.181 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:00.788 # -odown master zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:01.280 * +slave-reconf-inprog slave 192.168.0.181:6379 192.168.0.181 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:01.281 * +slave-reconf-done slave 192.168.0.181:6379 192.168.0.181 6379 @ zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:01.353 # +failover-end master zs_master 192.168.0.180 6379
99137:X 19 Jun 2024 06:00:01.353 # +switch-master zs_master 192.168.0.180 6379 192.168.0.182 6379
将180,181节点,当做从节点处理。
99137:X 19 Jun 2024 06:00:01.354 * +slave slave 192.168.0.181:6379 192.168.0.181 6379 @ zs_master 192.168.0.182 6379
99137:X 19 Jun 2024 06:00:01.354 * +slave slave 192.168.0.180:6379 192.168.0.180 6379 @ zs_master 192.168.0.182 6379
当前哨兵发现180从节点仍不能用,主观下线。
99137:X 19 Jun 2024 06:00:31.393 # +sdown slave 192.168.0.180:6379 192.168.0.180 6379 @ zs_master 192.168.0.182 6379

编程语言调用

  • 以原生PHP为例:
// 创建Redis哨兵对象,通过这个对象获取主节点的地址,这里是写死的,可以写成数组,随机访问1个节点,用于减轻压力。
$sentinel = new RedisSentinel('tcp://192.168.0.183', 26379);
//主机名
$master = $sentinel->getMasterAddrByName('zs_master');

if(! $master) {
    echo '主节点获取失败';
    die;
}


//获取到的主节点地址进行写操作
$redis = new Redis();
$redis->connect($master[0], $master[1]);
$redis->auth('123456');
$redis->set('key', 'val');


//从节点读操作。
$slave = $sentinel->slaves('zs_master');
if(empty($slave)) {
    echo '从节点获取失败';
    die;
}

//随机从节点配置数组的下标,使其读写趋向于均衡,防止访问压力倾斜
$slave_arr_key = mt_rand(0, count($slave));

//这块的从节点配置不能写死,一定要写活,万一这里的从节点,变成了主节点呢?
$redis->connect($slave[$slave_arr_key]['ip'], $slave[$slave_arr_key]['port']);
$redis->auth('123456');
$res = $redis->get('key');
print_r($res);


// 关闭连接
$redis->close();
composer require monospice/laravel-redis-sentinel-drivers

Laravel5.5以下版本需要再config/app.php中配置如下内容
'providers' => [
    ...
    Monospice\LaravelRedisSentinel\RedisSentinelServiceProvider::class,
    ...
],

在config/database.php的Redis段的同级中添加,这是演示,生产环境推荐参数写活
这插件支持直接配置到.env,不用配config,但是一旦执行php artisan optimize加速,那么env('config_key')将会是null,所以不用。
    'redis-sentinel' => [
        'default' => [
            ['host' => '192.168.0.183', 'port' => 26379,],
            ['host' => '192.168.0.184', 'port' => 26379,],
            ['host' => '192.168.0.185', 'port' => 26379,],
        ],

        'options' => [
        //主机名
        'service' =>  'zs_master',
        'parameters' => [
            'password' => '123456',
            'database' => 0,
        ],
    ],


若要配置Redos Facdeds:
.env文件添加REDIS_DRIVER=redis-sentinel
这个不用配置config,因为没有地方配置,插件直接读取env文件,测试php artisan optimize,发现能照常读取。


若要配置Cache Facdeds:
config/cache.php的'default' => env('CACHE_DRIVER', 'file'), 此处最好修改env,将值改为redis-sentinel
并添加:
'stores' => [ //注意层级关系
    ...
    'redis-sentinel' => [
        'driver' => 'redis-sentinel',
        'connection' => 'default',
    ],
],


若要配置Session Facdeds:
config/session.php的'driver' => env('SESSION_DRIVER', 'file'),,此处最好修改env,将值改为redis-sentinel,
并将connection项设置为'',否则报错。


若要配置Queue队列:
config/queue.php的'default' => env('QUEUE_CONNECTION', 'sync'),, 此处最好修改env,将值改为redis-sentinel,
并在connections项内部配置:
    'redis-sentinel' => [
        'driver' => 'redis-sentinel',
        'connection' => 'default',
        'queue' => 'default',
        'retry_after' => 90, // Laravel >= 5.4.30
        'expire' => 90,      // Laravel < 5.4.30
    ],
测试延时队列,可正常使用。

其余常用配置说明

  • redis.conf
    • min-replicas-to-write 1与min-replicas-max-lag 10:这个是高可用的两个配置项,它根筷子一样通常在一块出现,默认是禁用的配置,配置在主节点和从节点。表示主节点一个写操作,至少有1个确认接收,且确认回复(ack)时间不能超过10毫秒,否则返回的既不是nil也不是0,而是错误异常。它能够保证主从复制时的高可用,弊端就是若有从节点挂掉,达不到min-replicas-to-write设置的数量,主节点写入将会报错,导致写功能废掉。一般情况下,前者设置看情况,后者设置为3000~5000。
    • replica-priority 100:被选中作为从节点的优先级,从1设置为10,从2设置为100,则主节点挂掉,从1(小的优先)优先被设定为从机。

主观下线(Sdown)与客观下线(Odown)

  • 主观下线:当前(单个)哨兵认为主节点挂了,有可能没挂。
  • 客观下线:多个哨兵都认为主节点挂了,则主节点大概率挂了,准备开始选举其它从节点做主节点。

仲裁(Failover)

哨兵在主节点失效时进行自动故障转移(failover)的行为。

脑裂(Split-brain)导致的数据丢失问题

  • 分布式系统的概念:脑裂是一种在分布式系统中可能出现的情况,指当网络故障导致部分节点无法与其它节点通信时,这些节点可能会错误地认为自己是系统中唯一的活跃实例,从而导致数据的不一致(一个地方每个山头都独立为王,那就乱了,多了个头头,好比脑裂了)。
  • 在哨兵模式下(极少出现):客户端与主节点连接正常,但是哨兵与主节点连接异常,那么哨兵可能就会让其它从节点变为主节点,此时分布式节点中,就会有多个主节点。然而客户端仍旧和旧的数据通信(执行写操作),在这个过程下,数据会丢失。
    若此时旧主节点恢复与哨兵的连接,旧主节点就会降级为从节点,旧主节点未同步的数据也会被新节点强制覆盖。

脑裂(Split-brain)导致的数据丢失原因

  • 感谢IT老奇的架构600讲:https://www.bilibili.com/video/BV1eF41147iL/?vd_source=19da1fcedca1050975549448303b95c2
  • 背景:接上文,因为脑裂问题引发的哨兵模式自动故障转移(failover)的行为(仲裁),,旧的主节点,有着最近的进度。
  • 过程:当这个旧主节点与哨兵恢复连接后,并且已经有新的主节点诞生,旧主节点会被降级为从节点。
  • 数据丢失内部机制:旧主节点,会向新的主节点申请全量数据,新主节点会通过bgsave命令,异步生成rdb文件,回传到从节点,从节点拿到后先清空自身的所有数据,用一个干净的数据仓库承接主节点发来的数据,这个清空的过程,造成了数据的丢失。
  • 结论:客户端与Redis服务端通信期间,最新的进度没了,数据自然也就丢失了。

脑裂(Split-brain)问题改善

由于网络的隔离,通信无法像MySQL本地事务那样做到几乎完美的一致性策略,说白了哨兵与主节点的通信断了就是网断了,断了就是断了,不可控也无规律,代码写的再好也不能解决这个问题。所以这个问题的解决方案,不能用解决修饰,用改善修饰会更好。

可在主节点和其它从节点添加min-replicas-to-write 与min-replicas-max-lag参数去改善,参数含义上文有讲。
思路就是:判断主节点写入数据时从节点发送ack的数量,以及延迟,若发生脑裂,则主节点数量+1,从节点数量-1,达不到min-replicas-to-write阈值时,就会报错。这也会导致缓存写功能不可用,将异常兜底下推到代码层。

哨兵监控主节点(哨兵切换步骤0)

哨兵探测主节点是否异常是用的心跳机制,就是每隔一段时间,向主节点发送ping进而保持连接。

哨兵长(Zhang)选举(哨兵切换步骤1)

  • 动作:当主节点挂掉,哨兵进行选举主节点时,实际上进行了两步:第一步,哨兵会投票选举一个哨兵长,第二步,哨兵长选举主节点。
  • 选举算法:Redis使用的时Raft算法。
  • 算法流程(个人理解):
    时序不同,数量不同会得到不同的结果,具体读者可深入理解Raft算法官方说明。
步骤 哨1 哨2 哨3
1 给自己投1票,将投票请求发送给哨2哨3,告诉他们自己要成为哨兵长
2 给自己投1票,将投票请求发送给哨1哨2,告诉他们自己要成为哨兵长
3 收到哨3请求,拒绝 收到哨3请求,同意
4 收到哨1请求,拒绝
5 1票同意 2票同意,成为哨兵长

主节点选举(哨兵切换步骤2)

  • 引言:多个从节点,都有可能被选择其中一个为主节点,随机算法可以实现,但是有更好的判断策略,所以不用随机,而这些策略受一些因素限制。
  • 因素1:如果发现副本服务器与主服务器断开连接的时间超过所配置的主服务器超时时间(down-after-milliseconds)的10倍,再加上从执行故障切换的哨兵的角度来看主服务器也不可用的时间,则副本服务器被认为不适合进行故障切换,并被跳过。
  • 因素2:优先级:replica-priority参数,redis.conf中默认为100,开发人员根据当前实例情况可设置不同的值,有较小值的节点会被设置为主节点。
  • 因素3:进度大小:若优先级一样,就看进度,进度就是所谓的偏移量。偶尔受网络抖动或者其它因素影响,会有一个进度快(进度新),进度慢的,进度新的就做了主节点。若让进度旧的做主节点,则会让整个链路整体丢失小部分数据。
  • 因素4:若权限和进度一样,则就看Run ID,这个每个Redis实例上都会有,根据Run ID做ASICC的字典排序,最终选取一台从节点实例做主节点。
  • 当某个从节点被哨兵选中当做主节点时,哨兵会发送replicaof no one命令给从节点,让其不做任何节点的从节点。

从节点换绑主节点(哨兵切换步骤3)

能走到这个阶段,就说明新的主节点已经诞生,其它从节点已经有了切换主节点的目标。
哨兵使用Redis发布/订阅消息功能,在主服务器和所有从服务器中连续广播相关配置,直到每个节点配置完成。

热门相关:神级幸运星   邪王独爱:娘子不准逃跑   林氏荣华   重生九零娇娇媳   废材归来:绝色魔君驭兽后