Redis5.x哨兵搭建手记

Redis5.x 之后,单机、哨兵、集群搭建的难度已经简化。鉴于目前看到太多文章都是复制粘贴以往一些 3.x 版本的一些内容,所以打算基于当前 Redis 的最新版本做一次单机、哨兵和集群的搭建,记录一下过程步骤和遇到的问题。编写本文的时间是2019年10月6日(国庆假期…..),当前 Redis 的最新版本为 5.0.5 。操作系统用的是虚拟机里面安装的 CentOS 7 。先确定已经安装好 Redis 服务,可以参考笔者写的前一篇文章: 《Redis5.x单机服务搭建手记》 。出于书写习惯,本文有可能把哨兵称为 SentinelRedis Sentinel 、哨兵或者 Redis 哨兵,这四个名词是等价的。

哨兵简介

一定要有一个概念:哨兵实例也是特殊的 Redis 实例,也就是哨兵实例是独立的进程,多个哨兵实例可以搭建主从( Master-Slave ),它们承担的职责和普通的 Redis 实例不一样。下面是官方文档中对哨兵的介绍:

Redis 哨兵为 Redis 提供了高可用性,意味着可以使用哨兵创建 Redis 服务部署,该部署可以在无需人工干预的情况下抵御某些类型的故障。 Redis 哨兵还提供其他功能,如监视、通知,并且为客户端提供配置入口( acts as a configuration provider for clients )。下面是 Redis 哨兵提供的完整功能列表:

  • 监控( Monitoring ): Sentinel 会不断检查 Master 实例和 Slave 实例是否按预期工作。
  • 通知( Notification ): Sentinel 可以通过API进行通知受监控的 Redis 实例出现问题。
  • 自动故障转移( Automatic Failover ):如果 Master 实例未按预期工作,则 Sentinel 可以启动故障转移程序,在该过程中,会将一个 Slave 实例提升为 Master 实例,将其他 Slave 实例重新配置为使用新的 Master 实例,并且会通知使用 Redis 实例的应用程序获取新的地址、连接信息。
  • 提供配置入口( Configuration provider ): Sentinel 充当客户端服务发现的授权来源( a source of authority ):客户端连接到 Sentinel ,可以询问 Redis 服务群中的 Master 实例的地址。如果发生故障转移, Sentinel 将通知客户端新的 Master 实例的地址。

Sentinel的分布式性质

Redis Sentinel 是一个分布式系统, Sentinel 采用同一份配置多个 Sentinel 进程共同协作运行的设计,多 Sentinel 进程协作的优势如下:

  • 多个 Sentinel 实例就给定的主机不再可用这一事实达成共识时,将执行故障检测,从而降低了误报的可能性。
  • Sentinel 群中即使不是所有 Sentinel 处于可用状态, Sentinel 群仍然能够正常工作,进行故障转移。

哨兵搭建

当前的 Redis 哨兵版本称为 哨兵2 ,哨兵版本1是 Redis 2.6 的时候引入,现在已经过期,不推荐使用。官方文档中部署哨兵的示例中指出: 一个健壮的部署至少需要三个Sentinel实例 。再加上一般情况下,普通的 Redis 服务实例为了保证健壮性需要搭建 树状主从 ,至少建议部署三个实例。这里的部署拓扑图如下:

环境配置

按照部署拓扑图,一共部署6个 Redis 实例,3个普通的 Redis 实例组成 Master-Slave ,并且是 树状主从 ,3个 Redis 哨兵实例。为了简单起见,6个 Redis 实例部署在同一个虚拟机中,注意在生产或者测试环境要分散机器部署,避免所有鸡蛋放在同一个篮子出现机器单点故障。具体信息如下:

实例标识 角色 主机IP 端口 备注
Sentinel-1 192.168.56.200 26379
Sentinel-2 192.168.56.200 26380
Sentinel-3 192.168.56.200 26381
Redis-1 Master 192.168.56.200 6380
Redis-11 Slave 192.168.56.200 6381 Redis-1 的从节点
Redis-111 Slave 192.168.56.200 6382 Redis-11 的从节点

Sentinel配置和配置文件创建

先看样板配置文件 sentinel.conf 的内容:

port 26379
daemonize no
pidfile /var/run/redis-sentinel.pid
logfile ""
dir /tmp
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
# sentinel auth-pass mymaster 123456
# bind 127.0.0.1
# ......

完整的配置属性列表比较少,而 portdaemonizepidfilelogfiledirbind 等属性上一篇文章已经分析过,这里不再复述。

sentinel monitor

  • master-group-name :被监控的目标 Master 实例的名称,这个值可以自定义。
  • ip :被监控的目标 Master 实例的服务器地址。
  • port :被监控的目标 Master 实例的端口。
  • quorum :仲裁参数,设置需要多少个 Sentinel 实例同意才能判断一个被监控的 Redis 实例失效。换言之,一个 Sentinel 群需要获得系统中多数 Sentinel 的支持, 才能发起一次自动故障迁移。如果我们使用3个 Sentinel 实例,那么这个值可以定义为2。

sentinel monitor 配置项比较特殊,主要用来指定 Master 角色 Redis 服务的链接信息。其他5个配置项采用下面的格式:

sentinel   

  • down-after-milliseconds :定了 Sentinel 认为被监控的 Redis 服务已经断线的总毫秒数。如果在指定的毫秒数之内,被监控的 Redis 服务没有向 Sentinel 回复 PING 信息或者回复了 Error 信息,那么 Sentinel 会开始认为被监控的 Redis 服务下线(其实这里是主观下线( subjectively down ,简称 SDOWN )。
  • parallel-syncs :指定了在执行故障转移时,最多可以有多少 Slave 实例同时对新的 Master 实例进行同步,这个数字越小,完成故障转移所需的时间就越长。这里建议参考样板配置中的值,设置为1。
  • failover-timeout :故障转移超时时间,单位为毫秒。
  • deny-scripts-reconfig :是否禁用 SENTINEL SET 命令运行时修改 notification-scriptclient-reconfig-script ,默认值为 yes
  • auth-pass :配置连接 Master 实例的认证密码,如果 Master 实例没有设置密码,可以不配置此项属性。

创建3份 Sentinel 配置文件 26379.conf26380.conf26381.conf ,它们的内容十分相似,这里只列出 26379.conf 的内容( 192.168.56.200是笔者虚拟机的主机地址 ):

port 26379
daemonize yes
bind 0.0.0.0
protected-mode no
pidfile /var/run/sentinel-26379.pid
logfile "/data/redis/sentinel-26379.log"
dir /data/redis
sentinel monitor doge-master 192.168.56.200 6380 2
sentinel down-after-milliseconds doge-master 30000
sentinel parallel-syncs doge-master 1
sentinel failover-timeout doge-master 180000

另外,创建3份 Redis 服务的配置文件 6380.conf6381.conf6382.conf ,它们的内容十分相似,这里只列出 6380.conf(主节点) 的内容:

port 6380
daemonize yes
bind 0.0.0.0
protected-mode no
pidfile /var/run/redis-6380.pid
logfile "/data/redis/redis-6380.log"
dir /data/redis
dbfilename "dump-6380.rdb"

6381.conf(从节点) 尾部添加额外配置:

slaveof 192.168.56.200 6380

6382.conf(从节点) 尾部添加额外配置:

slaveof 192.168.56.200 6381

每份配置记得替换好对应的端口号,都准备好了之后,依次启动主节点、两个从节点和3个 Sentinel (可以把命令写成一个 start.sh ,调用 sh start.sh ):

/data/redis/redis-5.0.5/src/redis-server /data/redis/6380.conf
/data/redis/redis-5.0.5/src/redis-server /data/redis/6381.conf
/data/redis/redis-5.0.5/src/redis-server /data/redis/6382.conf

/data/redis/redis-5.0.5/src/redis-sentinel /data/redis/26379.conf
/data/redis/redis-5.0.5/src/redis-sentinel /data/redis/26380.conf
/data/redis/redis-5.0.5/src/redis-sentinel /data/redis/26381.conf

此时查看哨兵的配置,发现被 Redis 修改,新增了发现的主从信息和哨兵实例信息:

查看一下哨兵实例的日志:

目前哨兵和 Redis 服务都正常运作。

模拟故障转移

官方文档中建议使用测试命令让 Redis 实例 Sleep 一个时间,从而触发故障转移:

redis-cli -p [port] DEBUG sleep 30

先查看当前的 Master 实例:

[root@localhost ~]# redis-cli -p 26379 
127.0.0.1:26379> SENTINEL get-master-addr-by-name doge-master
1) "127.0.0.1"
2) "6380"

再对 Master 实例执行 Sleep 命令:

redis-cli -p 6380 DEBUG sleep 40

该命令会阻塞直到40秒后,控制台释放后,再查看当前的 Master 实例:

127.0.0.1:26379> SENTINEL get-master-addr-by-name doge-master
1) "127.0.0.1"
2) "6381"
127.0.0.1:26379>

可见,已经成功切换 Master 实例为 6381 。那么,当前的 Master-Slave 的拓扑关系到底是怎么样的?这个时候先看一下 Sentinel 的日志:

这里可以看出了,恢复后的 6380 实例重新成为了 Slave 角色,感觉有点翻车了, 原来的树状主从部署变回了一主多从 ,笔者开始不相信,于是从当前的 Master 实例查看了一下主从信息:

确实如此,再检查了一下旧的主节点 6380 的配置:

port 6380
daemonize yes
bind 0.0.0.0
protected-mode no
pidfile "/var/run/redis-6380.pid"
logfile "/data/redis/redis-6380.log"
dir "/data/redis"
dbfilename "dump-6380.rdb"
# Generated by CONFIG REWRITE
replicaof 192.168.56.200 6381

发现,最后一行被新增了内容,它成为了从节点。这一点如果不实践,恐怕不知道会衍生出这种结果。画了个图表明一下整个过程:

这个问题暂时不深入探究,目前知道结果如此即可。

客户端代码测试

既然哨兵搭建完了,可以用 Java 客户端连接进行一些简单的操作。使用的是 Lettuce 驱动:

    io.lettuce
    lettuce-core
    5.1.8.RELEASE

测试代码:

@Test
public void testSentinel() throws Exception {
    RedisURI uri = RedisURI.builder()
            .withSentinelMasterId("doge-master")
            .withSentinel("192.168.56.200", 26379)
            .build();
    RedisClient redisClient = RedisClient.create(uri);
    StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), uri);
    connection.setReadFrom(ReadFrom.SLAVE_PREFERRED);
    RedisCommands commands = connection.sync();
    String result = commands.ping();
    log.info("PING:{}", result);
    commands.setex("name", 5, "throwable");
    result = commands.get("name");
    log.info("Get value:{}", result);
    connection.close();
    redisClient.shutdown();
}

输出结果:

PING:PONG
Get value:throwable

小结

Redis 哨兵搭建相对简单,但是需要注意 Redis 主从配置和 Sentinel 配置,一些命令可以直接写成 shell 脚本方便一键 shutdown 或者重启。在测试故障转移的时候发现了树状主从会变成一主多从,这个问题后面会分析。

参考资料:

(本文完 c-1-d e-a-20191007)