Sentinel

在上节的 Redis 主从复制中,如果主节点发生故障,我们是希望从节点可以自动提升而不是人工来干预,Redis 提供的 Sentinel 功能就可实现此功能。

Sentinel简介

Redis Sentinel 是一个分布式系统,类似于 Consul 集群,一般由 3 ~ 5 个节点组成,使用 Raft 算法实现领导者选举因为故障转移工作只需要一个 Sentinel 节点来完成,如下图所示,我们客户端部分直接和 Sentinel 集群交互,关于 Redis 主从节点的状态维护交由 Sentinel 去管理。

哨兵高可用 - 图1

Sentinel参数配置

  1. port 6379 # 端口号
  2. daemonize no # 是否为守护进程模式
  3. pidfile /var/run/redis-sentinel.pid
  4. logfile "" # 日志文件
  5. dir /tmp # 工作目录
  6. # 监控的 Master 名称,默认为 mymaster 可以设置不同的名称监控多个 Master,
  7. sentinel monitor mymaster 127.0.0.1 6379 2
  8. # Sentinel 通过 ping 命令检查 Master 是否可用,默认 30 秒
  9. sentinel down-after-milliseconds mymaster 30000
  10. # 限制每次向新的主节点发起复制操作的从节点数量,默认为 1
  11. sentinel parallel-syncs mymaster 1
  12. # 故障转移超时时间,默认 3 分钟,超过这个实践认为故障转移失败
  13. sentinel failover-timeout mymaster 180000
  14. sentinel deny-scripts-reconfig yes

Sentinel部署实践

在上节讲解的 Redis 主从复制一节,我们只用了一台机器,为了更好模拟生产环境,这里采用 3 台虚拟机进行实践,模拟故障转移。

以下为我们搭建好的示意图,客户端(Node.js 服务)通过链接 Sentinel 与各个 Redis 主从节点交互。

哨兵高可用 - 图2

环境准备

Redis 安装目录 /data/soft/redis-5.0.5/

机器 模式 节点 配置文件
192.168.6.128 Master 6379 redis-6379.conf
192.168.6.129 Slave1 6380 redis-6380.conf
192.168.6.130 Slave2 6381 redis-6381.conf
192.168.6.128 Sentinel1 26379 sentinel-26379.conf
192.168.6.129 Sentinel2 26379 sentinel-26379.conf
192.168.6.130 Sentinel3 26379 sentinel-26379.conf
机器 客户端服务 端口
192.168.6.131 node-sentinel-demo 3000

配置Redis主从

在主从配置一节有讲解,当时是在一台虚拟机上完成的,现在采用多台虚拟机,区别不大,建议还是先看下主从复制一节助您更好理解本节内容

redis-6379.conf

  1. $ cat redis.conf | grep -v "#" | grep -v "^$" > redis-6379.conf # 去掉了注释和空格
  1. bind 192.168.6.128
  2. port 6379
  3. daemonize yes # 开启守护进程
  4. pidfile /var/run/redis_6379.pid
  5. logfile "6379.log"
  6. # save 900 1
  7. # save 300 10
  8. # save 60 10000
  9. dbfilename dump-6379.rdb
  10. dir /data/soft/redis-5.0.5/data/
  11. replica-read-only yes # 默认从节点仅是只读模式

启动6379

  1. $ src/redis-server redis-6379.conf

redis-6380.conf

  1. $ cat redis.conf | grep -v "#" | grep -v "^$" > redis-6380.conf # 去掉了注释和空格
  1. # redis-6380.conf
  2. # 修改以下内容
  3. bind 192.168.6.129
  4. port 6380
  5. daemonize yes # 开启守护进程
  6. pidfile /var/run/redis_6380.pid
  7. logfile "6380.log"
  8. # save 900 1
  9. # save 300 10
  10. # save 60 10000
  11. dbfilename dump-6380.rdb
  12. dir /data/soft/redis-5.0.5/data/
  13. replicaof 192.168.6.128 6379 # 做为该地址端口的从节点

启动6380

  1. $ src/redis-server redis-6380.conf

redis-6381.conf

  1. $ cat redis.conf | grep -v "#" | grep -v "^$" > redis-6381.conf # 去掉了注释和空格
  1. # redis-6381.conf
  2. # 修改以下内容
  3. bind 192.168.6.130
  4. port 6381
  5. daemonize yes # 开启守护进程
  6. pidfile /var/run/redis_6381.pid
  7. logfile "6381.log"
  8. # save 900 1
  9. # save 300 10
  10. # save 60 10000
  11. dbfilename dump-6381.rdb
  12. dir /data/soft/redis-5.0.5/data/
  13. replicaof 192.168.6.128 6379 # 做为该地址端口的从节点

启动6381

  1. $ src/redis-server redis-6381.conf

检查主从是否启动成功

  1. $ src/redis-cli -h 192.168.6.128 -p 6379 client list
  2. id=3 addr=192.168.6.129:46818 fd=7 name= age=229 idle=0 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf
  3. id=6 addr=192.168.6.130:39904 fd=8 name= age=91 idle=1 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf
  4. id=8 addr=192.168.6.128:39582 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
  1. $ redis-cli -h 192.168.6.128 -p 6379 info replication
  2. # Replication
  3. role:master
  4. connected_slaves:2
  5. slave0:ip=192.168.6.129,port=6380,state=online,offset=364,lag=1
  6. slave1:ip=192.168.6.130,port=6381,state=online,offset=364,lag=0
  7. master_replid:f42c329990f416d2e7f62704bfa07ba231275cb0
  8. master_replid2:0000000000000000000000000000000000000000
  9. master_repl_offset:364
  10. second_repl_offset:-1
  11. repl_backlog_active:1
  12. repl_backlog_size:1048576
  13. repl_backlog_first_byte_offset:1
  14. repl_backlog_histlen:364

配置Sentinel集群

Sentinel 集群建议至少 3 个节点且总数最好为奇数,保证了高可用和公平性。由于部署在不同虚拟机上所以端口都为 26379 这里我暂时没有改动,如果是同一台机器部署 则需要更改端口号。

修改 Redis-Sentinel 配置

在 192.168.6.128、192.168.6.129、192.168.6.130 三台机器上分别进行如下操作:

  1. $ cat sentinel.conf | grep -v "#" | grep -v "^$" > sentinel-26379.conf
  1. $ vim
  2. port 26379
  3. daemonize no
  4. pidfile /var/run/redis-sentinel.pid
  5. logfile "26379.log"
  6. dir /data/soft/redis-5.0.5/data/
  7. sentinel monitor mymaster 127.0.0.1 6379 2
  8. sentinel down-after-milliseconds mymaster 30000
  9. sentinel parallel-syncs mymaster 1
  10. sentinel failover-timeout mymaster 180000
  11. sentinel deny-scripts-reconfig yes

启动 Redis-Sentinel

如下所示两种方式启动 Redis-Sentinel,需要在 Sentinel 对应的多台机器上执行以下两种方式的其中一种进行操作

  1. $ src/redis-sentinel sentinel-26379.conf # 方法一
  2. $ src/redis-server sentinel-26379.conf --sentinel # 方法二

检查 Redis-Sentinel 集群状态

如下所示三个 Sentinel 节点已成功开启

  1. $ src/redis-cli -h 192.168.6.128 -p 26379 client list
  2. id=3 addr=192.168.6.130:46518 fd=16 name=sentinel-9929680e-cmd age=105 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=publish
  3. id=4 addr=192.168.6.129:51008 fd=17 name=sentinel-cf0d2de4-cmd age=105 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping
  4. id=6 addr=192.168.6.128:44044 fd=18 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client

如下所示,展示了目前 Sentinel 所监控的主节点(192.168.6.128:6379)且该主节点有 2 个 Slave 节点还有 sentinels 节点数为 3

  1. $ src/redis-cli -h 192.168.6.129 -p 26379 info sentinel
  2. # Sentinel
  3. sentinel_masters:1
  4. sentinel_tilt:0
  5. sentinel_running_scripts:0
  6. sentinel_scripts_queue_length:0
  7. sentinel_simulate_failure_flags:0
  8. master0:name=mymaster,status=ok,address=192.168.6.128:6379,slaves=2,sentinels=3

至此 Sentinel 的服务端安装部署已经介绍完毕,接下来使用 Node.js 客户进行链接和故障转移演练实践

Nodejs客户端链接

在看下客户端如何使用 Sentinel,这里以 Node.js 为例,客户端通过 Sentinel 发现主从节点地址,然后在通过这些地址建立相应的链接来进行数据存取操作,以下用的是 Node.js 的 ioredis 客户端,你需要先执行 npm install ioredis 命令进行安装

  1. const Redis = require('ioredis');
  2. const redis = new Redis({
  3. sentinels:[
  4. { host: '192.168.6.128', port: 26379 },
  5. { host: '192.168.6.129', port: 26379 },
  6. { host: '192.168.6.130', port: 26379 },
  7. ],
  8. name: 'mymaster',
  9. });
  10. let count = 0;
  11. setInterval(async function() {
  12. count++;
  13. const key = `k_${count}`;
  14. try {
  15. await redis.set(key, count);
  16. console.log(key, redis.get(key));
  17. } catch (err) {
  18. console.error(err);
  19. }
  20. }, 1000)

故障转移演练

主节点宕机

192.168.6.128:6379 为我们最初设定的主节点,现在我们让主节点宕机来进行一次故障转移实战演练

  1. $ ps -ef | grep redis-server
  2. root 5080 1 0 20:04 ? 00:00:49 src/redis-server 192.168.6.128:6379

上面的 5080 为主节点的进程 id,执行 kill 命令使其断掉链接。

  1. kill -9 5080

观察客户端日志

现在让我们在来看下上面写的 Node.js 客户端日志情况,如下所示:

  1. k_1 1
  2. k_2 2
  3. k_3 3
  4. [ioredis] Unhandled error event: Error: connect ECONNREFUSED 192.168.6.128:6379
  5. at Object._errnoException (util.js:1022:11)
  6. at _exceptionWithHostPort (util.js:1044:20)
  7. at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1198:14)
  8. [ioredis] Unhandled error event: Error: connect ECONNREFUSED 192.168.6.128:6379
  9. at Object._errnoException (util.js:1022:11)
  10. at _exceptionWithHostPort (util.js:1044:20)
  11. at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1198:14)
  12. ... # 大概30秒之后恢复正常

观察服务端从节点日志

查看 6381 节点日志信息 tail -100 6381.log,在 33:01 秒链接被拒,在 33:31.546 时间点收到一条 user 请求该节点成为了 Master 节点,23:33:32.064 时间点向 6380 发送了一条请求使其成为从节点。

  1. 20945:S 29 Jun 2019 21:24:37.848 * Successful partial resynchronization with master.
  2. 20945:S 29 Jun 2019 21:24:37.848 * MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.
  3. 20945:S 29 Jun 2019 23:33:01.104 # Connection with master lost.
  4. 20945:S 29 Jun 2019 23:33:01.107 * Caching the disconnected master state.
  5. 20945:S 29 Jun 2019 23:33:01.524 * Connecting to MASTER 192.168.6.128:6379
  6. 20945:S 29 Jun 2019 23:33:01.527 * MASTER <-> REPLICA sync started
  7. 20945:S 29 Jun 2019 23:33:01.527 # Error condition on socket for SYNC: Connection refused
  8. ... 中间省略,一直在重连
  9. 20945:S 29 Jun 2019 23:33:30.884 * Connecting to MASTER 192.168.6.128:6379
  10. 20945:S 29 Jun 2019 23:33:30.885 * MASTER <-> REPLICA sync started
  11. 20945:S 29 Jun 2019 23:33:30.885 # Error condition on socket for SYNC: Connection refused
  12. 20945:M 29 Jun 2019 23:33:31.541 # Setting secondary replication ID to f42c329990f416d2e7f62704bfa07ba231275cb0, valid up to offset: 2106538. New replication ID is b456d532b1f700037e141d56cf294668d12634e3
  13. 20945:M 29 Jun 2019 23:33:31.541 * Discarding previously cached master state.
  14. 20945:M 29 Jun 2019 23:33:31.546 * MASTER MODE enabled (user request from 'id=6 addr=192.168.6.129:40770 fd=10 name=sentinel-cf0d2de4-cmd age=11529 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=140 qbuf-free=32628 obl=36 oll=0 omem=0 events=r cmd=exec')
  15. 20945:M 29 Jun 2019 23:33:31.550 # CONFIG REWRITE executed with success.
  16. 20945:M 29 Jun 2019 23:33:32.064 * Replica 192.168.6.129:6380 asks for synchronization
  17. 20945:M 29 Jun 2019 23:33:32.064 * Partial resynchronization request from 192.168.6.129:6380 accepted. Sending 164 bytes of backlog starting from offset 2106538.

Redis 主从状态验证

让我们在最后做次验证,如下所示,192.168.6.130:6381 端口已成功晋升为 Master 节点,其从节点仅为1个 192.168.6.129:6380,因为 192.168.6.129:6379 端口已经被我们关闭了。

  1. $ src/redis-cli -h 192.168.6.130 -p 6381 info replication
  2. # Replication
  3. role:master
  4. connected_slaves:1
  5. slave0:ip=192.168.6.129,port=6380,state=online,offset=2607904,lag=0
  6. master_replid:b456d532b1f700037e141d56cf294668d12634e3
  7. master_replid2:f42c329990f416d2e7f62704bfa07ba231275cb0
  8. master_repl_offset:2607904
  9. second_repl_offset:2106538
  10. repl_backlog_active:1
  11. repl_backlog_size:1048576
  12. repl_backlog_first_byte_offset:1559329
  13. repl_backlog_histlen:1048576

当前节点状态如下所示

如下图所示,主节点挂掉后,原先的主节点断开了与其从节点的复制,一个从节点被提升为新的主节点,其它从节点开始与新的主节点建立复制关系,在客户端这边是不受影响,它会通过 Sentinel 这个中间人进行处理不受影响。

哨兵高可用 - 图3

恢复 192.168.6.128:6379 节点

执行命令 src/redis-server redis-6379.conf,恢复 6379 节点,Sentinel 也会持续的监控我们已经挂掉的 6379 节点,恢复之后集群调整如下所示

哨兵高可用 - 图4