大家好,我是袁庭新。
在Redis主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性较低,不适用于线上生产环境。
为了解决这一问题,Redis引入了哨兵模式。就是为了让它更靠谱、不容易出问题。你想啊,如果Redis的主服务器突然挂了,那整个服务不就断了嘛?但有了哨兵模式,就相当于有了个小管家,它会时刻盯着Redis服务器们,看谁不对劲儿。下面就来给大家系统的介绍Redis的哨兵模式。
1 哨兵模式介绍
Sentinel是Redis的高可用性的解决方案。由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,Sentinel会自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Redis哨兵模式是一种特殊的模式,Redis为其提供了专属的哨兵命令,它是一个独立的进程,能够独立运行。哨兵模式的结构如下图所示。
Redis哨兵模式的作用如下:
- 监控:哨兵节点会以每30秒一次的频率对每个Redis节点发送PING命令,并通过Redis节点的回复来判断其运行状态。
- 自动故障恢复:当哨兵监测到主服务器发生故障时,会自动在从节点中选择一台机器,并将其提升为主服务器,当故障实例恢复后也以新的master为主。
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移(Failover)时,会将最新信息通过PubSub发布订阅模式推送给Redis的客户端以及其他的从节点。
2 哨兵模式原理
2.1 集群监控原理
Sentinel基于心跳机制监测服务状态,每隔30秒向集群的每个实例发送PING命令。
2.1.1 主观下线
主观下线,适用于主服务器和从服务器。如果在规定的时间内(配置参数down-after-milliseconds),Sentinel节点没有收到目标服务器的有效回复(Sentinel基于心跳机制监测服务状态),则判定该服务器为“主观下线”。例如Sentinel-1向主服务发送PING命令,在规定时间内没收到主服务器PONG回复,则Sentinel-1判定主服务器为“主观下线”。
2.1.2 客观下线
客观下线,只适用于主服务器。Sentinel-1发现主服务器出现了故障,它会通过相应的命令,询问其它Sentinel节点对主服务器的状态判断。如果超过指定数量(配置参数quorum)的Sentinel节点认为主服务器down掉(主观下线),则Sentinel-1节点判定主服务为“客观下线”。quorum值最好超过Sentinel实例数量的一半。
2.2 集群故障恢复原理
投票选举(底层使用的是Raft算法),所有Sentinel节点会通过投票机制,按照谁发现谁去处理的原则,例如是由Sentinel-1发现的,则选举Sentinel-1为领头节点去做Failover(故障转移)操作;Sentinel-1节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。
一旦发现master故障,Sentinel需要在salve中选择一个作为新的master。选择依据见下:
- 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点。
- 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举。
- 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高。
- 最后是判断slave节点的运行id大小,越小优先级越高。
当选出一个新的master后(假设选举的是salve1节点),该如何实现切换呢?流程如下:
- Sentinel给备选的slave1节点发送"REPLICAOF NO ONE"命令,让该节点成为master。
- Sentinel给所有其它slave节点发送"REPLICAOF 192.168.230.132 6379"命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
- 最后,Sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点。
对上述过程可简要概述为,Sentinel负责监控主从节点的“健康”状态。当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接Redis集群时,会首先连接Sentinel,通过Sentinel来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向Sentinel要地址,Sentinel会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。
3 搭建哨兵模式集群
这里我们搭建一个三节点形成的Sentinel哨兵模式集群,来监控之前的Redis主从复制模式集群。
Redis哨兵模式集群搭建设计,信息如下表所示。
主机名 | IP地址 | Redis服务/Sentinel服务 | 角色 |
node1 | 192.168.230.131 | 192.168.230.131:6379 | master |
node2 | 192.168.230.132 | 192.168.230.132:6379 | slave |
node3 | 192.168.230.133 | 192.168.230.133:6379 | slave |
node1 | 192.168.230.131 | 192.168.230.131:26379 | sentinel |
node2 | 192.168.230.132 | 192.168.230.132:26379 | sentinel |
node3 | 192.168.230.133 | 192.168.230.133:26379 | sentinel |
在Redis的安装目录下(/usr/local/src/redis-7.2.5),有一个sentinel.conf配置文件,该文件专门用于配置哨兵模式的相关参数信息。文件中用于配置Redis哨兵模式最核心的一个参数是monitor,其语法格式见下。
# 默认值:sentinel monitor mymaster 127.0.0.1 6379 2
sentinel monitor <master-name> <ip> <redis-port> <quorum>
上述语句的含义是,让Sentinel去监控一个地址为ip:redis-port的主服务器。对该命令中涉及到的几个参数做如下的说明。
参数 | 作用 |
master-name | 被监控主机名,被监控主机名的命名由开发者进行自定义。主名称不应包含特殊字符或空格。 |
ip | 主节点Redis服务IP地址。 |
redis-port | 主节点Redis服务端口。 |
quorum | 票数,只有在至少quorum个Sentinel同意的情况下才将其视为O_DOWN(Objectively DOWN)状态。即表示当有多少个Sentinel认为主服务器宕机时,它才算真正的宕机掉,通常数量为半数或半数以上才会认为主机已经宕机,quorum需要根据Sentinel的数量设置。 |
3.1 搭建主从复制模式
接下来,我们在三台虚拟机中使用主从复制模式搭建一个拥有三台服务器的Redis集群,具体操作步骤见下。
1.将三台Redis服务器的redis.conf配置文件(/usr/local/src/redis-7.2.5/redis.conf)做以下参数的配置。
# 允许访问的地址,默认是“bind 127.0.0.1 -::1”,会导致只能在本地访问。修改为“bind 0.0.0.0 -::1”则可以在任意IP访问,生产环境不要设置为“bind 0.0.0.0 -::1”
bind 0.0.0.0 -::1
# 守护进程,修改为yes后即可后台运行,默认值为no
daemonize yes
# 保护模式,no表示关闭保护模式,默认值为yes
protected-mode no
# 密码,设置后redis-cli访问必须输入密码,默认没有配置密码
requirepass 123456
2.在mater主机和两个slave从机的redis.conf配置文件,添加复制同步的密码参数配置,即master主机的requirepass参数的取值。该配置项必须添加,否则主机将拒绝从机请求。
# 配置复制同步密码(即master主机的requirepass参数的取值)
masterauth 123456
3.按照顺序逐一启动三台Redis服务器,并通过客户端连接到Redis服务。
# 检测6379端口是否在监听
$ netstat -lntp | grep 6379
# 进入Redis安装目录
$ cd /usr/local/src/redis-7.2.5
# 指定配置文件启动redis服务
$ redis-server redis.conf
$ redis-cli -h 127.0.0.1 -p 6379 -a 123456
4.依次查看三台Redis服务器的信息,发现三台Redis服务器的角色都是master。
127.0.0.1:6379> info replication
5.然后将第二台和第三台Redis服务器设置成192.168.230.131的从服务器。
127.0.0.1:6379> REPLICAOF 192.168.230.131 6379
OK
6.查看master主机的信息,发现此时有两台slave从机连接在master主机上,日志信息如下所示。
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.230.132,port=6379,state=online,offset=14,lag=1
slave1:ip=192.168.230.133,port=6379,state=online,offset=14,lag=1
master_failover_state:no-failover
master_replid:8d667e34232c22c8a145bfa36eea058e876b5f6a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
3.2 配置Sentinel哨兵模式
1.实际开发当中,不要直接修改sentinel.conf文件,在三台主机上我们先将这个配置文件备份一份。
$ cp sentinel.conf sentinel.conf.backup
2.分别在三台Redis服务器的sentinel.conf文件中,添加下面的配置内容。
# 守护进程,修改为yes后即可后台运行,默认值为no
daemonize yes
# 让Sentinel去监控一个地址为ip:port的主服务器
sentinel monitor mymaster 192.168.230.131 6379 2
# 若主服务器设置了密码,则哨兵必须也配置密码,否则哨兵无法对主从服务器进行监控,该密码与主服务器密码相同
sentinel auth-pass mymaster 123456
auth-pass命令用于设置用于与master主服务器和slave从服务器进行身份验证的密码。需要注意的是,master密码也用于slave,因此如果你想使用Sentinel监视这些实例,则无法在master实例和slave实例中设置不同的密码。
默认情况下,Redis Sentinel不作为守护进程运行。如果需要,请使用“yes”。请注意,Redis在守护进程化时会在/var/run/Redis-sentinel.pid中写入一个pid文件。
3.3 启动Sentinel哨兵模式
1.在三台服务器上,按照顺序逐一启动Sentinel哨兵,命令见下。
$ redis-sentinel sentinel.conf
2.在第一台Redis机器(192.168.230.131)上执行添加字符串操作并查看键的值。
127.0.0.1:6379> SET city xian
OK
127.0.0.1:6379> GET city
"xian"
3.然后在第二台和第三台Redis机器上,查看对应city键的值便可获取到,如下所示。
127.0.0.1:6379> GET city
"xian"
至此,Redis Sentinel哨兵模式的集群搭建完毕,同时启动了Sentinel哨兵集群。此时,就算主服务器挂了,该集群仍然可以正常运转。
3.4 停止主服务器服务
下面模拟主服务意外宕机的情况,首先直接将主服务器的Redis服务终止,然后查看从服务器是否被提升为了主服务器。具体操纵演示步骤见下。
1.关闭Redis主服务器master(192.168.230.131),执行以下命令。
127.0.0.1:6379> SHUTDOWN
(0.75s)
not connected> EXIT
2.在192.168.230.132机器上,查看Redis主从复制的集群信息。通过查询结果发现,该从服务器将192.168.230.133服务器做为master连接。
127.0.0.1:6379> info replication
Error: Server closed the connectionnot connected> info replication
# Replication
role:slave
master_host:192.168.230.133
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:109506
slave_repl_offset:109506
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:5719a81cea24fba3fad543bbbe4467a5ba7abcd4
master_replid2:f3b539f424774450b26de565fb92502737f108b4
master_repl_offset:109506
second_repl_offset:105831
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:109492
3.在192.168.230.133机器上,查看Redis主从复制的集群信息。通过查询结果发现,才服务器的角色从原来的slave变成了master,且192.168.230.132变成了它的的从服务器。
127.0.0.1:6379> info replication
Error: Server closed the connectionnot connected> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.230.132,port=6379,state=online,offset=113435,lag=1
master_failover_state:no-failover
master_replid:5719a81cea24fba3fad543bbbe4467a5ba7abcd4
master_replid2:f3b539f424774450b26de565fb92502737f108b4
master_repl_offset:113435
second_repl_offset:105831
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:113421
在Redis集群中开启哨兵模式后,如果主服务器master宕机,此时所有的从服务器slave在后台会自动发起激烈的投票,重新选出新的主服务器master。这就是开启哨兵模式后所发挥的作用。
思考问题:假设此时192.168.230.131机器(原来的主服务器)恢复正常运行状态,那么集群环境的状态将会怎样?
4.重新启动192.168.230.131机器的Redis服务。
[root@node1 redis-7.2.5]# redis-server redis.conf
[root@node1 redis-7.2.5]# redis-cli -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> KEYS *
Error: Server closed the connection
not connected> KEYS *
1) "city"
5.查看192.168.230.131机器的Redis信息并观察日志的输入内容。
- 重新归来的Redis服务启动成功后,立刻查看此台机器的Redis服务信息,发现角色为master。
- 几秒后再次查看当前机器的Redis服务信息,此时发现角色变成slave。原因是在Redis集群中开启了哨兵模式,哨兵检测到有新的Redis服务器被启动,立刻将该Redis服务拉入到现有的集群中以从服务器的身份加入。
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.230.133
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:182056
slave_repl_offset:182056
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:5719a81cea24fba3fad543bbbe4467a5ba7abcd4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:182056
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:168030
repl_backlog_histlen:14027
哨兵模式也是有缺点的,由于所有的写操作都是在master上完成的,然后再同步到slave上,因此两台机器之间通信会有延迟。当系统很繁忙的时候,延迟问题会加重;slave机器数量增加,问题也会加重。
4 RedisTemplate连接Sentinel
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用Lettuce实现了节点的感知和自动切换。
下面我们通过一个测试示例来实现RedisTemplate集成Sentinel哨兵机制,具体实现步骤见下。
4.1 基础环境搭建
1.创建Spring Boot项目。使用Spring Initializr方式创建一个名为springboot-cache-demo的Spring Boot项目,效果如下图所示。
2.引入相关依赖。在项目的pom.xml文件中添加Web模块中的Spring Data Redis依赖启动器、Spring Web依赖、Lombok依赖,以及Spring Boot单元测试依赖,示例代码如下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.8</version><relativePath/></parent><groupId>com.ytx</groupId><artifactId>redis-advanced-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>redis-advanced-demo</name><description>redis-advanced-demo</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>21</java.version></properties><dependencies><!-- Spring Data Redis依赖启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
3.在项目的resources目录下新建全局配置文件application.yml,并编写对应的日志级别的配置。
logging:level:io.lettuce.core: debugpattern:dateformat: MM-dd HH:mm:ss:SSS
4.编写Web访问层类。在项目中创建名为com.ytx.controller的包,并在该包下创建一个名为HelloRedisController的控制器类。
package com.ytx.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloRedisController {@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/set/{key}/{value}")public String hi(@PathVariable String key, @PathVariable String value) {redisTemplate.opsForValue().set(key, value);return "success";}@GetMapping("/get/{key}")public String hi(@PathVariable String key) {return redisTemplate.opsForValue().get(key);}
}
4.2 配置Sentinel
1.配置Redis Sentinel的连接信息。在项目的全局配置文件application.yml中添加Redis数据库服务器的连接配置,示例代码如下。
spring:data:redis:sentinel:master: mymasternodes: # 指定Redis的Sentinel哨兵集群相关信息- 192.168.230.131:26379- 192.168.230.132:26379- 192.168.230.133:26379password: 123456 # Redis密码
2.接下来我们在项目中创建名为com.ytx.config的包,在该包下创建一个Redis自定义配置类RedisConfig,示例代码如下。
package com.ytx.config;
import io.lettuce.core.ReadFrom;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/** 定义一个Redis配置类 */
@Configuration
public class RedisConfig {/** 配置读写分离 */@Beanpublic LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer() {return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);}
}
在上述代码中,使用了io.lettuce.core.ReadFrom类,该类中提供了对Redis的读写策略,具体介绍见下表。
属性 | 作用 |
MASTER | 从主节点读取。 |
MASTER_PREFERRED | 优先从master节点读取,master不可用才读取replica。 |
REPLICA | 从slave(replica)节点读取。 |
REPLICA _PREFERRED | 优先从slave(replica)节点读取,所有的slave都不可用才读取master。 |
3.启动项目,项目启动成功后,通过浏览器访问http://localhost:8080/set/country/China向Redis数据库中插入一条数据,结果如下图所示。
4.然后在通过浏览器访问http://localhost:8080/get/country查询key为country的数据,结果如下图所示。
5 总结
Redis哨兵模式是一种提高其高可用性的解决方案。在Redis主从复制模式中,主服务器宕机需要手动切换从服务器,导致服务不可用和数据安全风险。哨兵模式通过引入哨兵节点来自动监控和管理Redis集群。
哨兵节点会定期向Redis节点发送PING命令,判断其运行状态。当主服务器出现故障时,哨兵节点会自动选择一个从服务器升级为新的主服务器,并通知其他节点更新配置,实现自动故障转移。这个过程包括投票选举最优从服务器、发送命令让其成为新主服务器,并调整其他从服务器的配置。
哨兵模式还具备通知功能,当发生故障转移时,会将最新信息推送给Redis客户端和其他从节点。这种模式简化了系统管理,降低了人为干预的风险,提升了Redis集群的可靠性和稳定性。总的来说,Redis哨兵模式让Redis服务更加可靠和稳定,保障了业务的连续性。