Redis哨兵模式

Sentinel(哨岗、哨兵)是Redis的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
                                     ——《Redis设计与实现》

准备环境和工具


docker-compose环境

配置文件目录

redis一主二从三哨兵搭建 redis一主一从两哨兵_Redis

Sentinel配置

redis-sentinel-1.conf

# bind 127.0.0.1

# 哨兵的端口号
# 因为各个哨兵节点会运行在单独的Docker容器中
# 所以无需担心端口重复使用
# 如果需要在单机
port 26379

# 设定密码认证
#requirepass 123456

# 配置哨兵的监控参数
# 格式:sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name是为这个被监控的master起的名字
# ip是被监控的master的IP或主机名。因为Docker容器之间可以使用容器名访问,所以这里写master节点的容器名
# redis-port是被监控节点所监听的端口号
# quorom设定了当几个哨兵判定这个节点失效后,才认为这个节点真的失效了
sentinel monitor local-master 192.168.56.10 6379 2

# 连接主节点的密码
# 格式:sentinel auth-pass <master-name> <password>
sentinel auth-pass local-master 123456

# master在连续多长时间无法响应PING指令后,就会主观判定节点下线,默认是30秒
# 格式:sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds local-master 30000

redis-sentinel-2.conf

# bind 127.0.0.1

# 哨兵的端口号
# 因为各个哨兵节点会运行在单独的Docker容器中
# 所以无需担心端口重复使用
# 如果需要在单机
port 26380

# 设定密码认证
#requirepass 123456

# 配置哨兵的监控参数
# 格式:sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name是为这个被监控的master起的名字
# ip是被监控的master的IP或主机名。因为Docker容器之间可以使用容器名访问,所以这里写master节点的容器名
# redis-port是被监控节点所监听的端口号
# quorom设定了当几个哨兵判定这个节点失效后,才认为这个节点真的失效了
sentinel monitor local-master 192.168.56.10 6379 2

# 连接主节点的密码
# 格式:sentinel auth-pass <master-name> <password>
sentinel auth-pass local-master 123456

# master在连续多长时间无法响应PING指令后,就会主观判定节点下线,默认是30秒
# 格式:sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds local-master 30000

redis-sentinel-3.conf

# bind 127.0.0.1

# 哨兵的端口号
# 因为各个哨兵节点会运行在单独的Docker容器中
# 所以无需担心端口重复使用
# 如果需要在单机
port 26381

# 设定密码认证
#requirepass 123456

# 配置哨兵的监控参数
# 格式:sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name是为这个被监控的master起的名字
# ip是被监控的master的IP或主机名。因为Docker容器之间可以使用容器名访问,所以这里写master节点的容器名
# redis-port是被监控节点所监听的端口号
# quorom设定了当几个哨兵判定这个节点失效后,才认为这个节点真的失效了
sentinel monitor local-master 192.168.56.10 6379 2

# 连接主节点的密码
# 格式:sentinel auth-pass <master-name> <password>
sentinel auth-pass local-master 123456

# master在连续多长时间无法响应PING指令后,就会主观判定节点下线,默认是30秒
# 格式:sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds local-master 30000

Redis配置

redis-master.conf

# bind 127.0.0.1

# 启用保护模式
# 即在没有使用bind指令绑定具体地址时
# 或在没有设定密码时
# Redis将拒绝来自外部的连接
# protected-mode yes

# 监听端口
port 6379

# 启动时不打印logo
# 这个不重要,想看logo就打开它
always-show-logo no

# 设定密码认证
requirepass 123456

# 禁用KEYS命令
# 一方面 KEYS * 命令可以列出所有的键,会影响数据安全
# 另一方面 KEYS 命令会阻塞数据库,在数据库中存储了大量数据时,该命令会消耗很长时间
# 期间对Redis的访问也会被阻塞,而当锁释放的一瞬间,大量请求涌入Redis,会造成Redis直接崩溃
rename-command KEYS ""

# 此外还应禁止 FLUSHALL 和 FLUSHDB 命令
# 这两个命令会清空数据,并且不会失败

redis-slave1.conf

# bind 127.0.0.1

# 启用保护模式
# 即在没有使用bind指令绑定具体地址时
# 或在没有设定密码时
# Redis将拒绝来自外部的连接
# protected-mode yes

# 监听端口
port 6380

# 启动时不打印logo
# 这个不重要,想看logo就打开它
always-show-logo no

# 设定密码认证
requirepass 123456

# 禁用KEYS命令
# 一方面 KEYS * 命令可以列出所有的键,会影响数据安全
# 另一方面 KEYS 命令会阻塞数据库,在数据库中存储了大量数据时,该命令会消耗很长时间
# 期间对Redis的访问也会被阻塞,而当锁释放的一瞬间,大量请求涌入Redis,会造成Redis直接崩溃
rename-command KEYS ""

# 此外还应禁止 FLUSHALL 和 FLUSHDB 命令
# 这两个命令会清空数据,并且不会失败

# 配置master节点信息
# 格式:
#slaveof <masterip> <masterport>
# 此处masterip所指定的redis-server-master是运行master节点的容器名
# Docker容器间可以使用容器名代替实际的IP地址来通信
slaveof 192.168.56.10 6379

# 设定连接主节点所使用的密码
masterauth "123456"

redis-slave2.conf

# bind 127.0.0.1

# 启用保护模式
# 即在没有使用bind指令绑定具体地址时
# 或在没有设定密码时
# Redis将拒绝来自外部的连接
# protected-mode yes

# 监听端口
port 6381

# 启动时不打印logo
# 这个不重要,想看logo就打开它
always-show-logo no

# 设定密码认证
requirepass 123456

# 禁用KEYS命令
# 一方面 KEYS * 命令可以列出所有的键,会影响数据安全
# 另一方面 KEYS 命令会阻塞数据库,在数据库中存储了大量数据时,该命令会消耗很长时间
# 期间对Redis的访问也会被阻塞,而当锁释放的一瞬间,大量请求涌入Redis,会造成Redis直接崩溃
rename-command KEYS ""

# 此外还应禁止 FLUSHALL 和 FLUSHDB 命令
# 这两个命令会清空数据,并且不会失败

# 配置master节点信息
# 格式:
#slaveof <masterip> <masterport>
# 此处masterip所指定的redis-server-master是运行master节点的容器名
# Docker容器间可以使用容器名代替实际的IP地址来通信
slaveof 192.168.56.10 6379

# 设定连接主节点所使用的密码
masterauth "123456"

docker-compose配置

sentinel和redis-server分别用两个docker-compose文件来管理

sentinel的docker-compose.yml文件

容器内部访问需要设置 networks

是先启动redis自定义后查看的网络

docker network ls

redis一主二从三哨兵搭建 redis一主一从两哨兵_docker_02

version: '3'

services:
  redis-sentinel-1:
    image: redis:4.0.12
    container_name: redis-sentinel-1
    restart: always
    # 为了规避Docker中端口映射可能带来的问题
    # 这里选择使用host网络
    network_mode: host
#    networks:
#      - server_my-redis-network
#    ports:
#      - 26379:26379
    volumes:
      - ./redis-sentinel-1.conf:/usr/local/etc/redis/redis-sentinel.conf
    # 指定时区,保证容器内时间正确
    environment:
      TZ: "Asia/Shanghai"
#    sysctls:
#      net.core.somaxconn: '511'
    command: ["redis-sentinel", "/usr/local/etc/redis/redis-sentinel.conf"]
  redis-sentinel-2:
    image: redis:4.0.12
    container_name: redis-sentinel-2
    restart: always
    network_mode: host
#    networks:
#      - server_my-redis-network
#    ports:
#      - 26380:26380
    volumes:
      - ./redis-sentinel-2.conf:/usr/local/etc/redis/redis-sentinel.conf
    environment:
      TZ: "Asia/Shanghai"
#    sysctls:
#      net.core.somaxconn: '511'
    command: ["redis-sentinel", "/usr/local/etc/redis/redis-sentinel.conf"]
  redis-sentinel-3:
    image: redis:4.0.12
    container_name: redis-sentinel-3
    restart: always
    network_mode: host
#    networks:
#      - server_my-redis-network
#    ports:
#      - 26381:26381
    volumes:
      - ./redis-sentinel-3.conf:/usr/local/etc/redis/redis-sentinel.conf
    environment:
      TZ: "Asia/Shanghai"
#    sysctls:
#      net.core.somaxconn: '511'
    command: ["redis-sentinel", "/usr/local/etc/redis/redis-sentinel.conf"]
#networks:
#  server_my-redis-network:
#    external: true

redis的docker-compose文件

version: '3'

services:
  # 主节点的容器
  redis-server-master:
    image: redis:4.0.12
    container_name: redis-server-master
    restart: always
    # 为了规避Docker中端口映射可能带来的问题
    # 这里选择使用host网络
    network_mode: host
#    ports:
#      - 6379:6379
#    networks:
#      - my-redis-network
    # 指定时区,保证容器内时间正确
    environment:
      TZ: "Asia/Shanghai"
    volumes:
      # 映射配置文件和数据目录
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
      - ./data/redis-master:/data
#    sysctls:
#      # 必要的内核参数
#      net.core.somaxconn: '511'
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  # 从节点1的容器
  redis-server-slave-1:
    image: redis:4.0.12
    container_name: redis-server-slave-1
    restart: always
    network_mode: host
#    ports:
#      - 6380:6380
#    networks:
#      - my-redis-network
    depends_on:
      - redis-server-master
    environment:
      TZ: "Asia/Shanghai"
    volumes:
      - ./redis-slave-1.conf:/usr/local/etc/redis/redis.conf
      - ./data/redis-slave-1:/data
#    sysctls:
#      net.core.somaxconn: '511'
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  # 从节点2的容器
  redis-server-slave-2:
    image: redis:4.0.12
    container_name: redis-server-slave-2
    restart: always
    network_mode: host
#    ports:
#      - 6381:6381
#    networks:
#      - my-redis-network
    depends_on:
      - redis-server-master
    environment:
      TZ: "Asia/Shanghai"
    volumes:
      - ./redis-slave-2.conf:/usr/local/etc/redis/redis.conf
      - ./data/redis-slave-2:/data
#    sysctls:
#      net.core.somaxconn: '511'
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
#networks:
#  my-redis-network:
#    driver: bridge

哨兵模式验证

启动redis

在对应的目录下启动redis

cd /root/redis-sentinel/server
docker-compose up -d
docker-compose ps

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis_03

启动sentinel

在对应的目录下启动sentinel

cd /root/redis-sentinel/sentinel
docker-compose up -d
docker-compose logs -f

观察sentinel的日志

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis_04

验证集群情况

docker exec -it redis-server-master  redis-cli -p 6379 -a '123456' info replication
docker exec -it redis-sentinel-1 redis-cli -p 26379 info sentinel
docker exec -it redis-sentinel-2 redis-cli -p 26380 info sentinel
docker exec -it redis-sentinel-3 redis-cli -p 26381 info sentinel

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis_05

新窗口执行

cd /root/redis-cluster/server
docker-compose stop redis-server-master

可以看到三个sentinel节点都监视了master节点,这个时候我们可以把master节点停下来试一下

redis一主二从三哨兵搭建 redis一主一从两哨兵_Redis_06

可以看到日志输出,有两个sentinel节点认为master节点主观下线之后,有一个节点认为master节点客观下线

redis一主二从三哨兵搭建 redis一主一从两哨兵_Redis_07


redis一主二从三哨兵搭建 redis一主一从两哨兵_redis一主二从三哨兵搭建_08

最终选举出

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis一主二从三哨兵搭建_09

此时,如果重启之前的 Master 节点,哨兵会发现节点上线,并不再主观认为该节点下线。但是,现在这个节点已经变成了一个 Slave 节点。

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis一主二从三哨兵搭建_10

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis一主二从三哨兵搭建_11

springboot集成

pom

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
@Controller
public class RedisController {

//    @Autowired
//    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @ResponseBody
    @RequestMapping("/test")
    public String test(){
        System.out.println("进入test方法 "+ LocalDateTime.now());
        int value= (int)(Math.random()*100);
        String v2="v"+value;
        redisTemplate.opsForValue().set("name",v2);
        String name = String.valueOf(redisTemplate.opsForValue().get("name"));
        return name;
    }
}

redis一主二从三哨兵搭建 redis一主一从两哨兵_docker_12

停止redis主节点

docker-compose stop redis-server-master

sentinel重新选择主节点

redis一主二从三哨兵搭建 redis一主一从两哨兵_redis_13

代码层面重新创建连接

redis一主二从三哨兵搭建 redis一主一从两哨兵_docker_14