为什么用 Sentinel

普通主从复制只有一个 master:master 挂掉后,需要运维手工把某个 slave 升成 master,再让其他 slave 切换指向新 master。Sentinel 就是为了自动化这件事:

  • 监控:持续检查 master / slave 是否健康
  • 通知:通过 API / pub-sub 让应用知道状态变化
  • 自动故障转移:master 失效时,从 slave 中投票选新 master
  • 配置提供者:客户端连接 Sentinel,自动获取当前 master 地址

一个 Sentinel 集群至少 3 个节点,避免单点。quorum 通常设为 2(多数派)。

节点拓扑

角色            容器名             IP             client / sentinel
master          redisMaster       172.21.0.2    6668
slave 1         redisSlaves01     172.21.0.3    6669
slave 2         redisSlaves02     172.21.0.4    6670
slave 3         redisSlaves03     172.21.0.5    6671
sentinel 1      redisSentinel01   172.21.0.6    26668
sentinel 2      redisSentinel02   172.21.0.7    26669
sentinel 3      redisSentinel03   172.21.0.8    26670

配置文件

三份配置分别放 ./conf/:

master-redis.conf

bind 0.0.0.0
port 6379
protected-mode no

# 持久化
appendonly yes
appendfsync everysec

# 认证
requirepass <REPLACE_WITH_STRONG_PASSWORD>
masterauth  <REPLACE_WITH_STRONG_PASSWORD>

slave-redis.conf

bind 0.0.0.0
port 6379
protected-mode no

appendonly yes
appendfsync everysec

requirepass <REPLACE_WITH_STRONG_PASSWORD>
masterauth  <REPLACE_WITH_STRONG_PASSWORD>

# Redis 5+ 用 replicaof(slaveof 仍兼容)
replicaof 172.21.0.2 6379

replica-serve-stale-data yes
replica-read-only yes

master-sentinel.conf

bind 0.0.0.0
port 26379
daemonize no

# 监控 master(最后那个 2 是 quorum)
sentinel monitor mymaster 172.21.0.2 6379 2

# master 30 秒没回应视为下线
sentinel down-after-milliseconds mymaster 30000

# 故障转移超时
sentinel failover-timeout mymaster 60000

# 同时同步新 master 的 slave 数量
sentinel parallel-syncs mymaster 1

# 认证
sentinel auth-pass mymaster <REPLACE_WITH_STRONG_PASSWORD>
Sentinel 配置里监控的 IP 要用 master 的实际可达地址,不要写 127.0.0.1——容器间靠 172.21.0.0/16 互通。

docker-compose 编写

version: "3.8"

services:
  master:
    image: redis:7.2-alpine
    container_name: redisMaster
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./conf/master-redis.conf:/usr/local/etc/redis/redis.conf
    ports: [ "6668:6379" ]
    networks:
      cluster_net: { ipv4_address: 172.21.0.2 }

  slaves01:
    image: redis:7.2-alpine
    container_name: redisSlaves01
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./conf/slave-redis.conf:/usr/local/etc/redis/redis.conf
    ports: [ "6669:6379" ]
    networks: { cluster_net: { ipv4_address: 172.21.0.3 } }

  slaves02:
    image: redis:7.2-alpine
    container_name: redisSlaves02
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./conf/slave-redis.conf:/usr/local/etc/redis/redis.conf
    ports: [ "6670:6379" ]
    networks: { cluster_net: { ipv4_address: 172.21.0.4 } }

  slaves03:
    image: redis:7.2-alpine
    container_name: redisSlaves03
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./conf/slave-redis.conf:/usr/local/etc/redis/redis.conf
    ports: [ "6671:6379" ]
    networks: { cluster_net: { ipv4_address: 172.21.0.5 } }

  sentinel01:
    image: redis:7.2-alpine
    container_name: redisSentinel01
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./conf/master-sentinel.conf:/usr/local/etc/redis/sentinel.conf
    ports: [ "26668:26379" ]
    networks: { cluster_net: { ipv4_address: 172.21.0.6 } }

  sentinel02:
    image: redis:7.2-alpine
    container_name: redisSentinel02
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./conf/master-sentinel.conf:/usr/local/etc/redis/sentinel.conf
    ports: [ "26669:26379" ]
    networks: { cluster_net: { ipv4_address: 172.21.0.7 } }

  sentinel03:
    image: redis:7.2-alpine
    container_name: redisSentinel03
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./conf/master-sentinel.conf:/usr/local/etc/redis/sentinel.conf
    ports: [ "26670:26379" ]
    networks: { cluster_net: { ipv4_address: 172.21.0.8 } }

networks:
  cluster_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/16

启动与验证

docker compose up -d

# 看 master 视角的副本
docker exec redisMaster redis-cli -a <PASSWORD> info replication
# role:master
# connected_slaves:3

# 看 Sentinel 视角
docker exec redisSentinel01 redis-cli -p 26379 sentinel masters
# 1) "name"
# 2) "mymaster"
# 3) "ip"
# 4) "172.21.0.2"
# 5) "port"
# 6) "6379"
# ...
# 14) "num-slaves"
# 15) (integer) 3
# 16) "num-other-sentinels"
# 17) (integer) 2

故障转移演示

手动杀掉 master:

docker stop redisMaster

等 30 秒(down-after-milliseconds 默认值),Sentinel 会判定 master 不可达。再次查看:

docker exec redisSentinel01 redis-cli -p 26379 sentinel masters

输出里 ip 应已变成某个 slave 的 IP(如 172.21.0.3),且 role-reported 为 master。

把老 master 启动回来:

docker start redisMaster

它会以 slave 身份重新加入(这是 Sentinel 的标准行为——避免脑裂)。

客户端接入

应用不应该硬编码 master IP,而是连 Sentinel 自动发现当前 master:

# Python redis-py
from redis.sentinel import Sentinel
sentinel = Sentinel(
    [("172.21.0.6", 26379),
     ("172.21.0.7", 26379),
     ("172.21.0.8", 26379)],
    sentinel_kwargs={"password": "<PASSWORD>"},
    password="<PASSWORD>",
)
master = sentinel.master_for("mymaster")
master.set("foo", "bar")
// Java Lettuce
RedisURI sentinelUri = RedisURI.create("redis://:<PASSWORD>@172.21.0.6:26379");
RedisClient client = RedisClient.create(sentinelUri);
StatefulRedisMasterConnection master =
    MasterClient.connect(client,
        ImmutableList.of("172.21.0.6", "172.21.0.7", "172.21.0.8"),
        "mymaster");

使用准则

  1. Sentinel 至少 3 节点,quorum 多数派。2 节点不行(脑裂风险)。
  2. Sentinel 自身也要持久化(dir),重启后才知道 master 是谁。
  3. down-after-milliseconds 别设太小,否则网络抖动会误触发切换。
  4. 应用必须用支持 Sentinel 的客户端,否则故障切换后连不上。
参考资料