1. 三种高可用方案到底该怎么选
Redis 的高可用方案,绕来绕去其实就三条路:主从复制、哨兵模式、Cluster 集群。很多刚接触的朋友一上来就问“哪个最好”,这个问题本身就问错了——它们不是替代关系,而是递进关系,各自解决不同层面的问题。
主从复制解决的是数据冗余和读扩展,哨兵解决的是主节点故障自动切换,Cluster 解决的是数据分片和水平扩展。你可以只用主从,也可以主从加哨兵,但 Cluster 是另一套体系,它自带分片和故障转移能力,不需要哨兵参与。
我见过不少团队在只有几 GB 数据、单机完全扛得住的情况下硬上 Cluster,结果运维复杂度翻了三倍,节点间通信开销反而拖慢了性能。也见过数据量已经几十 GB 了还在用单主从,主节点内存快爆了才想起来扩容。所以选型的第一步不是看哪个高级,而是先算清楚自己的数据量、并发量、能接受的故障恢复时间这三个指标。
下面这张表可以先帮你快速定位:
| 方案 | 数据分片 | 自动故障转移 | 最小节点数 | 适用数据量 | 运维复杂度 |
|---|---|---|---|---|---|
| 主从复制 | 不支持 | 不支持 | 2 | 单机内存以内 | 低 |
| 哨兵模式 | 不支持 | 支持 | 3(哨兵)+2(数据) | 单机内存以内 | 中 |
| Cluster | 支持 | 支持 | 6(3主3从) | 可水平扩展 | 高 |
选型逻辑很直白:数据量在单机内存范围内、能接受几十秒的人工介入恢复,主从就够了;要求自动切换、业务不能长时间中断,加哨兵;数据量超过单机内存、或者写入并发已经逼近单机上限,直接上 Cluster。
注意:这里说的“单机内存以内”不是指物理内存大小,而是指 Redis 实例能安全承载的数据量。一般建议单实例数据量不超过物理内存的 70%,留出 RDB 持久化和复制缓冲区的空间。
2. 主从复制:最基础也最容易踩坑的一层
2.1 复制原理与核心参数
主从复制的本质是把主节点的写命令流同步到从节点。第一次建立连接时走全量同步,主节点 fork 出子进程生成 RDB 快照发给从节点,从节点加载完快照后再接收增量命令。之后的日常运行走增量同步,主节点把每条写命令传播给从节点。
这里有个关键概念叫replication backlog,它是主节点维护的一个环形缓冲区,默认 1MB。从节点断线重连时,如果断线期间主节点写入的数据量没超过这个缓冲区,就能做部分重同步,只补发缺失的那部分命令;超过了就只能再来一次全量同步。生产环境 1MB 往往不够,我一般会调到 16MB 甚至 64MB,具体看写入频率。
核心配置项如下:
# 从节点配置 replicaof 192.168.1.10 6379 masterauth your_password replica-read-only yes repl-backlog-size 64mb repl-timeout 60replica-read-only yes是默认值,从节点只读,防止误写导致数据不一致。repl-timeout控制复制超时时间,网络抖动频繁的环境可以适当调大。
2.2 搭建步骤与验证
假设主节点 192.168.1.10,从节点 192.168.1.11,端口都用 6379。
主节点配置:
bind 0.0.0.0 port 6379 requirepass your_password masterauth your_password daemonize yes logfile /var/log/redis/redis.log dir /var/lib/redis从节点配置:
bind 0.0.0.0 port 6379 requirepass your_password masterauth your_password replicaof 192.168.1.10 6379 replica-read-only yes daemonize yes logfile /var/log/redis/redis.log dir /var/lib/redis两边启动后,在主节点执行redis-cli -a your_password info replication,看到connected_slaves:1就说明从节点挂上来了。再在从节点执行同样的命令,master_link_status:up表示复制链路正常。
验证数据同步:主节点set testkey hello,从节点get testkey能拿到值就对了。注意从节点默认只读,写操作会报READONLY错误,这是预期行为。
2.3 主从模式的坑与注意事项
第一个坑是主节点重启后从节点全量同步。如果主节点没有开启持久化,重启后数据为空,从节点会清空自己的数据重新同步,等于数据全丢。所以主从架构下主节点必须开 AOF 或 RDB,我一般两个都开。
第二个坑是复制风暴。一个主节点挂太多从节点时,每次全量同步都要 fork 一次,fork 本身会阻塞主线程。从节点数量建议控制在 5 个以内,更多从节点可以挂到从节点下面做级联复制。
第三个坑是密码配置不一致。主节点设了requirepass,从节点必须配masterauth,否则复制链路建立不起来,日志里会报NOAUTH。这个错误很常见,排查时先看日志。
实操心得:主从搭建完成后,一定要手动 kill 掉主节点进程,观察从节点日志里
MASTER <-> REPLICA sync的状态变化,确认从节点能感知到主节点下线。这个测试能提前暴露很多配置问题。
3. 哨兵模式:让故障切换自动化
3.1 哨兵的工作原理
哨兵本身是一个独立的进程,它不存储数据,只负责监控、通知和自动故障转移。多个哨兵组成一个哨兵集群,通过Raft 类协议选举出 leader,由 leader 执行故障转移。
哨兵判断主节点下线的逻辑分两步:主观下线和客观下线。单个哨兵发现主节点在down-after-milliseconds时间内没响应,标记为主观下线;当足够数量(quorum)的哨兵都认为主节点下线了,才标记为客观下线,然后触发选举和故障转移。
故障转移的过程是:从从节点中选一个升级为主节点,让其他从节点指向新主节点,通知客户端新主节点地址。选主节点的依据是优先级、复制偏移量、runid,优先级高的优先,优先级相同看谁的数据最新。
3.2 哨兵集群搭建实操
哨兵的最小生产配置是 3 个节点,保证选举时能形成多数派。假设三台机器 192.168.1.10、11、12,每台都跑一个 Redis 实例和一个哨兵进程。
哨兵配置文件sentinel.conf:
port 26379 daemonize yes logfile /var/log/redis/sentinel.log sentinel monitor mymaster 192.168.1.10 6379 2 sentinel auth-pass mymaster your_password sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000逐项解释:sentinel monitor mymaster 192.168.1.10 6379 2里的2是 quorum,表示至少 2 个哨兵认为主节点下线才触发故障转移。down-after-milliseconds 30000是 30 秒无响应判定主观下线,这个值别设太小,网络抖动容易误判。parallel-syncs 1表示故障转移后同时只让 1 个从节点同步新主节点,避免新主节点被同步请求打满。
三台机器配置一样,只是sentinel monitor里写的主节点地址相同。启动命令:
redis-sentinel /etc/redis/sentinel.conf启动后执行redis-cli -p 26379 sentinel master mymaster,能看到主节点信息和哨兵数量。再执行sentinel sentinels mymaster可以看到其他哨兵节点。
3.3 故障转移验证与客户端接入
验证故障转移:直接 kill 掉主节点进程,观察哨兵日志。正常情况下 30 秒后哨兵会标记主观下线,再经过选举触发故障转移,日志里会出现+switch-master字样,后面跟着新旧主节点地址。
客户端接入哨兵模式时,不能直连 Redis 地址,而是连接哨兵集群,通过SENTINEL get-master-addr-by-name mymaster获取当前主节点地址。主流客户端库都支持哨兵模式,配置里填哨兵节点列表和 master 名称即可。
Java 客户端示例配置:
Set<String> sentinels = new HashSet<>(); sentinels.add("192.168.1.10:26379"); sentinels.add("192.168.1.11:26379"); sentinels.add("192.168.1.12:26379"); JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, "your_password");3.4 哨兵模式的常见问题
脑裂问题:网络分区时,旧主节点还在接受写入,哨兵已经选出了新主节点,等网络恢复后旧主节点的数据会被清空重新同步。缓解办法是配置min-replicas-to-write和min-replicas-max-lag,让主节点在从节点数量不足或延迟过大时拒绝写入。
min-replicas-to-write 1 min-replicas-max-lag 10这表示至少 1 个从节点延迟在 10 秒以内,主节点才接受写入,否则返回错误。这个配置能有效减少脑裂期间的数据丢失。
哨兵节点数必须是奇数:3 个、5 个都行,2 个不行。因为 quorum 要过半,2 个哨兵时 quorum 至少是 2,挂一个就永远无法触发故障转移。
故障转移后配置自动重写:哨兵会自动修改自己的配置文件和 Redis 的配置文件,把新主节点地址写进去。所以哨兵配置文件所在目录要有写权限,否则故障转移后重启会丢失状态。
踩过的坑:有一次哨兵配置文件放在只读目录,故障转移成功了,但哨兵重启后配置回滚到旧主节点地址,导致整个集群状态混乱。后来统一把配置目录权限设为可写,这个问题再没出现过。
4. Cluster 集群:数据分片与水平扩展
4.1 哈希槽与分片原理
Cluster 的核心是16384 个哈希槽。每个 key 通过CRC16(key) mod 16384计算出属于哪个槽,每个主节点负责一部分槽。3 主节点时,槽的分配通常是 0-5460、5461-10922、10923-16383。
为什么是 16384 而不是 65536?官方给出的理由是:节点间心跳包需要携带槽位图,16384 个槽的位图是 2KB,65536 个是 8KB,心跳包太大会浪费带宽;而且 Cluster 节点数一般不超过 1000 个,16384 个槽足够分配。
Cluster 的故障转移和哨兵不同,它不需要额外进程。每个主节点都带至少一个从节点,主节点之间互相心跳检测。当多数主节点认为某个主节点下线时,该主节点的从节点会发起选举,升级为主节点接管槽位。
4.2 三主三从集群搭建
最小生产集群是 3 主 3 从,共 6 个节点。假设 6 台机器或 6 个端口,这里用端口区分:7000-7005。
每个节点的配置文件redis-7000.conf:
port 7000 bind 0.0.0.0 daemonize yes logfile /var/log/redis/redis-7000.log dir /var/lib/redis/7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 appendonly yes requirepass your_password masterauth your_password关键配置是cluster-enabled yes和cluster-config-file,后者由 Redis 自动维护,不要手动改。cluster-node-timeout 15000是节点超时时间,15 秒无响应判定下线。
6 个节点都启动后,用redis-cli --cluster create创建集群:
redis-cli --cluster create \ 192.168.1.10:7000 192.168.1.10:7001 192.168.1.10:7002 \ 192.168.1.10:7003 192.168.1.10:7004 192.168.1.10:7005 \ --cluster-replicas 1 -a your_password--cluster-replicas 1表示每个主节点配 1 个从节点。执行后会显示槽位分配方案,输入yes确认。创建完成后用redis-cli -c -p 7000 cluster info查看集群状态,cluster_state:ok表示正常。
4.3 集群操作与数据分布验证
Cluster 模式下客户端必须用-c参数才能自动重定向:
redis-cli -c -p 7000 -a your_password写入一个 key,如果它不属于当前节点负责的槽,会返回MOVED重定向到正确节点。-c参数让客户端自动跟随重定向。
查看槽位分配:cluster slots命令列出每个主节点负责的槽范围。查看节点状态:cluster nodes列出所有节点及其角色、槽位、连接状态。
验证故障转移:kill 掉一个主节点进程,等待cluster-node-timeout后,它的从节点会升级为主节点。用cluster nodes可以看到角色变化,原来从节点的slave标记变成master。
4.4 Cluster 的批量操作与限制
Cluster 最大的限制是跨槽操作。mget、mset涉及多个 key 时,如果这些 key 不在同一个槽,会报CROSSSLOT错误。解决办法是用hash tag,把相关的 key 用{}包裹相同的内容,强制它们落到同一个槽:
mset user:{1000}:name "Alice" user:{1000}:age 30{1000}就是 hash tag,Redis 只对花括号内的内容做 CRC16 计算,所以这两个 key 一定在同一个槽。
另一个限制是Lua 脚本,脚本里涉及的所有 key 也必须在同一个槽。事务multi/exec同理。
实操心得:设计 key 命名规范时就要考虑 hash tag。比如订单相关的 key 统一用
order:{orderId}:xxx,这样订单的所有字段都在同一个槽,批量操作和事务都不会出问题。这个规范要在项目初期定好,后期改起来非常痛苦。
5. 三种方案的问题排查速查表
实际运维中遇到的问题五花八门,我把最常见的整理成一张表,方便快速定位:
| 现象 | 可能原因 | 排查命令 | 解决方向 |
|---|---|---|---|
从节点master_link_status:down | 网络不通、密码错误、主节点挂了 | info replication、看日志 | 检查网络、核对masterauth |
| 哨兵不触发故障转移 | quorum 不足、哨兵数量不够 | sentinel master mymaster | 增加哨兵节点、调整 quorum |
Clustercluster_state:fail | 槽位未全覆盖、多数主节点下线 | cluster info、cluster nodes | 补全槽位、恢复节点 |
MOVED重定向频繁 | 客户端未开-c、槽位迁移中 | cluster nodes | 客户端开启集群模式 |
CROSSSLOT错误 | 批量操作跨槽 | 检查 key 命名 | 使用 hash tag |
| 主从数据不一致 | 脑裂、复制中断 | info replication对比 offset | 配置min-replicas-to-write |
| Cluster 节点无法加入 | 端口未开放、集群总线端口不通 | cluster meet测试 | 开放端口+10000 的总线端口 |
关于 Cluster 的端口,有个容易忽略的点:Cluster 总线端口是普通端口加 10000。比如节点端口 7000,总线端口就是 17000。防火墙必须同时开放这两个端口,否则节点间无法通信,集群建不起来。这个坑我踩过不止一次,排查半天才发现是防火墙只开了 7000 没开 17000。
6. 生产环境的经验与建议
6.1 持久化策略的选择
主从和哨兵模式下,主节点建议开 AOF,appendfsync everysec兼顾性能和安全。从节点可以只开 RDB,用于备份和快速恢复。Cluster 模式下每个节点都建议开 AOF,因为故障转移后新主节点需要尽快恢复数据。
AOF 重写会 fork 子进程,fork 时主线程阻塞,数据量越大阻塞越久。单实例数据量控制在 10GB 以内,fork 阻塞通常在毫秒级;超过 20GB 就要谨慎了,建议在低峰期手动触发bgrewriteaof。
6.2 内存与连接数规划
单实例maxmemory建议设为物理内存的 70%,留 30% 给 fork 时的写时复制、复制缓冲区和系统开销。maxmemory-policy根据业务选,缓存场景用allkeys-lru,持久化数据用noeviction并配合监控告警。
连接数方面,maxclients默认 10000,一般够用。但要注意每个从节点和哨兵都会占用主节点的连接,Cluster 节点间还有总线连接。连接数接近上限时,新连接会被拒绝,日志里报max number of clients reached。
6.3 监控指标不能少
不管用哪种方案,这几个指标必须监控:主从复制偏移量差值、哨兵数量、Cluster 槽位覆盖状态、各节点内存使用率、连接数、慢查询数量。复制偏移量差值持续增大说明从节点跟不上主节点,可能是网络问题或从节点负载过高。
慢查询用slowlog get 10查看,slowlog-log-slower-than默认 10000 微秒,生产环境建议调到 5000 微秒,及早发现性能问题。
6.4 扩容与缩容的注意事项
主从和哨兵模式扩容只能加从节点,主节点内存到顶了就得换更大的机器,或者迁移到 Cluster。Cluster 扩容用redis-cli --cluster add-node加新节点,然后用--cluster reshard迁移槽位。迁移槽位时数据会逐个 key 搬移,大 key 会导致迁移卡顿,所以提前用--bigkeys扫描并拆分大 key。
缩容时先迁移槽位再删除节点,顺序反了会导致数据丢失。迁移过程中集群状态是ok,但部分 key 会返回ASK重定向,客户端需要支持ASK重定向才能无缝迁移。
最后分享一个小技巧:不管哪种方案,搭建完成后一定要做一次全链路故障演练。主节点 kill、从节点 kill、哨兵 kill、网络分区模拟,每种场景都跑一遍,记录恢复时间和数据丢失情况。演练中暴露的问题,远比生产环境出事后再排查代价小得多。我在实际项目中坚持每次架构变更都做演练,好几次提前发现了配置遗漏,避免了线上事故。