前阵子把一个项目的 Redis 从单机改成哨兵模式,整个过程踩了不少坑,也把之前只停留在理论层面的自动故障转移和读写分离真正跑了一遍。这篇把我实际设计思路、配置细节和踩坑记录整理出来,给正在做同样改造的人省点时间。
先交代一下背景:线上有一台 Redis 单点,平时看着挺稳,结果有一天凌晨它挂了,上游的请求瞬间后撤到数据库,重试风暴直接把数据库也压垮了。那次事故之后我的目标就很明确:Redis 不能单点裸奔,至少要保证主节点挂了能自动切换;顺便把读流量从主节点上分担出去,降低主节点压力。
这篇文章会从环境搭建讲起,到 Spring Boot 配置、读写分离实现、故障转移实测,再到常见问题的排查思路,全程都是可以照着复现的操作。适合正在维护小型到中型 Redis 部署、想把高可用和读写分离落地的 Java 后端同学,也适合准备面试时想真正理解哨兵机制的人。
1. 为什么必须折腾哨兵模式
1.1 单节点 Redis 的痛点与事故复盘
先说说那次事故。凌晨的流量其实不高,但那个 Redis 节点承载的不只是缓存,还有一个延时队列和一个库存预扣的计数。节点一挂,所有依赖 Redis 的接口基本全部超时,服务端的重试逻辑开始疯狂重发,数据库连接池瞬间被打满,最终整个应用不可用。事后复盘,最根本的原因就两个:没有副本、没有自动切换机制。
很多团队对 Redis 的定位是“缓存而已”,挂了重新加载就行。但 Redis 一旦承担了分布式锁、计数、队列这些有状态的功能,单节点就变成了系统里最脆弱的环节。所以我个人的建议是,只要项目里有用到 Redis 做“非纯缓存”的事情,就应该认真考虑主从加哨兵的架构,这比临时扩容一台机器靠谱得多。
1.2 主从复制和哨兵模式的区别
主从复制解决的是读扩展和容灾备份的问题:主节点负责写,从节点复制主节点的数据,读请求可以打到从节点。但主从复制有一个致命缺陷:主节点挂了,系统不会自动把从节点提升为主节点,需要人工介入。
哨兵模式就是在主从复制之上增加了监控、通知和自动故障转移三个能力。用一个不太严谨但好理解的比喻:哨兵就像小区保安,不仅实时盯着大门是否正常,还和隔壁几个保安互相通信;主大门一坏,立刻把备用门启用,住户几乎无感。
哨兵自身的三个核心机制值得展开一下:
- 监控:每个哨兵每秒向主从节点发送 PING,超过 down-after-milliseconds 配置的时间没响应,就标记为主观下线。
- 通信与决议:哨兵之间通过 Redis 的发布订阅机制交换信息,当标记主节点主观下线的哨兵数量达到 quorum 配置的值,就升级为主观下线判定后的客观下线。
- 故障转移:由选举出的 leader 哨兵挑选一个从节点提升为新主节点,并让其他从节点去复制新主节点。
这三个机制决定了哨兵模式对外表现出的“自动”和“高可用”,后面的故障转移实测部分我会展示完整的日志链路。
1.3 为什么不直接用 Redis Cluster
有人可能会问:既然要高可用,为什么不直接上 Redis Cluster。我在这个项目里没选 Cluster,主要是这几个原因:第一,项目写扩展需求不高,主节点写入量远没到单机瓶颈;第二,Cluster 限制了多键操作,比如跨 slot 的事务、Lua 脚本、MGET 都需要在同一个哈希槽里才能保证原子性,老代码改造量不小;第三,哨兵模式对已有代码几乎无感,只需要改 spring 配置和少量 Bean,迁移成本低。
哨兵模式适合的数据量级是单机内存能扛住的场景,比如几十 GB 以内的缓存、会话、计数类数据。如果数据量已经多到单机装不下,或者写并发高到单点 CPU 扛不住,那确实应该考虑 Cluster。但如果你只是想要高可用,哨兵模式是性价比最高的选择。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:用 Docker Compose 一分钟拉起主从加哨兵
2.1 目录规划与配置文件清单
为了快速复现,我在本机和测试环境都用 Docker Compose 来部署,整体结构是:1 个主节点、2 个从节点、3 个哨兵节点。这个组件的数量是经过考虑的:哨兵要满足 quorum 和 majority,最少也要 3 个节点才能在一个节点挂掉后还能完成选举;从节点建议至少 2 个,既承担读流量,也在主节点故障后有余量。
目录结构如下,配置全部放在 config 目录,方便统一管理和修改:
bash复制redis-sentinel-demo/
├── docker-compose.yml
├── config/
│ ├── master.conf
│ ├── replica1.conf
│ ├── replica2.conf
│ ├── sentinel1.conf
│ ├── sentinel2.conf
│ └── sentinel3.conf
2.2 主从节点 Redis 配置
主节点的配置很简单,我用的 Redis 7.0,开启 AOF 保证持久化:
conf复制# master.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
appendfsync everysec
两个从节点配置基本相同,唯一的关键是增加 replicaof 指向主节点:
conf复制# replica1.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
appendfsync everysec
replicaof redis-master 6379
replica-read-only yes
replica-read-only 这个参数默认值是 yes,也就是从节点默认只读,这个必须保持开启,否则可能出现从节点被误写入数据,造成主从数据不一致。从节点追主节点的过程,推荐配合 AOF 一起使用,虽然全量同步依赖的是 RDB,但 AOF 能在节点意外重启后快速恢复本地数据,减少主从需要重新全量同步的概率。
2.3 哨兵节点配置与关键参数含义
三个哨兵节点配置内容基本一样,只有端口和日志输出的容器名不同:
conf复制# sentinel1.conf
port 26379
bind 0.0.0.0
protected-mode no
sentinel monitor mymaster redis-master 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1
这里几个参数逐个解释一下:
sentinel monitor mymaster redis-master 6379 2:监控名为 mymaster 的 Redis 主从组,主节点地址是 docker-compose 里的服务名 redis-master,端口 6379,最后的 2 是 quorum 值,表示至少 2 个哨兵认为主节点不可达,才判定为客观下线。down-after-milliseconds5000:哨兵连续 5 秒 PING 不通主节点,就标记主观下线。不能配太小,网络抖动可能造成误判;也不能配太大,故障转移的时间会拉长。failover-timeout10000:故障转移的超时时间,超过这个时间还没完成就取消并重试。一般要大于 down-after-milliseconds,给选举和切换留出余量。parallel-syncs1:故障转移后,同时允许多少个从节点去同步新主节点。生产环境建议就配 1,避免新主节点瞬间被多个从节点同时全量同步拖垮。
2.4 启动、自检与常见网络坑
docker-compose.yml 的核心部分如下,重点是所有服务在同一个自定义网络里,互相之间用服务名通信:
yaml复制version: '3.8'
services:
redis-master:
image: redis:7.0
container_name: redis-master
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./config/master.conf:/usr/local/etc/redis/redis.conf
ports:
- "6379:6379"
networks:
- redis-sentinel-net
redis-replica-1:
image: redis:7.0
container_name: redis-replica-1
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./config/replica1.conf:/usr/local/etc/redis/redis.conf
ports:
- "6380:6379"
networks:
- redis-sentinel-net
depends_on:
- redis-master
redis-replica-2:
image: redis:7.0
container_name: redis-replica-2
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./config/replica2.conf:/usr/local/etc/redis/redis.conf
ports:
- "6381:6379"
networks:
- redis-sentinel-net
depends_on:
- redis-master
sentinel1:
image: redis:7.0
container_name: sentinel1
command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"]
volumes:
- ./config/sentinel1.conf:/usr/local/etc/redis/sentinel.conf
ports:
- "26379:26379"
networks:
- redis-sentinel-net
depends_on:
- redis-master
sentinel2:
image: redis:7.0
container_name: sentinel2
command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"]
volumes:
- ./config/sentinel2.conf:/usr/local/etc/redis/sentinel.conf
ports:
- "26380:26379"
networks:
- redis-sentinel-net
depends_on:
- redis-master
sentinel3:
image: redis:7.0
container_name: sentinel3
command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"]
volumes:
- ./config/sentinel3.conf:/usr/local/etc/redis/sentinel.conf
ports:
- "26381:26379"
networks:
- redis-sentinel-net
depends_on:
- redis-master
networks:
redis-sentinel-net:
driver: bridge
启动命令就两行:
bash复制docker compose config -q
docker compose up -d
启动后先检查主从同步状态,进入任一 Redis 容器执行:
bash复制docker exec -it redis-master redis-cli INFO replication
正常情况下可以看到 connected_slaves:2,说明两个从节点已经成功挂上来了。
这里一定要提醒一个 Docker 环境的经典坑:sentinel monitor 里的主节点地址写成 redis-master 这个容器服务名,在容器网络内部是通的;但 Spring Boot 应用如果跑在宿主机上,通过哨兵获取到的“新主节点地址”会是容器 IP,比如 172.20.0.2,应用直接连这个地址大概率连不上。解决办法有两种:一是让应用也部署在同一个 Docker 网络内,二是给三个 Redis 容器设置固定的容器 IP,并在哨兵配置里用 announce-ip 指定哨兵和主节点对外可达的地址。这个不配置,故障转移测试的时候就会出现“哨兵以为自己切换成功了,但应用根本连不上新主”的情况。
3. Spring Boot 集成:从普通连接改为哨兵模式
3.1 依赖和配置文件的版本差异
Spring Boot 集成 Redis 不需要额外引入特定依赖,用 spring-boot-starter-data-redis 就行。重点留意一下配置的变更:Spring Boot 2.x 的配置前缀是 spring.redis,3.x 改成了 spring.data.redis。如果你在升级 Spring Boot 3.x 或 4.x,原来的哨兵配置不迁移是会被静默忽略的。
我以 Spring Boot 3.x 为例,application.yml 里的配置是这样:
yaml复制spring:
data:
redis:
timeout: 3s
lettuce:
pool:
max-active: 32
max-idle: 16
min-idle: 4
sentinel:
master: mymaster
nodes:
- 192.168.1.10:26379
- 192.168.1.11:26379
- 192.168.1.12:26379
nodes 填的是宿主机上 26379、26380、26381 三个映射端口对应的地址。Spring Data Redis 会随机挑一个哨兵节点发起连接,并通过 SENTINEL get-master-addr-by-name mymaster 拿到当前主节点地址,再建立真正的连接。某个哨兵节点不可用时,Lettuce 会自动切换到下一个哨兵节点获取拓扑,这就是哨兵模式下客户端具备的基本容错能力。
3.2 Lettuce 哨兵拓扑刷新原理
Lettuce 是 Spring Boot 默认的 Redis 客户端,它对哨兵模式的支持比较完善。启动时会向哨兵集群获取主节点信息,然后在后台维持一个拓扑结构,RedisTemplate 每次读写时根据这个拓扑路由到具体节点。当发生故障转移后,Lettuce 会周期性地刷新拓扑,把新主节点地址更新到连接信息里,不需要重启应用。
Spring Data Redis 2.5 以后的版本默认支持 Lettuce 的哨兵拓扑刷新,大多数情况下主节点切换后应用会在几十秒内自动恢复连接。如果你还在用很老的 Spring Boot 版本,或者遇到切换后一直连不上原地址的情况,可以通过自定义 LettuceConnectionFactory 来开启和调整刷新策略。
3.3 连接测试与序列化规范
配置完成后,写一个简单的接口验证读写:
java复制@RestController
@RequestMapping("/redis")
public class RedisTestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/set")
public String set(String key, String value) {
stringRedisTemplate.opsForValue().set(key, value);
return "ok";
}
@GetMapping("/get")
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
}
还有个建议:在项目里把 StringRedisTemplate 和 RedisTemplate 的定位分清楚。StringRedisTemplate 默认用 String 序列化,RedisTemplate 默认用 JdkSerializationRedisSerializer。如果不做任何处理,RedisTemplate 写入的 key 在 Redis Desktop Manager 里看起来会是一长串十六进制乱码。所以统一序列化方案非常必要,常见配置是 key 用 StringRedisSerializer,value 用 GenericJackson2JsonRedisSerializer。这个话题单独展开又是一篇长文,这里先提醒一句:序列化方案最好在项目一开始就定好,中途切换会面临存量数据无法反序列化的风险。
4. 读写分离落地:从节点读、主节点写
4.1 ReadFrom 五种模式怎么选
Spring Data Redis 的 Lettuce 客户端提供了 readFrom 配置,用来控制读请求的路由策略。可选值有五个,理解起来很直观:
| 配置值 | 行为 | 适用场景 |
|---|---|---|
| MASTER | 只读主节点 | 强一致要求,默认配置 |
| MASTER_PREFERRED | 优先读主节点,主不可用读从 | 要求接近强一致 |
| REPLICA | 只读从节点,从节点全部不可用时报错 | 读多写少,容忍一定延迟 |
| REPLICA_PREFERRED | 优先读从节点,从不可用读主 | 读多写少,也接受主可用性降级 |
| NEAREST | 读网络距离最近的节点 | 跨机房部署,减少延迟 |
我的建议是生产环境稳妥选 REPLICA_PREFERRED,也就是说正常情况下读流量都走从节点,如果从节点因为故障或正在同步暂时不可用,读请求会降级到主节点,业务不会直接报错。REPLICA 模式如果从节点全挂,可能会造成大面积读失败,适合对主节点可用性容忍度极低的内部系统。
具体实现有两种方式。第一种最简单,在 Lettuce 客户端配置上增加自定义配置:
java复制@Bean
public LettuceClientConfigurationBuilderCustomizer lettuceConfigurationCustomizer() {
return builder -> builder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
第二种是直接定义连接工厂,适合需要同时控制多个参数的情况:
java复制@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("192.168.1.10", 26379)
.sentinel("192.168.1.11", 26379)
.sentinel("192.168.1.12", 26379);
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
注意一下,一旦自定义了 LettuceConnectionFactory,原来 application.yml 里配置的 pool 参数、超时时间可能不会自动生效,需要自己在 ClientConfiguration 里补上,或者只使用第一种 Customizer 的方式,让 Spring Boot 自动配置保持生效,这也是我实际更推荐的做法。
4.2 哪些操作必须强制走主节点
readFrom 配置是全局生效的,但有些操作不能走从节点。比如事务(MULTI/EXEC)、Lua 脚本(EVAL)、带原子性的命令如 INCR、DECR、SETNX,以及在分布式锁场景里用到的 SET NX EX 等,这些都必须打到主节点才能保证原子性和一致性。从节点即使配置了 replica-read-only yes,也不允许执行写命令,所以全局 readFrom 改成从节点优先之后,以上操作如果还走从节点,就会直接报 READONLY 错误。
实际项目中通常的做法是维护两个 RedisTemplate:一个全局读多写少,默认 REPLICA_PREFERRED;另一个专门用于强一致操作,强制走主节点。两个模板各自连接同一个连接工厂,通过不同的 readFrom 设置实现路由差异。我这边就是这么做的,用起来没有副作用。
还有一点容易忽略:刚写完立刻去读,如果走从节点,可能因为主从复制延迟读到旧值。对于这种强一致的读写场景,比如支付回调更新库存后立即回查,建议强制走主节点,或者接受几毫秒到几十毫秒的延迟。主从复制是异步的,这一点从架构上就无法完全消除。
4.3 实测验证读写分离
配置改完,如何确认读请求真的走到了从节点。我一开始直接打印 RedisTemplate 的当前连接对象,发现拿到的是抽象封装,看不到 role。后面换成从 Lettuce 原生连接里执行 INFO replication 就清楚了:
java复制public String currentRedisRole() {
return stringRedisTemplate.execute((RedisCallback<String>) connection -> {
Object nativeConn = connection.getNativeConnection();
if (nativeConn instanceof io.lettuce.core.api.StatefulRedisConnection) {
io.lettuce.core.api.StatefulRedisConnection<?, ?> conn =
(io.lettuce.core.api.StatefulRedisConnection<?, ?>) nativeConn;
return conn.sync().info("replication");
}
return "unsupported";
});
}
注意,哨兵模式下连接池里的连接可能同时包含主节点和从节点,单次打印只能代表当前拿到的连接。更直观的验证方式是在两个从节点容器上看连接数变化:
bash复制docker exec -it redis-replica-1 redis-cli INFO clients
docker exec -it redis-replica-2 redis-cli INFO clients
当我持续调用读接口时,两个从节点的客户端连接数明显上升,而主节点的连接数基本保持稳定,这就证明读流量确实被分流到从节点了。配合 Redis Desktop Manager 或 Another Redis Desktop Manager 观察,也能看到主从节点的数据是同步的。
5. 故障转移实测:把主节点杀了看会发生什么
5.1 哨兵从发现到完成转移的完整链路
理论说了一堆,最过瘾的还是亲手把主节点干掉。测试步骤是:先确定当前主节点是 redis-master,然后执行 docker stop redis-master,接着立刻观察哨兵日志。
bash复制docker logs -f sentinel1
日志会按顺序出现这样几条关键信息:
text复制26379:X 05 Jun 2024 10:00:00.123 # +sdown master mymaster 172.20.0.2 6379
26379:X 05 Jun 2024 10:00:01.456 # +odown master mymaster 172.20.0.2 6379 #quorum 2/2
26379:X 05 Jun 2024 10:00:01.457 # +new-epoch 1
26379:X 05 Jun 2024 10:00:01.458 # +try-failover master mymaster 172.20.0.2 6379
26379:X 05 Jun 2024 10:00:01.459 # +vote-for-leader 3c0c4db...
26379:X 05 Jun 2024 10:00:02.101 # +selected-slave slave 172.20.0.3:6379 ...
26379:X 05 Jun 2024 10:00:02.102 * +promoted-slave slave 172.20.0.3:6379 ...
26379:X 05 Jun 2024 10:00:02.120 # +switch-master mymaster 172.20.0.2 6379 172.20.0.3 6379
整个链路拆开来看就是:哨兵连续 5 秒没有收到主节点响应,第一次出现 +sdown,标记主观下线;随后 3 个哨兵之间经过通信,有 2 个哨兵确认主节点不可达,满足 quorum,触发 +odown,标记客观下线;接着进入选举阶段,其中一个哨兵被推选为本次故障转移的 leader;leader 从存活从节点里挑选一个数据最完整的节点,发出 +selected-slave、+promoted-slave;最后确认新主节点,并通知其他从节点切换复制源。docker stop 和日志显示的时间间隔大概只有几秒,和配置的 down-after-milliseconds 基本吻合。
5.2 故障转移期间应用的表现
故障发生的那几秒钟,应用肯定不可能完全无感。实测下来,写入请求会短暂报错,比如 RedisConnectionFailureException、Cannot get connection 之类的异常,持续时间和 down-after-milliseconds 设置有关。等哨兵完成切换、Lettuce 刷新拓扑后,应用会自动恢复,旧主节点重新启动后也会自动降级为从节点。
我在代码里做了一个快速验证,循环往 Redis 写数据并捕获异常,观察日志可以看到:开始报错约 6 秒,哨兵完成切换,随后写入恢复,总耗时在 10 秒以内。这说明这套机制的“自动”是真自动,但也提醒我们业务层要考虑故障期间的降级方案,比如短时间内的写入失败需要由业务重试或返回友好提示,不能指望故障转移完全掩盖这十几秒的窗口。
拓扑刷新周期这个参数也可以在 Lettuce 里调小,让应用更快感知到新主节点:
java复制@Bean
public ClientOptions clientOptions() {
return ClientOptions.builder()
.topologyRefreshOptions(TopologyRefreshOptions.builder()
.enablePeriodicRefresh()
.refreshPeriod(Duration.ofSeconds(10))
.build())
.build();
}
注意这个方法需要和 LettuceClientConfigurationBuilderCustomizer 配合使用,不同 Spring Data Redis 版本的配置方式略有差别,关键是理解思路:把默认几十秒的拓扑刷新周期缩短到 10 秒左右,故障转移后的恢复时间就能缩短。
5.3 故障转移参数调优经验
故障转移涉及时间相关的参数主要是三个,我给出实际比较推荐的一套组合:
text复制sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1
第一个参数控制主观下线的判定时间,如果网络环境抖动频繁,比如跨机房部署,可以适当调大到 8000 或 10000,避免哨兵误判导致频繁切换。第二个参数要从业务可接受的延迟角度考虑,建议是第一个参数的 2 倍以上。第三个参数不建议调大,一次故障转移只让一个从节点先同步,避免新主节点压力过大。
还有一个容易被忽略的点:如果 Redis 配置了密码,主从节点之间需要 masterauth,哨兵也需要通过 auth-pass 来执行 INFO 和 PING。这个配置漏掉的话,哨兵会一直报认证失败,主从同步和数据迁移都会失败,整个高可用等于没搭。
6. 常见问题与排查实录
6.1 高频问题与解决思路
我整理了一下这次实操中遇到的问题,有些是我自己的踩坑,有些是朋友项目里遇到后问我的,总共这几类最常见:
第一个是哨兵能监控到主从,但 Spring Boot 连接不上。这种情况先检查哨兵配置里 monitor 的地址是不是容器内服务名。应用跑在宿主机时,如果哨兵返回的是 172.x 容器 IP,应用自然连不上,解决办法上面已经说过,用 announce-ip 固定对外地址,或者让应用也进这个 Docker 网络。
第二个是故障转移后应用一直连的是旧主节点。先查 Lettuce 版本,Spring Data Redis 2.5 之前对哨兵拓扑刷新支持不完善,故障转移后可能需要手动重置连接工厂。升级版本后再开启周期刷新,问题基本能解决。
第三个是主从数据不一致。最典型的原因是从节点被误写入数据,或者从节点长时间断连后复制中断。排查命令很简单,进入 Redis 执行 INFO replication,重点看 master_replid、master_repl_offset,以及日志里 lag 过大或握手中的字眼。
6.2 问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 应用启动提示 Unable to connect to Redis | sentinel nodes 配置错误或哨兵未启动 | 检查哨兵端口连通性,逐个验证 26379 端口 |
| 写入时报 READONLY | 读写分离后写命令命中了从节点 | 把事务、脚本等强一致操作单独路由到主节点 |
| 故障转移后应用仍连旧主节点 | Lettuce 拓扑刷新未开启或版本过旧 | 升级 Spring Data Redis,开启周期刷新 |
| 哨兵日志出现认证失败 | Redis 配置密码但主从和哨兵未配置 auth | 从节点加 masterauth,哨兵加 auth-pass |
| Docker 内主从同步正常但宿主机连不上 | 容器 IP 对外不可达 | 使用 announce-ip 指定宿主机可达地址 |
| Redis Desktop Manager 显示 key 乱码 | RedisTemplate 默认 JDK 序列化 | 统一使用 StringRedisSerializer 和 GenericJackson2JsonRedisSerializer |
6.3 安全加固与权限控制
Redis 默认配置并不安全,尤其是部署到公网或测试环境,裸奔的 6379 端口经常被扫描爆破。给几个最实际的建议:生产环境必须开启 requirepass,考虑使用 Redis 7.0 的 ACL 为用户单独分配权限;主从和哨兵之间全部用密码通信;保护 Spring Boot 的 Actuator 端点,特别是 /actuator/env、/actuator/configprops 这类会泄露配置信息的端点,Redis 的连接地址和密码很可能就在其中。之前我排查过一起线上信息泄露事件,入口就是这个未经权限控制的 Actuator 端点,所以这一点值得特别提醒。
7. 写在最后,一点实际体会
整套方案跑下来,我的感受是哨兵模式的“自动故障转移”并不复杂,真正的复杂度在于参数调优、Docker 网络隔离、客户端拓扑刷新这些容易忽略的细节。读写分离也并不是在 YAML 里加一行 readFrom 就完事,事务、脚本、强一致读这些场景都需要单独设计路由。
如果你是第一次做这个改造,建议先在本机把 1 主 2 从 3 哨兵的 Docker 环境搭起来,亲手杀掉主节点看一遍完整日志,再对照这篇文章去排查应用端的表现。把这个流程跑通之后,再上生产就会踏实很多。以后如果形势需要横向扩展写能力,从哨兵模式平滑迁移到 Cluster 也是一个水到渠成的过程,核心组件复用度很高。祝各位一次切换成功,少熬夜。
