Redis高可用部署:3台服务器打造哨兵集群

1、Redis集群介绍

Redis 集群(Redis Cluster)是Redis提供的一种分布式部署方式,旨在提供高可用性。如果某个主节点发生故障,集群能够自动进行故障转移,将副本提升为主节点,从而保证数据的持续可用,Redis 集群非常适合于大规模缓存系统和大数据量场景。

2、搭建集群的几种方式

  • 主从复制(Master-Slave):这种模式下,主节点(Master)负责数据写入和读取,而从节点(Slave)则只能用于数据读取和备份,不能够写入数据。若主节点发生故障,需人工介入,将某个从节点提升为新的主节点。该模式没有自动故障转移,需要手动处理故障,因此可用性较差。最少需要2台服务器搭建一主一从或者多台服务器搭建一主多从。在资源不允许的情况下,可以借助高可用工具实现自动故障转移。如:Keepalived + Redis主从实现主备双活。
  • 哨兵模式(Redis Sentinel) : 哨兵模式下,需要额外搭建哨兵(Sentinel)服务用于监控Redis集群中的主节点,并且能够在主节点发生故障时自动进行故障转移,通过选举的方式,将从节点升级为主节点。它提供高可用性和自动恢复能力。常见的集群方式为:1主2从 + 3个Sentinel进程,哨兵(Sentinel)进程可以部署在相同的3个节点上,以减少服务器资源。比较适用于需要高可用的中小型应用,数据量不是特别大的场景。
  • 分片集群(Redis Cluster):这一模式支持数据分片(Sharding)存储和多节点水平扩展,有效的提高了内存利用率和写入性能。插入的数据会根据一定的规则(哈希槽)被分布到不同的节点上,每个节点负责数据的一个子集(通过哈希槽的范围),它会自动管理数据的分布和迁移。它采用了去中心化的多主多从架构,最低配置要求为3个主节点(Master)和3个从节点(Slave),通常情况下可以在3台服务器中每台服务器配置一主一从进行搭建,虽然这样会影响一定的性能,但是可以减少服务器所需的资源。如果需要发挥其最大的价值,至少需要6台服务器节点才可以。该集群非常适用于更大规模和更高要求的数据处理场景。

3、选择集群模式

这里根据所属的业务系统需要的Redis需求进行合适的选择。使用Redis Cluster 模式搭建分片集群的要求比较高,客户端必须该支持集群模式,而且配置Redis Cluster 需要比普通的Redis主从模式复杂的多,需要的成本比较高。只有当应用需要高性能、高吞吐量,并且对数据分布和扩展性有需求时,Redis Cluster 是更好的选择。使用Redis Sentinel 模式搭建集群相对来说不是特别复杂,只需要搭建Redis主从模式后在启动相关的Sentinel 即可。如果数据量不是特别大,使用Redis集群只是用来提供高可用性和故障转移,可以考虑使用 Redis Sentinel 。本篇使用 哨兵模式(Redis Sentinel) 进行搭建Redis集群。

哨兵模式(Redis Sentinel)相关介绍:

Redis哨兵模式(Redis Sentinel)首次引入是在 Redis 2.8.0 版本中。该版本于 2013年12月 发布。Redis Sentinel 的设计初衷是为了弥补 Redis 在高可用性和自动故障转移方面的不足,特别是对于主从复制架构中的主节点宕机的容错处理。通过Redis Sentinel 可以实现自动检测故障和自动故障转移,从而提高 Redis 的可用性和可靠性。在这一模式中,哨兵负责实时监控主节点的运行状况。如果主节点出现故障,哨兵将基于预设的投票机制,自动将某个从节点晋升为新的主节点,以保障服务的连续性和数据的可用性。哨兵本身是一个独立的进程,运行在Redis本身进程之外。它通过持续监控 Redis 服务器的状态,包括主节点和从节点,并定期通过 ping 主从节点来确认它们是否仍然可用,来判断被监控的Redis实例是否正常运行来确保Redis服务的稳定性。

4、环境准备

4.1、服务器信息

根据以上搭建的几种方式和相关建议,在3台服务器节点的情况下,首选Redis Sentinel哨兵模式进行搭建。以下是服务器信息:

服务器 IP 地址 部署应用
node1 192.168.42.131 Redis Master +Sentinel
node2 192.168.42.132 Redis Slave + Sentinel
node3 192.168.42.133 Redis Slave + Sentinel

4.2、软件版本

  • 操作系统: CentOS 7.9
  • Redis 版本: 5.0

5、配置Redis主从复制

5.1、创建目录

分别在三台服务器中创建以下目录:

sh 复制代码
# 选择Redis存储文件目录,创建以下目录
mkdir -p /root/docker/redis/config /root/docker/redis/data /root/docker/redis/sentinel

# config 存入redis配置文件信息 如:redis.conf
# data 存入持久化Redis数据
# sentinel 存入哨兵的配置文件信息 如:sentinel.conf

下载配置文件redis.conf到本地,并上传到服务器config目录,下载地址:https://github.com/redis/redis/blob/5.0.4/redis.conf

5.2、配置Redis配置文件

node1node2node3 上更新Redis配置文件信息 vim ~/docker/redis/config/redis.conf,配置密码和主从关系。

主节点(node1):

ini 复制代码
# 运行端口
port 6379
# 允许外部所有IP访问
bind 0.0.0.0
# 是否以守护进程运行,Docker运行必须设置为no。
daemonize no
# redis密码
requirepass "1234qwer"
# 配置主从连接密码
masterauth "1234qwer"
# 开启持久化
appendonly yes

#对外固定开放IP地址
slave-announce-ip 192.168.42.131
#对外固定开放端口
slave-announce-port 6379

从节点(node2):

ini 复制代码
port 6379
bind 0.0.0.0
daemonize no
requirepass "1234qwer"
masterauth "1234qwer"
appendonly yes

# 对外固定开放IP地址
slave-announce-ip 192.168.42.132
# 对外固定开放端口
slave-announce-port 6379
# 配置主节点的IP和端口
REPLICAOF  192.168.42.131 6379

从节点(node3)

ini 复制代码
port 6379
bind 0.0.0.0
daemonize no
requirepass "1234qwer"
masterauth "1234qwer"
appendonly yes

# 对外固定开放IP地址
slave-announce-ip 192.168.42.133
# 对外固定开放端口
slave-announce-port 6379
# 配置主节点的IP和端口
REPLICAOF  192.168.42.131 6379

5.3、启动Redis容器

主节点(node1):

bash 复制代码
docker run -d -p 6379:6379 --restart=always --privileged=true --name redis-master -v ~/docker/redis/config/redis.conf:/etc/redis/redis.conf -v ~/docker/redis/data:/data redis:5.0 redis-server /etc/redis/redis.conf

从节点(node2):

bash 复制代码
docker run -d -p 6379:6379 --restart=always --privileged=true --name redis-slave -v ~/docker/redis/config/redis.conf:/etc/redis/redis.conf -v ~/docker/redis/data:/data redis:5.0 redis-server /etc/redis/redis.conf

从节点(node3):

sh 复制代码
docker run -d -p 6379:6379 --restart=always --privileged=true --name redis-slave -v ~/docker/redis/config/redis.conf:/etc/redis/redis.conf -v ~/docker/redis/data:/data redis:5.0 redis-server /etc/redis/redis.conf

5.4、验证主从复制

登录redis输入:info replication 可以查看Redis集群配置信息。如果搭建成功,会显示节点信息。

主节点(node1)显示以下信息:

sh 复制代码
# 首先通过docker进入redis容器
docker exec -it redis-master bash
# 进入成功后,登录redis
redis-cli -p 6379
# 登录成功,进行密码认证,然后查看集群配置信息
127.0.0.1:6379> auth 1234qwer
ok
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.42.132,port=6379,state=online,offset=280,lag=0
slave1:ip=192.168.42.133,port=6379,state=online,offset=280,lag=0
master_replid:a052149baf6e1dbb345f55ceb37476e95efca431
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280

从节点(node2)显示以下信息:

sh 复制代码
# 首先通过docker进入redis容器
docker exec -it redis-slave bash
# 进入成功后,登录redis
redis-cli -p 6379
# 登录成功,进行密码认证,然后查看集群配置信息
127.0.0.1:6379> auth 1234qwer
ok
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.42.131
master_port:6379
master_link_status:up
master_last_io_seconds_ago:10
master_sync_in_progress:0
slave_repl_offset:448
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a052149baf6e1dbb345f55ceb37476e95efca431
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:448
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:434

从节点(node3)显示以下信息:

sh 复制代码
# 首先通过docker进入redis容器
docker exec -it redis-slave bash
# 进入成功后,登录redis
redis-cli -p 6379
# 登录成功,进行密码认证,然后查看集群配置信息
127.0.0.1:6379> auth 1234qwer
ok
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.42.131
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:532
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a052149baf6e1dbb345f55ceb37476e95efca431
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:532
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:532

输出以上信息,说明Redis主从配置已经连接成功了,接下来验证一下存取数据。

5.5、验证存取数据

在两个从节点存入数据会报错。

sh 复制代码
127.0.0.1:6379> set verify-key "Test Verify is Success"
(error) READONLY You can't write against a read only replica.

因为从节点只可以读取数据不可以写入数据,要想写入数据需要在主节点进行。主节点可以读写数据。登录主节点存入数据:

sh 复制代码
127.0.0.1:6379> set verify-key "Test Verify is Success"
OK
127.0.0.1:6379> get verify-key
"Test Verify is Success"

然后分别在两个从节点读取数据,看看是否可以读取成功。

sh 复制代码
# 登录 192.168.42.132 节点redis信息
127.0.0.1:6379> get verify-key
"Test Verify is Success"

# 登录 192.168.42.133 节点redis信息
127.0.0.1:6379> get verify-key
"Test Verify is Success"

获取成功,说明主从复制搭建成功。

6、配置 Redis 哨兵

6.1、主节点(node1)配置启动

进入:/root/docker/redis/sentinel目录,下载sentinel.conf文件并上传的该目录,配置哨兵连接。 文件下载地址: https://github.com/redis/redis/blob/5.0.4/sentinel.conf

下载成功后,修改配置文件。执行命令vim /root/docker/redis/sentinel/sentinel.conf

properties 复制代码
# 哨兵节点的端口号
port 26379
bind 0.0.0.0
logfile "/var/log/redis-sentinel.log"
# 关闭保护模式,允许外部访问,Docker环境中必须设置为 no
protected-mode no
# 禁止Redis以守护进程方式运行
daemonize no
# 哨兵工作目录
dir /tmp
sentinel monitor mymaster 192.168.42.131 6379 2
# 解析:
# - mymaster:主节点的名称标识
# - 49.235.180.130:主节点的IP地址
# - 6379:主节点的端口号
# - 2:判定主节点故障需要的哨兵投票数(至少2个哨兵认为主节点故障才会进行故障转移)
#redis节点密码
sentinel auth-pass mymaster 1234qwer
# 解析:
# - mymaster:对应的主节点名称
# - 1234qwer:Redis节点的访问密码
# 主观下线时间,单位毫秒
sentinel down-after-milliseconds mymaster 10000
# 解析:如果10秒内无法连接主节点,则认为主节点主观下线

# 同步配置
sentinel parallel-syncs mymaster 1
# sentinel parallel-syncs:并行同步配置,mymaster:主节点的别名,1:并行同步的从节点数量。
#作用和影响:
#1. 控制故障转移期间的数据同步行为,当值为1时:从节点逐个同步,当值大于1时:多个从节点并行同步
#示例场景:
# parallel-syncs = 1 时:主节点故障,slave1被选为新主节点 slave2开始与slave1同步,slave3等待slave2同步完成后再同步,过程较慢但主节点压力小
# parallel-syncs = 2 时:主节点故障,slave1被选为新主节点,slave2和slave3同时与slave1同步,过程较快但主节点压力大

# 故障转移超时时间
sentinel failover-timeout mymaster 600000
# 解析:故障转移的超时时间为600秒(10分钟)

sentinel deny-scripts-reconfig yes
# 解析:禁止通过脚本修改哨兵配置,提高安全性

# 对外宣告的IP地址
sentinel announce-ip "192.168.42.131"
# 对外宣告的端口号
sentinel announce-port 26379
# 解析:
# - 用于在Docker或NAT网络环境中,声明哨兵节点的实际可访问地址
# - 确保其他节点可以正确连接到该哨兵节点

启动哨兵:

bash 复制代码
docker run -d -p 26379:26379 --restart=always --privileged=true --name redis-sentinel  -v /root/docker/redis/sentinel/sentinel.conf:/etc/redis/sentinel.conf redis:5.0 redis-sentinel /etc/redis/sentinel.conf

6.2、从节点(node2)配置启动

下载并修改配置文件。执行命令vim /root/docker/redis/sentinel/sentinel.conf

sh 复制代码
# 配置文件和主节点启动的哨兵配置文件一致,只需要更改一下对外监听的哨兵地址即可

# 对外宣告的IP地址
sentinel announce-ip "192.168.42.132"
# 对外宣告的端口号
sentinel announce-port 26379

启动哨兵:

sh 复制代码
docker run -d -p 26379:26379 --restart=always --privileged=true --name redis-sentinel  -v /root/docker/redis/sentinel/sentinel.conf:/etc/redis/sentinel.conf redis:5.0 redis-sentinel /etc/redis/sentinel.conf

6.3、从节点(node3)配置启动

下载并修改配置文件。执行命令vim /root/docker/redis/sentinel/sentinel.conf

sh 复制代码
# 配置文件和主节点启动的哨兵配置文件一致,只需要更改一下对外监听的哨兵地址即可

# 对外宣告的IP地址
sentinel announce-ip "192.168.42.133"
# 对外宣告的端口号
sentinel announce-port 26379

启动哨兵:

sh 复制代码
docker run -d -p 26379:26379 --restart=always --privileged=true --name redis-sentinel  -v /root/docker/redis/sentinel/sentinel.conf:/etc/redis/sentinel.conf redis:5.0 redis-sentinel /etc/redis/sentinel.conf

6.4、验证哨兵集群

首先选择一台服务器进行登录哨兵,可以在主节点或从节点执行以下命令,进入哨兵容器:

sh 复制代码
docker exec -it redis-sentinel bash

连接哨兵客户端:

sh 复制代码
redis-cli -p 26379

查看哨兵信息:

sh 复制代码
# info sentinel
127.0.0.1:26379> info sentinel
# 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=192.168.42.131:6379,slaves=2,sentinels=3

#输出以上信息配置成功

查看哨兵信息,如果发现已经集成成功了,接下来就可以验证哨兵模式了。在哨兵模式下,当master服务器宕机之后,哨兵自动会在从 节点服务器里面投票选举一个master服务器来,这个master服务器也可以进行读写操作。

先把主节点关闭掉:

sh 复制代码
docker stop redis-master

再次查看哨兵信息:

sh 复制代码
127.0.0.1:26379> info sentinel
# 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=192.168.42.132:6379,slaves=2,sentinels=3

# 根据最后一行信息,可以看出主节点的IP进行变化了。由192.168.42.131 -> 192.168.42.132

7、SpringBoot连接Redis哨兵模式

7.1、引入依赖

pom.xml 中添加依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

7.2、配置 application.yml

yaml 复制代码
spring:
   redis:
    #密码
      password: 1234qwer
      timeout: 5000
      sentinel:
       # 对应前面sentinel的配置文件信息
        master: mymaster
       # 三个哨兵的ip端口
        nodes: 192.168.42.131:26379,192.168.42.132:26379,192.168.42.133:26379

7.3、编写测试代码

创建 RedisConfig使用Jedis客户端连接

java 复制代码
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String password;
    // 新增哨兵配置
    @Value("${spring.redis.sentinel.master}")
    private String masterName;
    @Value("${spring.redis.sentinel.nodes}")
    private String sentinelNodes;
  
     @Bean
     public JedisSentinelPool jedisSentinelPool() {
        // 解析哨兵节点
        Set<String> sentinels = new HashSet<>(Arrays.asList(sentinelNodes.split(",")));
        // 创建哨兵连接池
        JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinels, createPoolConfig(), timeout, password,0);
         log.info("使用哨兵模式,主节点名称:{} JedisSentinelPool注入成功!!", masterName);
         return sentinelPool;
     }
  
      private JedisPoolConfig createPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(50);
        jedisPoolConfig.setMinIdle(5);
        jedisPoolConfig.setMaxWaitMillis(10000);
        jedisPoolConfig.setJmxEnabled(true);
        return jedisPoolConfig;
    }
}

创建 JedisTemplate操作Redis类

java 复制代码
@Component
@ConditionalOnClass(RedisConfig.class)
public class JedisTemplate {
  
    @Autowired(required = false)
    private JedisSentinelPool jedisSentinelPool;
  
   /**
     * 保存数据
     * @param key
     * @param value
     * @param seconds 小于等于0时为不限制时长
     */
    public final void setStringData(String key, String value, int seconds) {
        Jedis jedis = null;
        try {
            jedis = jedisSentinelPool.getResource();
            boolean flag = jedis.exists(key);
            if (flag) {
                jedis.del(key);
            }
            jedis.set(key, value);
            if (seconds > 0) {
                jedis.expire(key, seconds);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    /**
     * 根据key获取redis里面的字符串
     * @param key
     * @return
     */
    public String getStringData(String key) {
        String str = null;
        Jedis jedis = null;
        try {
            jedis = jedisSentinelPool.getResource();
            str = jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return str;
    }
  
   /**
     * 删除一个key
     * @param key
     */
    public void deleteKey(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisSentinelPool.getResource();
            jedis.del(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

创建RedisTestController测试Web控制器

java 复制代码
@RestController
public class RedisTestController {
    @Autowired
    private JedisTemplate jedisTemplate;
  
    @GetMapping("/redis/test/{key}")
    public Response getRedisSentinelTest(@PathVariable("key") String key){
        String randomNumeric = RandomStringUtils.randomNumeric(12);
        jedisTemplate.setStringData(key,"哈哈哈" + randomNumeric, 180);
        String stringData = jedisTemplate.getStringData(key);
        System.out.println(stringData);
        return Response.success(stringData);
    }
}

7.4、启动项目

sh 复制代码
2025-02-22 17:40:14.223 INFO  -[JedisSentinelPool.java:198]- Trying to find master from available Sentinels...
2025-02-22 17:40:14.328 INFO  -[JedisSentinelPool.java:254]- Redis master running at 192.168.42.131:6379, starting Sentinel listeners...
2025-02-22 17:40:14.345 INFO  -[JedisSentinelPool.java:188]- Created JedisPool to master at 192.168.42.131:6379
2025-02-22 17:40:14.345 INFO  -[RedisConfig.java:42]- 使用哨兵模式,主节点名称:mymaster JedisSentinelPool注入成功!!
2025-02-22 17:40:48.609 INFO  -[DirectJDKLog.java:173]- Starting ProtocolHandler ["http-nio-9090"]
2025-02-22 17:40:48.630 INFO  -[TomcatWebServer.java:220]- Tomcat started on port(s): 9090 (http) with context path ''
2025-02-22 17:40:48.642 INFO  -[StartupInfoLogger.java:61]- Started App in 2.892 seconds (JVM running for 3.562)
2025-02-22 17:41:05.914 INFO  -[DirectJDKLog.java:173]- Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-02-22 17:41:05.915 INFO  -[FrameworkServlet.java:525]- Initializing Servlet 'dispatcherServlet'
2025-02-22 17:41:05.929 INFO  -[FrameworkServlet.java:547]- Completed initialization in 14 ms

调用接口使用PostMan调用接口进行测试:http://localhost:9090/redis/test/1234 ,测试哨兵是否创建成功。

8、总结

到此Redis哨兵集群就搭建完成了。Redis哨兵为我们提供了强大的监控和自动修复功能,这使得Redis服务在面临各种故障时,能够快速自动恢复,减少人工干预。回顾整个搭建过程,搭建并不是特别复杂,主要也就3个步骤,包括:1、配置Redis主从复制。2、设置Redis哨兵节点。3、验证高可用性。最后,希望本文能帮助你了解Redis哨兵模式的搭建流程,并为你提供参考。如果你在搭建过程中遇到问题,欢迎讨论或查阅Redis官方文档,进一步优化配置。