docker+redis+哨兵模式实现的高可用

redis的哨兵工作原理

1、哨兵每隔10秒向主节点发送一次ping,返回pong和从节点信息
2、哨兵通过向主节点的__sentinel__:hello 频道发送自己的ip和端口号,同时订阅该频道获取其他哨兵的信息,以此来发现彼此。哨兵之间每隔2秒回互相发送一个心跳包,确保集群状态(主要是对主节点的存活状态判断)。
3、当哨兵向主节点发送ping之后在sentinel down-after-milliseconds sentinel_redis_master设置的毫秒内没有返回,则主节点被标记为主观下线(odown,因此可以看到哨兵日志很多+odown,-odown等字样)
4、当某个哨兵发现主节点主观下线,会向其他哨兵发送命令询问是否下线(odown),如果大于等于设置的数量,则主节点标记为客观下线(sdown)。
5、当客观下线之后,哨兵就会选举出一个领导者的哨兵,由他来更新sentinel.conf文件,并发送给其他哨兵。然后筛选一个redis从节点作为主节点,并更新他的配置(去掉redis.conf的replicaof)。
6、根据第一步获取的从节点信息,将除了新主节点的从节点的配置文件修改为连接新的主节点(修改redis.conf的replicaof)

地址 名称 说明
192.168.157.130:6656 redis_master 主库
192.168.157.130:6657 redis_master_slave1 从库1
192.168.157.130:6658 redis_master_slave2 从库2
192.168.157.130:6659 redis_master_sentinel1 主库哨兵1
192.168.157.130:6660 redis_master_sentinel2 主库哨兵2
192.168.157.130:6661 redis_master_sentinel3 主库哨兵3

由于我虚拟机上已经装了docker,就不在这里安装了,如果没有docker请自行安装。本文只有redis的安装和哨兵的配置。最低配置1主对3哨兵,哨兵数量必须是奇数

1、拉取镜像

#最好带上版本号,CentOs 7已经不支持最新的redis,实测7.2.4版本支持
docker pull redis:7.2.4
#如果出现下面的内容,一般是没有配置镜像加速站点的原因。需要修改/etc/docker/daemon.json文件,修改之后分别执行sudo systemctl daemon-reload    sudo systemctl restart docker
Using default tag: latest
Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
#文件内容如下
{
  "registry-mirrors": [
      "https://12v5hh5l.mirror.aliyuncs.com",
      "https://docker.m.daocloud.io"
  ]
}  

2、创建相应的文件夹

#创建相应的文件夹
mkdir -p /etc/redis/{redis_master,redis_master_slave1,redis_master_slave2,redis_master_sentinel1,redis_master_sentinel2,redis_master_sentinel3}/{data,conf}
  
#启用内存过度提交
sudo vim /etc/sysctl.conf
# 添加这段内容
vm.overcommit_memory = 1

#保存之后执行,使配置生效
sudo sysctl -p

3、创建基础配置

#redis_master、redis_slave1、redis_slave2的目录都要配置以下内容,可以在redis_master目录创建了cp到其他节点的目录)
sudo tee /etc/redis/redis_master/conf/redis.conf <<-'EOF'
#========================= 基础配置  =========================
 #redis服务的启动端口
port 6379            
#允许所有IP访问 
bind 0.0.0.0               
#是否开启守护进程。docker环境必须关闭,不然会无限重启reids容器
daemonize no
#pid文件(跟conf文件是同一个目录)docker环境不需要此配置
#pidfile /usr/local/etc/redis/redis.pid  
#redis日志文件
logfile /usr/local/etc/redis/data/redis.log
#数据文件存储目录(AOF和RDB都在这里)
dir /usr/local/etc/redis/data
#========================= 基础配置  =========================

#========================= RDB持久化配置  =========================
# RDB原理:在一定时间内触发了指定时间的写入操作,redis就会将内存中的数据保存一次二进制快照文件(.rdb),使用RDB模式可以很快恢复数据,但是可能会丢失最后一次快照之后的数据。
# 10分钟(600秒)写入了5条数据,保存一次快照,保存一次之后又开始下一轮10分钟计时
save 600 5        
# 快照文件名
dbfilename dump.rdb 
# 禁用压缩算法。当rdbcompression yes时,文件体积减少约20-30%,但增加CPU开销, 生产环境建议禁用
rdbcompression no
# 分阶段刷盘降低I/O压力
rdb-save-incremental-fsync yes   
#=================================== RDB持久化配置  ===================================

#=================================== AOF持久化配置  ===================================
#AOF原理:每一次写操作的命令都写入AOF文件,如果文件太大了恢复速度就较慢,因为是按照文件内容一行一行的执行命令恢复数据。可读性比RDB好,可以查看写入的命令,使用everysec 最多丢失1秒的数据。
# 启用AOF日志追加模式(记录每个写操作命令)
appendonly yes
#AOF文件名
appendfilename "appendonly.aof"   
# 同步策略:
#everysec 每秒同步一次,平衡性能与数据安全
#always 每次写入同步,最安全但性能最低
#no 由OS决定同步时机,性能最高但风险最大(不建议使用)
appendfsync everysec 
# 自动重写触发条件。文件增长100%且超过100M时触发。
# 重写就是保留最新的操作命令,删除之前的操作命令,比如有个键UserCount,可能在用户上线的时候就加一次,下线的时候就减一次。比如SET UserCount=1,UserCount=2,UserCount=3,UserCount=2(下线一个)。最终重写之后的命令就是UserCount=2
# 第一次启动redis,达到了100m的时候重写一次,假设第一次重写之后文件减小到70M,那第二次只有达到140M(增长率100%)的时候再触发重写。假设第二次重写之后文件大小是40m,那第三次文件达到80m的时候不重写,只有超过100m才重写。 
#触发重写的文件大小增长率
auto-aof-rewrite-percentage 100  
#文件大小大于等于此条件才会重写,根据写入数据量的大小来设置。数据量写入特别频繁就需要设置大一点,避免频繁重写。
auto-aof-rewrite-min-size 100mb  
# 重写期间是否暂停AOF写入,生产环境最好设置no,保证数据安全性
no-appendfsync-on-rewrite no 
#========================= AOF持久化配置  =========================

#启用AOF和RDB混合模式(在AOF文件保存的时候,自动保存RDB快照)
aof-use-rdb-preamble yes   

#设置当主节点挂了,当前从节点被选举为新的主节点的权重。redis_master节点设置为100
replica-priority 100    
#配置密码为testredis
requirepass  testredis
#设置主从同步密码。三个节点都要设置,不然发生了故障迁移,redis_master变成从节点之后没有此字段,就无法连接到新的主节点
masterauth testredis   
#设置从节点只读数据
replica-read-only yes  

EOF

4、配置redis_master

vim /etc/redis/redis_master/conf/redis.conf
#加上下面的配置
#暴露主机名称到组从复制集群(配置文件必须加上这一段)
replica-announce-ip redis_master

5、配置redis_master_slave1、redis_master_slave2

vim /etc/redis/redis_master_slave1/conf/redis.conf
#加上下面的配置
#设置主库的ip和端口(必须是宿主机ip+映射端口,不能使用hostname,hostname在故障迁移的时候一直报错no-good-slave)。
replicaof 192.168.157.130 6656
#暴露主机名称到组从复制集群(配置文件必须加上这一段)
replica-announce-ip redis_master_slave1

vim /etc/redis/redis_master_slave2/conf/redis.conf
#加上下面的配置
#设置主库的ip和端口(必须是宿主机ip+映射端口,不能使用hostname,hostname在故障迁移的时候一直报错no-good-slave)。
replicaof 192.168.157.130 6656
#暴露主机名称到组从复制集群(配置文件必须加上这一段)
replica-announce-ip redis_master_slave2

6、配置哨兵节点(这里只配置了redis_master_sentinel1)

sudo tee /etc/redis/redis_master_sentinel1/conf/sentinel.conf <<-'EOF'
# 哨兵端口
port 26379
#允许所有IP访问
bind 0.0.0.0 
# 监控主节点配置。
# sentinel_redis_master:主节点的名称,可以定义但必须保证主节点所有哨兵都是一样的。 
# 192.168.157.130: 主节点的ip
# 6379: 主节点容器内部端口(不是宿主机映射端口)
# 2:判定主节点失效所需哨兵投票数(建议哨兵总数/2 +1)
sentinel monitor sentinel_redis_master 192.168.157.130 6656 2
# 主节点主观下线判定时间(毫秒),网络延迟大可以适当调大
sentinel down-after-milliseconds sentinel_redis_master 3000
# 故障转移总超时时间(毫秒), 包含选举、同步等全流程.60秒
sentinel failover-timeout sentinel_redis_master 60000
# 故障转移后同时同步从节点的数量。1表示单个节点同步,>1可加速同步,但增加主节点压力
sentinel parallel-syncs sentinel_redis_master 1
# 主节点认证密码(需与主节点redis.conf中的requirepass一致)
sentinel auth-pass sentinel_redis_master testredis
#启用主机名解析功能
sentinel resolve-hostnames yes
#哨兵进程工作目录 
dir /usr/local/etc/redis/data
# 日志文件路径(需确保存储权限)
logfile "/usr/local/etc/redis/data/sentinel.log"
# 禁用保护模式(否则无法被其他哨兵发现)
protected-mode no
# 哨兵进程是否启用守护模式(后台运行),docker环境必须关闭
daemonize no
# 系统日志级别(debug/verbose/notice/warning)
loglevel warning 
EOF

7、复制剩下两个哨兵节点的配置文件

 cp /etc/redis/redis_master_sentinel1/conf/sentinel.conf /etc/redis/redis_master_sentinel2/conf/sentinel.conf
 cp /etc/redis/redis_master_sentinel1/conf/sentinel.conf /etc/redis/redis_master_sentinel3/conf/sentinel.conf

8、设置文件权限(重要)

#linux权限数字组成:所有者+所属组+其他用户。每一个数字又是由写(2),读(4),执行(1)三个权限点
#组成,一般文件不需要执行权限。
#比如sudo chmod -R 644 /etc/redis/redis_master*/conf/*.conf,这里的644就代表
#所属用户可读写,所属组用户只读,其他用户只读

#设置redis容器挂载文件夹的归属权。第一个999是docker运行redis时候的用户id,第二个是组id
sudo chown -R 999:999 /etc/redis/redis_master* 
#设置文件权限。所属用户读写执行,所属组读执行,其他用户读
sudo chmod -R 754 /etc/redis/redis_master* 
#设置文件权限。所属用户读写,所属组读,其他用户无权限
sudo chmod -R 640 /etc/redis/redis_master*/conf/*.conf  

9、创建docker-compose文件

sudo tee /etc/redis/redis_docker_compose.yml <<-'EOF'
services:
    #主库服务名称
  redis_master:
    #替redis镜像ID或名称
    image: redis:7.2.4
    #容器名称
    container_name: redis_master
    #设置容器的主机名称为redis_master
    hostname: redis_master
    #设置除非手动停止,不然在意外终止的时候会一直重启
    restart: unless-stopped  
    networks:
      # 加入专用网络(确保跨容器通信)
      - redis-net
    #映射端口,宿主机端口:容器端口
    ports:
      - "6656:6379"
    deploy:
      resources:
        limits:
          # 限制1核CPU
          cpus: "1"
          # 限制1GB内存
          memory: 1024m
    volumes:
      # 挂载配置文件
      - /etc/redis/redis_master/conf:/usr/local/etc/redis
      # 挂载AOF、RDB持久化文件的目录
      - /etc/redis/redis_master/data:/usr/local/etc/redis/data
    #启动命令
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  #从库1服务名称
  redis_master_slave1:
    #替redis镜像ID或名称
    image: redis:7.2.4
    #容器名称
    container_name: redis_master_slave1
    #设置容器的主机名称为redis_master_slave1
    hostname: redis_master_slave1
    #设置除非手动停止,不然在意外终止的时候会一直重启
    restart: unless-stopped    
    networks:
      # 加入专用网络(确保跨容器通信)
      - redis-net
    #映射端口,宿主机端口:容器端口
    ports:
      - "6657:6379"
    deploy:
      resources:
        limits:
          # 限制1核CPU
          cpus: "1"
          # 限制1GB内存
          memory: 1024m
    volumes:
      # 挂载配置文件
      - /etc/redis/redis_master_slave1/conf:/usr/local/etc/redis
      # 挂载AOF、RDB持久化文件的目录
      - /etc/redis/redis_master_slave1/data:/usr/local/etc/redis/data
    #启动命令
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  #从库2服务名称
  redis_master_slave2:
    #替redis镜像ID或名称
    image: redis:7.2.4
    #容器名称
    container_name: redis_master_slave2
    #设置容器的主机名称为redis_master_slave2
    hostname: redis_master_slave2
    #设置除非手动停止,不然在意外终止的时候会一直重启
    restart: unless-stopped
    networks:
      # 加入专用网络(确保跨容器通信)
      - redis-net
    #映射端口,宿主机端口:容器端口
    ports:
      - "6658:6379"
    deploy:
      resources:
        limits:
          # 限制1核CPU
          cpus: "1"
          # 限制1GB内存
          memory: 1024m
    volumes:
      # 挂载配置文件
      - /etc/redis/redis_master_slave2/conf:/usr/local/etc/redis
      # 挂载AOF、RDB持久化文件的目录
      - /etc/redis/redis_master_slave2/data:/usr/local/etc/redis/data
    #启动命令
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  # 哨兵1配置
  redis_master_sentinel1:
    image: redis:7.2.4
    container_name: redis_master_sentinel1
    ports:
      - "6659:26379"
    volumes: 
      - /etc/redis/redis_master_sentinel1/conf/:/usr/local/etc/redis   
      - /etc/redis/redis_master_sentinel1/data:/usr/local/etc/redis/data 
    command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] # 启动哨兵进程
    #必须redis_master启动之后在启动
    depends_on:
      - redis_master
      - redis_master_slave1
      - redis_master_slave2
    networks:
      # 加入专用网络(确保跨容器通信)
      - redis-net
    deploy:
      resources:
        limits:
          # 限制0.4核CPU
          cpus: "0.5"
          # 限制256m内存
          memory: 256m
    #健康检查(自动剔除故障节点)
    healthcheck:
      # 检测哨兵状态
      test: ["CMD", "redis-cli", "-p", "26379", "sentinel", "masters"]
      # 每30秒检测一次
      interval: 30s
      # 超时时间
      timeout: 10s
      # 连续失败3次标记为不健康
      retries: 3

  # 哨兵2配置
  redis_master_sentinel2:
    image: redis:7.2.4
    container_name: redis_master_sentinel2
    ports:
      - "6660:26379"
    volumes: 
      - /etc/redis/redis_master_sentinel2/conf/:/usr/local/etc/redis  
      - /etc/redis/redis_master_sentinel2/data:/usr/local/etc/redis/data 
    command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] # 启动哨兵进程
    #必须redis_master启动之后在启动
    depends_on:
      - redis_master
      - redis_master_slave1
      - redis_master_slave2
    networks:
      # 加入专用网络(确保跨容器通信)
      - redis-net
    deploy:
      resources:
        limits:
          # 限制0.4核CPU
          cpus: "0.5"
          # 限制256m内存
          memory: 256m
    #健康检查(自动剔除故障节点)
    healthcheck:
      # 检测哨兵状态
      test: ["CMD", "redis-cli", "-p", "26379", "sentinel", "masters"]
      # 每30秒检测一次
      interval: 30s
      # 超时时间
      timeout: 10s
      # 连续失败3次标记为不健康
      retries: 3

  # 哨兵3配置
  redis_master_sentinel3:
    image: redis:7.2.4
    container_name: redis_master_sentinel3
    ports:
      - "6661:26379"
    volumes: 
      - /etc/redis/redis_master_sentinel3/conf/:/usr/local/etc/redis  
      - /etc/redis/redis_master_sentinel3/data:/usr/local/etc/redis/data 
    command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] # 启动哨兵进程
    #必须redis_master启动之后在启动
    depends_on:
      - redis_master
      - redis_master_slave1
      - redis_master_slave2
    networks:
      # 加入专用网络(确保跨容器通信)
      - redis-net
    deploy:
      resources:
        limits:
          # 限制0.4核CPU
          cpus: "0.5"
          # 限制256m内存
          memory: 256m
    #健康检查(自动剔除故障节点)
    healthcheck:
      # 检测哨兵状态
      test: ["CMD", "redis-cli", "-p", "26379", "sentinel", "masters"]
      # 每30秒检测一次
      interval: 30s
      # 超时时间
      timeout: 10s
      # 连续失败3次标记为不健康
      retries: 3 

networks:
  redis-net:
    driver: bridge 
EOF

10、使用docker compose启动所有容器

docker compose -f /etc/redis/redis_docker_compose.yml up -d
#查看docker运行状态
docker ps -a
#出现下面的内容就是成功。哨兵必须是healthy(如果显示starting,就等一段时间再查询)
[root@localhost data]# docker ps 
CONTAINER ID   IMAGE         COMMAND                   CREATED          STATUS                    PORTS                                                     NAMES
d7e9d0128455   redis:7.2.4   "docker-entrypoint.s…"   55 seconds ago   Up 54 seconds (healthy)   6379/tcp, 0.0.0.0:6659->26379/tcp, [::]:6659->26379/tcp   redis_master_sentinel1
d5c73c901bd1   redis:7.2.4   "docker-entrypoint.s…"   55 seconds ago   Up 54 seconds (healthy)   6379/tcp, 0.0.0.0:6661->26379/tcp, [::]:6661->26379/tcp   redis_master_sentinel3
beeb387d0a93   redis:7.2.4   "docker-entrypoint.s…"   55 seconds ago   Up 54 seconds (healthy)   6379/tcp, 0.0.0.0:6660->26379/tcp, [::]:6660->26379/tcp   redis_master_sentinel2
2e2281360aaf   redis:7.2.4   "docker-entrypoint.s…"   21 minutes ago   Up 21 minutes             0.0.0.0:6657->6379/tcp, [::]:6657->6379/tcp               redis_master_slave1
0fe4f5f12859   redis:7.2.4   "docker-entrypoint.s…"   21 minutes ago   Up 21 minutes             0.0.0.0:6658->6379/tcp, [::]:6658->6379/tcp               redis_master_slave2
c32273f3630b   redis:7.2.4   "docker-entrypoint.s…"   21 minutes ago   Up 21 minutes             0.0.0.0:6656->6379/tcp, [::]:6656->6379/tcp               redis_master

手动的停止redis_master检验哨兵是否有效

常用命令

#1、查看redis_master_sentinel1 容器的ip
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis_master_sentinel1 
 
#2、查看哨兵日志(如果容器成功启动)
cat /etc/redis/redis_master_sentinel1/data/sentinel.log 

#3、查看哨兵日志(如果容器启动失败)
docker logs redis_master_sentinel1 

常见问题

1、哨兵日志提示failover-abort-no-good-slave master…

#1、检查所有节点是否加入了redis-net组。redis-net就是在docker compose定义的网络。如果输出了哨兵,从节点,主节点,就是没问题(必须容器启动的时候才可以输出)。如果不存在某一个节点,比如redis_master_sentinel1。那么需要检查docker compose是否配置了redis-net。
docker network inspect redis_redis-net

#2、进入主库检查从库信息
#2.1、进入主库容器
docker exec -it redis_master  sh 
#2.2、查看从库,如果输出redis_master_slave1和redis_master_slave2就是正确的。如果输出的是一个ip,就说明没有在配置文件里面加上replica-announce-ip redis_master_slave1
redis-cli -a "testredis" info replication  

2、故障迁移之后,重启所有redis容器的问题

当停止了redis_master之后会自动选举新的主节点,这个过程需要一定时间,在这段时间不要做任何操作。然后启动redis_master,结果是redis_master成为了从节点。到此还正常,但是如果把所有的节点(包括哨兵)停止之后再启动,会发现redis_master又成为了主节点,且slave1和slave2并未连接到master。核心原因是我们docker compose配置的是master启动之后才启动哨兵,当哨兵发现只有一个master的时候,会把master设置为主节点,然后另外两个节点启动之后,就会发现连接配置不正确。解决办法是在全部重启的时候,手动修改docker compose文件,配置好正确的启动顺序。或者配置哨兵必须等所有节点启动之后再启动
另外在哨兵选举和修改配置的过程中不要去打开任何配置文件,否则会写入失败。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐