乐趣区

关于redis:Redis哨兵机制全面深入分析与讲解实战演示篇

文章简介

本文将通过实践 + 实际的形式从头到尾总结 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 端口号
(数据节点)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
  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 1
save 20 1
save 30 1
# 2. 当 bgsave 命令产生谬误时,进行写入操作。stop-writes-on-bgsave-error yes
# 3. 是否开启 rbd 文件压缩
rdbcompression yes
  1. (数据节点)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
  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.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. 咱们在 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>
  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
# 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
  1. 查看哨兵端口为 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
  1. 查看哨兵端口为 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 端口了,其余的信息没有产生扭转,阐明哨兵曾经实现切换工作。

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

  1. 在 3 号端口 (新的 master) 执行一个 del 命令。
127.0.0.1:8004> del age
(integer) 1
127.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 8002
127.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 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
  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 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"
}
退出移动版