一、前言

很多 Java 项目一开始用 Redis,都是单机部署。

本地开发时这样配置:

spring:
  data:
    redis:
      host: localhost
      port: 6379

测试环境这样用也没什么问题。

但到了生产环境,如果还是单机 Redis,就会有明显风险。

一旦 Redis 宕机,可能会导致:

  • 登录态失效
  • 缓存大量未命中
  • 分布式锁不可用
  • 限流失效
  • 热点接口直接打到数据库

所以生产环境里,Redis 通常不会单机裸跑,而是会使用:

  • 主从复制
  • Sentinel 哨兵
  • Redis Cluster 集群

很多人知道这些名词,但分不清它们到底解决什么问题。

本文就把 Redis 主从、哨兵、Cluster 的区别和选型讲清楚。


二、单机 Redis 有什么问题?

单机 Redis 最大的问题有两个:

单点故障
容量和性能上限

1. 单点故障

如果 Redis 只有一个节点:

应用 -> Redis 单节点

Redis 一挂,应用就无法访问缓存。

如果业务代码没有降级,可能出现:

大量请求阻塞
数据库压力暴增
接口超时

2. 容量和性能上限

单机 Redis 再强,也受限于:

  • 单机内存
  • 单机 CPU
  • 单机网络
  • 单机磁盘

如果数据量越来越大,单节点总会遇到上限。

所以 Redis 高可用和扩展就非常重要。


三、主从复制

1. 什么是主从复制?

主从复制就是一个 master 节点负责写,多个 slave 节点复制 master 的数据。

结构大概是:

        -> slave1
master  -> slave2
        -> slave3

master 接收写请求,然后把数据同步给 slave。

slave 可以用于:

  • 数据备份
  • 读请求分担
  • 故障切换基础

2. 主从复制解决什么问题?

主从复制主要解决两个问题:

读扩展
数据副本

如果读请求很多,可以让部分读请求访问从节点。

如果 master 挂了,从节点上还有数据副本。

3. 主从复制不能解决什么?

主从复制本身不能自动故障转移。

也就是说:

master 挂了,slave 不会自动变成 master。

如果没有其他机制,需要人工处理:

选一个 slave
提升为 master
修改客户端连接地址
恢复服务

这显然不适合生产环境。

所以还需要哨兵。

4. 主从复制怎么开启?

Redis 开启主从复制并不复杂,核心就是让从节点执行:

replicaof <master-ip> <master-port>

以前老版本 Redis 里也能看到 slaveof,新版本更推荐使用 replicaof

假设我们准备 3 个 Redis 节点:

master:10.0.0.10:6379
slave1:10.0.0.11:6379
slave2:10.0.0.12:6379

最简单的方式,是登录从节点执行命令。

slave1 上执行:

redis-cli -h 10.0.0.11 -p 6379

127.0.0.1:6379> replicaof 10.0.0.10 6379
OK

slave2 上执行:

redis-cli -h 10.0.0.12 -p 6379

127.0.0.1:6379> replicaof 10.0.0.10 6379
OK

这样两个从节点就会开始复制 master 的数据。

不过命令行方式有一个问题:Redis 重启后配置可能丢失。生产环境更推荐写到 redis.conf

从节点 redis.conf 示例:

port 6379
bind 0.0.0.0
daemonize yes

# 当前节点作为从节点,复制 10.0.0.10:6379
replicaof 10.0.0.10 6379

如果 master 设置了密码,还要在从节点配置 masterauth

requirepass redis123
masterauth redis123
replicaof 10.0.0.10 6379

这里容易搞混:

  • requirepass:别人访问当前 Redis 节点需要的密码;
  • masterauth:当前从节点连接 master 时使用的密码。

如果 master 有密码,但从节点没有配置 masterauth,复制会失败。

5. Docker 环境怎么模拟主从?

如果只是学习或测试,可以在本机用 Docker 快速启动 3 个 Redis。

先创建一个网络:

docker network create redis-net

启动 master:

docker run -d --name redis-master \
  --network redis-net \
  -p 6379:6379 \
  redis:7 redis-server --appendonly yes

启动两个 slave:

docker run -d --name redis-slave-1 \
  --network redis-net \
  -p 6380:6379 \
  redis:7 redis-server --replicaof redis-master 6379 --appendonly yes

docker run -d --name redis-slave-2 \
  --network redis-net \
  -p 6381:6379 \
  redis:7 redis-server --replicaof redis-master 6379 --appendonly yes

验证复制是否正常:

docker exec -it redis-master redis-cli

127.0.0.1:6379> set name luobin
OK

再到从节点读取:

docker exec -it redis-slave-1 redis-cli

127.0.0.1:6379> get name
"luobin"

能读到 master 写入的数据,就说明主从复制已经生效。

6. 如何查看主从状态?

Redis 提供了 info replication 命令。

在 master 上执行:

redis-cli -h 10.0.0.10 -p 6379 info replication

你会看到类似信息:

role:master
connected_slaves:2
slave0:ip=10.0.0.11,port=6379,state=online,offset=1024,lag=0
slave1:ip=10.0.0.12,port=6379,state=online,offset=1024,lag=0
master_repl_offset:1024

关键看几个字段:

  • role:master:当前节点是主节点;
  • connected_slaves:已连接从节点数量;
  • state=online:从节点在线;
  • lag:从节点延迟,越大说明复制越慢;
  • master_repl_offset:master 当前复制偏移量。

在 slave 上执行:

redis-cli -h 10.0.0.11 -p 6379 info replication

会看到类似:

role:slave
master_host:10.0.0.10
master_port:6379
master_link_status:up
slave_read_repl_offset:1024
slave_repl_offset:1024

重点看:

  • role:slave:当前节点是从节点;
  • master_link_status:up:和 master 连接正常;
  • slave_repl_offset:从节点同步到的偏移量。

如果 master_link_status:down,说明从节点没有连上 master,要检查网络、防火墙、密码和 replicaof 配置。

7. 主从复制的几个生产细节

第一,从节点默认只读。

Redis 从节点默认配置通常是:

replica-read-only yes

这表示从节点不允许写入。不要为了方便把它改成 no,否则从节点写入的数据不会同步回 master,故障切换后很容易出现脏数据。

第二,第一次同步可能比较重。

从节点第一次连接 master 时,通常会触发全量同步。master 会生成 RDB 快照并发送给从节点,从节点加载完成后再继续增量同步。

如果 Redis 数据量很大,第一次挂从库要避开业务高峰,并关注:

  • master CPU;
  • 网络带宽;
  • RDB 生成耗时;
  • 从节点加载耗时;
  • repl-backlog-size 是否足够。

第三,主从不是强一致。

写入 master 成功,不代表 slave 已经同步完成。读写分离时要接受短暂旧数据。如果业务刚写完必须马上读到最新值,就应该读 master,或者业务层做一致性兜底。

第四,主从只是哨兵和 Cluster 的基础。

主从复制本身只负责复制数据,不负责自动选主。master 挂了以后,如果没有 Sentinel 或 Cluster,客户端不会自动切到 slave。


四、复制延迟问题

Redis 主从复制是异步的。

也就是说,master 写入成功后,slave 可能还没来得及同步。

看这个场景:

1. 用户更新个人信息
2. 写入 master 成功
3. 立刻从 slave 查询
4. slave 还没同步完成,读到旧数据

这就是复制延迟。

所以读写分离时要注意:

刚写完就要立刻读最新数据,最好读 master。

不是所有读请求都适合走从库。


五、Sentinel 哨兵

1. 哨兵解决什么问题?

Sentinel 主要解决 Redis 主从架构下的自动故障转移问题。

它会做几件事:

  • 监控 master 和 slave 是否存活
  • master 故障时自动选举新的 master
  • 通知客户端新的 master 地址
  • 维护 Redis 主从拓扑

结构大概是:

Sentinel 集群
     |
  监控 Redis 主从

2. 为什么哨兵也要集群?

如果只有一个哨兵,那哨兵自己也会成为单点。

所以生产环境通常部署多个哨兵:

sentinel1
sentinel2
sentinel3

多个哨兵通过投票判断 master 是否真的故障。

这可以避免单个哨兵误判。

3. 哨兵故障切换过程

大致流程如下:

1. 哨兵发现 master 不可用
2. 多个哨兵确认 master 下线
3. 从 slave 中选出一个新的 master
4. 其他 slave 改为复制新 master
5. 客户端获取新的 master 地址

应用侧如果使用支持 Sentinel 的客户端,就能自动感知 master 变化。


六、Spring Boot 连接 Sentinel 示例

配置示例:

spring:
  data:
    redis:
      sentinel:
        master: mymaster
        nodes:
          - 10.0.0.1:26379
          - 10.0.0.2:26379
          - 10.0.0.3:26379
      timeout: 2s

这里的 master 不是 Redis 节点地址,而是哨兵配置中的 master 名称。

Java 客户端会通过哨兵获取当前真正的 master 地址。

所以不要在代码里写死 Redis master IP。

这里要特别注意一个容易误解的点:

Spring Boot 的 sentinel 配置,是让 Java 应用连接哨兵。
Redis 主从复制配置,是让 slave 节点复制 master。
这两件事不是一回事。

也就是说,下面这段配置:

spring:
  data:
    redis:
      sentinel:
        master: mymaster
        nodes:
          - 10.0.0.1:26379
          - 10.0.0.2:26379
          - 10.0.0.3:26379
      timeout: 2s

只能告诉 Java 客户端:

去连接这几个 Sentinel 节点,
然后问它们 mymaster 当前真正的 master Redis 是谁。

它不会自动帮你把 Redis 配成主从,也不会自动启动 Sentinel。

完整顺序应该是:

1. 先部署 Redis master
2. 再部署 Redis slave,并配置 replicaof
3. 再部署 Sentinel,让 Sentinel 监控这个 master
4. 最后 Spring Boot 配置 sentinel 节点,让应用通过 Sentinel 找 master

Sentinel 自己也要有配置文件,比如 sentinel.conf

port 26379
daemonize yes

sentinel monitor mymaster 10.0.0.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

如果 Redis master 有密码,还需要加:

sentinel auth-pass mymaster redis123

其中:

  • mymaster 要和 Spring Boot 里的 sentinel.master 保持一致;
  • 10.0.0.10 6379 是当前 Redis master 的地址;
  • 最后的 2 表示至少 2 个 Sentinel 认为 master 下线,才会触发客观下线判断。

所以 Spring Boot 配置的是“客户端入口”,Redis/Sentinel 配置的是“服务端高可用架构”。两边都配好,Java 项目才能真正具备 Redis 主从自动故障转移能力。


七、Sentinel 适合什么场景?

Sentinel 适合:

  • 数据量不算特别大
  • 单个 master 能扛住写入
  • 主要诉求是高可用
  • 不想引入太复杂的分片架构

比如大多数中小型 Java 项目:

Redis 用作缓存、登录态、限流、分布式锁
数据量几十 GB 以内
写入压力不极端

这种情况下,主从 + 哨兵通常就够了。


八、Redis Cluster

1. Cluster 解决什么问题?

Redis Cluster 主要解决的是水平扩展问题。

它把数据分散到多个 master 节点上。

Redis Cluster 使用 16384 个 hash slot。

每个 key 会根据 hash 结果落到某个 slot。

slot 再分配到不同 master 节点。

大致结构:

master1 负责一部分 slot
master2 负责一部分 slot
master3 负责一部分 slot

每个 master 通常还有自己的 slave。

2. Cluster 的优点

Cluster 的优点是:

  • 支持数据分片
  • 支持水平扩展
  • 单节点内存压力降低
  • 部分节点故障可以自动切换

当单机 Redis 容量不够时,Cluster 是常见选择。

3. Cluster 的限制

Cluster 也有一些限制。

比如多 key 操作。

如果执行:

mget user:1 order:1

这两个 key 如果不在同一个 slot,可能会报错。

因为 Redis Cluster 不支持跨 slot 的普通多 key 操作。

如果确实要让多个 key 落到同一个 slot,可以使用 hash tag:

user:{1001}:name
order:{1001}:list

{1001} 中的内容会用于计算 slot。


九、Spring Boot 连接 Cluster 示例

spring:
  data:
    redis:
      cluster:
        nodes:
          - 10.0.0.1:6379
          - 10.0.0.2:6379
          - 10.0.0.3:6379
          - 10.0.0.4:6379
          - 10.0.0.5:6379
          - 10.0.0.6:6379
      timeout: 2s

使用 Cluster 时,客户端要支持重定向。

因为请求某个 key 时,如果连接的节点不是该 key 所在 slot 的节点,Redis 会返回 MOVED 或 ASK,让客户端跳转到正确节点。

所以一定要使用支持 Cluster 的客户端。

比如:

  • Lettuce
  • Jedis
  • Redisson

十、Sentinel 和 Cluster 怎么选?

可以简单这样判断。

1. 只需要高可用

选择:

主从 + Sentinel

适合大多数普通业务。

优点:

  • 架构简单
  • 运维成本低
  • 客户端使用简单

缺点:

  • 写入仍然集中在一个 master
  • 单 master 容量有限

2. 需要水平扩展

选择:

Redis Cluster

适合:

  • 数据量很大
  • 单机内存不够
  • 写入吞吐很高
  • 需要分片扩展

缺点:

  • 架构复杂
  • 运维成本更高
  • 多 key 操作受限制
  • key 设计要更谨慎

十一、故障切换不是完全无感

很多人以为用了哨兵或 Cluster,Redis 故障切换就完全无感。

这不现实。

故障切换期间可能出现:

  • 短暂连接失败
  • 命令执行超时
  • 客户端重连
  • 部分写入失败
  • 主从数据短暂不一致

所以 Java 业务代码仍然要设置:

  • 连接超时
  • 读取超时
  • 失败重试
  • 熔断降级
  • 兜底逻辑

不要让 Redis 故障把 Tomcat 线程全部卡死。


十二、Java 项目中的建议

1. 不要无限等待 Redis

Redis 客户端必须设置超时。

比如:

spring:
  data:
    redis:
      timeout: 2s

如果没有超时,Redis 抖动时可能拖垮整个应用。

2. 缓存失败要允许降级

缓存读取失败时,可以考虑直接查数据库。

但要注意防止数据库被打爆。

可以配合:

  • 本地缓存
  • 限流
  • 熔断
  • 默认值

3. 锁失败要有业务策略

如果 Redis 分布式锁不可用,业务要明确:

是直接失败?
还是走数据库唯一约束兜底?
还是进入排队?

不要让异常逻辑随机发生。

4. 监控必须到位

至少要关注:

  • Redis 连接数
  • 内存使用率
  • QPS
  • 慢查询
  • 主从延迟
  • key 过期数量
  • 故障切换次数
  • 客户端超时数量

十三、常见坑

1. 以为主从能自动切换

主从复制本身不负责自动故障转移。

需要 Sentinel 或 Cluster。

2. 读写分离读到旧数据

主从复制有延迟。

刚写完就要读最新数据时,不要随便读 slave。

3. Cluster 中跨 slot 操作失败

多 key 操作要注意 key 是否在同一个 slot。

必要时使用 hash tag。

4. 客户端写死 Redis 地址

使用哨兵或 Cluster 时,客户端必须支持拓扑感知。

不要在代码里写死单个 master。

5. 没有降级

Redis 再高可用,也可能短暂不可用。

业务必须有兜底策略。


十四、总结

Redis 主从、哨兵、Cluster 解决的问题不一样。

简单总结:

  • 主从复制:解决数据副本和读扩展
  • Sentinel:解决主从架构的自动故障转移
  • Cluster:解决数据分片和水平扩展

选型建议:

中小型项目:主从 + Sentinel
大容量高吞吐项目:Redis Cluster

但不管用哪种架构,都不要忘记:

  • 客户端超时
  • 故障降级
  • 数据兜底
  • 监控告警

高可用不是部署几个节点就结束了。

真正的高可用,是 Redis 架构、客户端配置、业务降级和监控体系一起配合。

更多推荐