Redis 集群部署与调优实战:从主从到哨兵到 Cluster 的完整演进 原创

温馨提示:
本文最后更新于 2026-09-15,已超过 0 天没有更新。 若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我

Redis 集群部署与调优实战:从主从到哨兵到 Cluster 的完整演进

Redis 作为最流行的内存数据库,几乎每个互联网后端都在用它扛缓存、队列和分布式锁。但当你的业务从一台机器上的小打小闹,长成需要扛住千万级 QPS 的分布式系统时,单机 Redis 的瓶颈会逐一暴露:单点故障、内存上限、吞吐天花板。

本文不讲理论空谈,直接用可复用的配置和脚本,带你走完 Redis 从单机 → 主从 → 哨兵 → Cluster的完整演进路线,并把持久化、内存管理、性能调优、监控告警、生产加固这些硬功夫一并讲透。

一、演进路线:什么时候该升级,选哪个方案

很多人一上来就想上 Cluster,其实是不必要的。选型的第一性原理是:用当前最小复杂度解决当前真实问题。下面这张表决定了你该站在哪一级台阶上:

阶段 读 QPS 数据量 核心痛点 推荐方案
单机 < 5万 < 20GB 可用性差,宕机即失忆 单机 + AOF
成长 5万 ~ 20万 20~80GB 读压力大、怕宕机 主从 + 读写分离
关键业务 20万 ~ 50万 80~150GB 自动故障转移 哨兵 (Sentinel)
大规模 > 50万 > 150GB / 多机房 容量、多写、水平扩展 Cluster 分片

决策口诀:

  • 怕丢数据 → 先上持久化,再谈高可用。
  • 怕宕机 → 主从打底。
  • 要自动切换、无需人工介入 → 上哨兵。
  • 单机内存装不下、要水平扩展 → 上 Cluster。

注意:哨兵和 Cluster 不是替代关系。哨兵解决的是高可用(failover),Cluster 解决的是高扩展(分片)。如果你数据量不大但要求 99.99% 可用,哨兵足够;只有当你需要把数据摊到多台机器上时,Cluster 才登场。

二、主从复制:replicaof 与同步机制

主从复制是 Redis 高可用的地基。配置极其简单,在从节点 redis.conf 里加一行:

# 从节点配置 (redis-slave.conf)
replicaof 10.0.0.10 6379
replica-read-only yes          # 从节点只读,防止写错
repl-backlog-size 64mb         # 复制积压缓冲区
repl-backlog-ttl 3600          # 积压缓冲区存活时间(秒)
replica-priority 100           # 哨兵选举时从节点的优先级(越小越优先)

主从复制分两种模式:

  • 全量同步 (Full Resync):从节点第一次连主,或复制中断过久、积压缓冲区已被覆盖时触发。主节点执行 BGSAVE 生成 RDB,传给从节点加载,期间新写入的命令记录在积压缓冲区里,RDB 传完后一并补发。耗时与数据量成正比,是阻塞主从的元凶。
  • 增量同步 (Partial Resync):连接短暂中断后,从节点用 PSYNC <runid> <offset> 带上自己的复制偏移量,只要该偏移量仍在主的积压缓冲区内,就能只补发缺失的命令,秒级恢复,几乎无感。

复制积压缓冲区调优是减少全量同步的关键。它是个环形缓冲区,用来保存最近写入的命令,以便断连的从节点追进度。调优要点:

# 按"断连期间可能产生的写入量"估算 backlog 大小
# 经验公式:repl-backlog-size ≈ 平均写吞吐(MB/s) × 期望容忍断连时间(s)
# 例如写吞吐 5MB/s,希望容忍 60 秒断连:
repl-backlog-size 512mb

如果 backlog 太小,从节点断连几十秒后重连,触发的不再是增量同步而是全量同步,主节点会瞬间 CPU 飙升、网络打满,甚至拖垮整个集群。所以 backlog 宁可大一点。

还要注意 避免主节点磁盘压力:全量同步时主节点执行 BGSAVE 会 fork 子进程,若磁盘慢会阻塞主线程。可开启 repl-diskless-sync yes,让主节点直接把 RDB 通过 socket 传给从节点,不落盘。

repl-diskless-sync yes
repl-diskless-sync-delay 5     # 延迟几秒等待多个从节点同时同步,避免反复传

三、哨兵模式:自动故障切换

主从复制解决了”读扩展”和”宕机兜底”,但主节点宕机后从节点不会自动上位,需要人工介入。哨兵 (Sentinel) 就是来解决这个问题的:它监控所有 Redis 节点,主节点挂了自动把某个从节点提升为新主。

哨兵本身也要高可用,通常部署 3 个(奇数个)哨兵进程。核心配置:

# sentinel.conf
port 26379
sentinel monitor mymaster 10.0.0.10 6379 2
sentinel auth-pass mymaster yourpassword
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 15000
sentinel parallel-syncs mymaster 1

关键参数逐个拆解:

  • sentinel monitor mymaster <ip> <port> 2:最后那个 2quorum,表示至少 2 个哨兵认为主节点主观下线 (sdown) 后,才判定为客观下线 (odown) 并触发故障转移。quorum 必须小于等于哨兵数量的一半以上,否则无法达成共识。公式:quorum ≥ 哨兵总数/2 + 1
  • down-after-milliseconds 5000:哨兵在 5 秒内 ping 不通主节点,就主观判定它下线。这个值影响故障发现速度,太小会误判(网络抖动),太大会拖慢切换。建议 3000~10000。
  • failover-timeout 15000:故障转移总超时,包含选举、晋升、通知的整个过程。
  • parallel-syncs 1:新主产生后,同时允许多少个从节点去同步新主。设 1 避免一次全量同步打爆新主。

Leader 选举机制:当判定主节点客观下线后,哨兵之间会用 SENTINEL is-master-down-by-addr 相互拉票,采用类 Raft 的机制选出 Leader 哨兵,由它执行故障转移。选举规则:

  1. 候选哨兵给其他哨兵发投票请求,每个哨兵在故障转移超时时间内只投一票;
  2. 获得超过 quorum(且过半)选票的哨兵当选 Leader;
  3. Leader 从从节点里挑新主,优先选 replica-priority 最小、复制偏移量最大、runid 最小的那个。

启动哨兵并验证:

# 启动 3 个哨兵
redis-server sentinel.conf --sentinel
redis-server sentinel2.conf --sentinel
redis-server sentinel3.conf --sentinel

# 查看哨兵视角的主从状态
redis-cli -p 26379 sentinel masters
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster

客户端必须用哨兵 SDK(如 Redis 的 Sentinel 客户端),它会自动从哨兵拿当前主节点地址,并在故障切换后自动重连新主。如果用普通客户端直连旧主 IP,切换后应用就断了。

四、Redis Cluster:16384 槽位分片

当数据量突破单机内存,或写入也需要水平扩展时,上 Redis Cluster。它把整个键空间切成 16384 个哈希槽 (hash slot),公式为 slot = CRC16(key) % 16384,每个节点负责一部分槽位。

官方推荐至少 6 个节点(3 主 3 从)。每个节点开启 cluster 模式:

# redis-cluster-node.conf
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
appendonly yes

节点握手与初始化:

# 用 redis-cli 一键创建 3 主 3 从集群
redis-cli --cluster create \
  10.0.0.10:7000 10.0.0.11:7001 10.0.0.12:7002 \
  10.0.0.13:7003 10.0.0.14:7004 10.0.0.15:7005 \
  --cluster-replicas 1

创建时 redis-cli 会自动把 16384 个槽位均分给 3 个主节点,并为每个主节点配 1 个从节点。

MOVED / ASK 重定向:这是 Cluster 的核心机制。客户端(如 redis-py-cluster、JedisCluster)会维护槽位与节点的映射表 (slot map)。当键的槽位不在当前节点时:

# 客户端没更新映射时,节点返回 MOVED 并告知正确节点
GET user:1001
(error) MOVED 14924 10.0.0.11:7001

# 迁移过程中的键,返回 ASK(临时迁移到目标节点)
(error) ASK 14924 10.0.0.11:7001
  • MOVED:槽位已永久归属另一节点,客户端应更新本地映射并重定向到新节点。属于正常的槽位归属变更。
  • ASK:槽位正处于迁移中,客户端应先用 ASKING 命令再访问目标节点,但不要更新映射(迁移完成后会收到 MOVED)。

这也是 Cluster 对客户端的硬性要求:必须用支持 Cluster 协议的客户端,普通客户端无法自动处理 MOVED/ASK。

集群伸缩(在线扩缩容):Cluster 最大的价值是可以在线加节点、搬槽位,不停服。

# 1. 加入新节点
redis-cli --cluster add-node 10.0.0.16:7006 10.0.0.10:7000

# 2. 把部分槽位从旧节点迁到新节点(交互式分配)
redis-cli --cluster reshard 10.0.0.16:7006

# 3. 为扩容节点补从节点
redis-cli --cluster add-node 10.0.0.17:7007 10.0.0.16:7006 --cluster-slave

# 4. 下线节点(先迁空它的槽位)
redis-cli --cluster del-node 10.0.0.11:7001 <node-id>

槽位迁移是逐个键进行的,期间键处于迁移中间态,客户端用 ASK 处理,因此业务无感。但要注意:迁移大量 key 时会消耗带宽和 CPU,建议低峰期操作,且迁移粒度是 key 而非槽位整体,大 key 迁移尤其慢。

五、数据持久化:RDB vs AOF

内存数据再快,断电即失。持久化是 Redis 的命根子,两种方案各有取舍:

方案 机制 优点 缺点 适用
RDB 定时 fork 子进程生成二进制快照 恢复快、文件紧凑、易备份 两次快照间可能丢数据、fork 耗内存 可容忍分钟级丢失的缓存
AOF 追加每一条写命令日志 数据丢失少,可到秒级 文件大、恢复慢 对数据一致性要求高的业务

生产上更推荐混合持久化(Redis 4.0+ 支持),同时开 RDB 和 AOF,兼顾两者的优点:

# redis.conf 持久化配置
save 900 1              # 900秒内至少1次写,触发RDB
save 300 10             # 300秒内至少10次写
save 60 10000           # 60秒内至少1万次写

appendonly yes          # 开启AOF
appendfsync everysec    # 每秒刷盘,性能与安全的平衡点
aof-use-rdb-preamble yes  # 混合持久化:AOF文件头部是RDB,后续是增量命令
auto-aof-rewrite-percentage 100  # AOF体积增长100%触发重写
auto-aof-rewrite-min-size 64mb    # AOF至少64MB才重写

AOF 重写优化:AOF 会无限增长,Redis 通过后台子进程重写 (rewrite),把当前内存状态重新生成一份紧凑的命令序列,替换旧 AOF,体积大幅缩小。触发条件就是上面两个 auto-aof-rewrite-*。手动触发:

redis-cli bgrewriteaof

重写期间新写入会先缓冲到 aof_rewrite_buf,重写完成后合并,不会阻塞也不丢数据。注意重写会 fork 子进程,内存占用峰值约为主进程的 1~2 倍,要给足物理内存或开启 vm.overcommit_memory=1

appendfsync 三档选择:

  • always:每条命令都 fsync,最安全但最慢(QPS 骤降);
  • everysec(推荐):每秒刷盘,最多丢 1 秒数据,性能几乎无损;
  • no:交给操作系统决定刷盘时机,最快但可能丢较多数据。

六、内存管理:淘汰策略与碎片监控

Redis 是内存数据库,内存耗尽会直接拒绝写入甚至 OOM。必须设置 maxmemory 并选择淘汰策略:

maxmemory 4gb
maxmemory-policy allkeys-lru

淘汰策略选型(重点):

策略 含义 适用场景
noeviction 内存满时拒绝写,报错 不允许丢任何数据的业务(如缓存了未落库的热数据)
allkeys-lru 从所有键中淘汰最久未使用 通用缓存,热数据访问频次差异大
volatile-lru 仅淘汰设置了 TTL 的键中最久未使用 希望无 TTL 的键永不被淘汰
allkeys-lfu 按访问频次淘汰(LFU) 热点集中、有”秒杀”等突发热key场景
volatile-random 从有过期时间的键中随机淘汰 对淘汰哪个不敏感

LRU vs LFU:

  • LRU(最近最久未使用):看”多久没被访问”,适合访问分布均匀的场景,但对偶发访问的热 key 可能误杀(一次访问就能”续命”很久)。
  • LFU(最近最少使用):看”访问频次”,通过计数衰减来反映真实热度,对”秒杀热 key”更精准,但实现复杂、占用更多内存。Redis 用近似算法,可通过 lfu-log-factorlfu-decay-time 调整。

实战建议:缓存业务默认 allkeys-lru;有明确热点(如商品详情、榜单)用 allkeys-lfu;不允许淘汰的核心数据用 noeviction 并配合监控预警。

内存碎片率监控:

redis-cli info memory | grep mem_fragmentation
# mem_fragmentation_ratio: 1.4

mem_fragmentation_ratio = 已用物理内存 / Redis 实际数据内存。经验判断:

  • 1.0 ~ 1.5:健康区间,正常。
  • > 1.5:碎片较多,常见于大量删除/过期操作后,可执行 redis-cli memory purge 或重启(低峰期);长期偏高可能是内存分配器不匹配(jemalloc vs glibc)。
  • < 1.0:表示发生了 swap(内存交换到磁盘),极其危险,说明物理内存不足,应立刻扩容或清理。

七、性能调优:Pipeline、Lua、慢查询、bigkey

Redis 单线程模型意味着:一条慢命令就会阻塞所有后续命令。优化核心就是”少发命令、避免阻塞命令、消灭大 key”。

1. Pipeline 批量

网络 RTT 是 Redis 性能的最大杀手。一次请求一次往返,批量操作用 Pipeline 把多条命令一次性发到服务端,吞吐可提升数倍甚至一个数量级:

# Python 示例:Pipeline 批量写入 1000 个键
import redis
r = redis.Redis(host='10.0.0.10', port=6379)
pipe = r.pipeline(transaction=False)
for i in range(1000):
    pipe.set(f'user:{i}', f'value{i}')
pipe.execute()   # 一次性发送,而非1000次RTT

注意 Pipeline 不是原子操作,只是批量发送;需要原子性时用 Lua 脚本。

2. Lua 脚本原子操作

Redis 执行 Lua 脚本是原子的(整个脚本作为一条命令执行),适合需要”读-改-写”多步且要求原子性的场景,如分布式限流、秒杀扣库存:

-- 原子扣减库存,库存不足返回 -1
local stock = tonumber(redis.call('GET', KEYS[1]) or 0)
if stock < tonumber(ARGV[1]) then
    return -1
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return stock - ARGV[1]
# 执行
redis-cli EVAL "$(cat deduct.lua)" 1 stock:1001 1

脚本中禁止用 KEYS 遍历(会阻塞),且脚本要尽量短——脚本执行期间整个 Redis 是阻塞的。

3. 慢查询日志

# 慢查询配置:执行超过 10ms 的命令记入日志
slowlog-log-slower-than 10000   # 单位微秒
slowlog-max-len 128

# 查看最近慢查询
redis-cli slowlog get 10

生产上把阈值设在 10ms(10000 微秒),定期巡检 slowlog,重点排查:大 key 的 KEYS/DEL/SMEMBERS、批量 SORT、复杂 Lua、阻塞的 BLPOP。

4. bigkey 检测

大 key(如千万成员的 hash、几 MB 的 string)会让 DELGET 等命令阻塞,必须提前发现。Redis 内置的 --bigkeys 扫描:

redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys...
# Biggest string found so far 'token:10086' has 5242880 bytes
# Biggest hash   found so far 'user_tag'   has 1000000 fields

它遍历整个键空间统计各类型最大的 key,用于定位。针对已知大 key 的处理:

  • 避免 DEL 大 key:用 UNLINK key 异步删除(不阻塞主线程)。
  • 压缩/拆分:大 hash 拆成多个小 hash(如按用户 ID 分桶),或改用 string 存 JSON。
  • TTL 兜底:给缓存 key 都设过期时间,防止无限增长。

八、监控告警:Redis Exporter + Prometheus + Grafana

生产环境必须有可观测性,否则故障发生全靠玄学。业界标准组合是 Prometheus + Grafana + redis_exporter。

1. 部署 redis_exporter 暴露指标:

# 用 systemd 或 docker 运行 exporter,抓取 Redis 指标
docker run -d --name redis-exporter -p 9121:9121 \
  -e REDIS_ADDR=redis://10.0.0.10:6379 \
  oliver006/redis_exporter:latest
# 验证指标
curl http://10.0.0.10:9121/metrics | head

2. Prometheus 配置抓取:

# prometheus.yml
scrape_configs:
  - job_name: 'redis'
    static_configs:
      - targets: ['10.0.0.10:9121', '10.0.0.11:9121', '10.0.0.12:9121']

3. 关键指标(Grafana 面板重点监控):

指标 含义 告警阈值建议
redis_connected_clients 连接数 超过 maxclients 的 80% 告警
redis_memory_used_bytes 已用内存 > maxmemory 的 80% 告警
redis_mem_fragmentation_ratio 内存碎片率 > 1.5 或 < 1.0 告警
redis_rdb_bgsave_in_progress / aof_rewrite_in_progress 持久化阻塞 持续 > 60s 告警
redis_uptime_in_seconds 运行时长 骤降=重启,立即告警
redis_latest_fork_usec 最近 fork 耗时 > 1000ms 告警(可能阻塞)
redis_keyspace_hits / redis_keyspace_misses 命中率 命中率 < 90% 关注(缓存可能失效)

4. 告警规则示例(alertmanager):

# rules.yml
groups:
  - name: redis-alerts
    rules:
      - alert: RedisMemoryHigh
        expr: redis_memory_used_bytes / (redis_config_maxmemory * 1024 * 1024) > 0.8
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "Redis 内存使用超过 80%"

      - alert: RedisDown
        expr: up{job="redis"} == 0
        for: 1m
        labels: { severity: critical }
        annotations:
          summary: "Redis 实例 {{ $labels.instance }} 不可用"

      - alert: RedisFragmentationHigh
        expr: redis_mem_fragmentation_ratio > 1.5
        for: 10m
        labels: { severity: warning }
        annotations:
          summary: "Redis 内存碎片率过高,建议 memory purge"

Grafana 直接用现成的 redis-dashboard(ID: 763)导入,即可获得完整可视化面板。

九、生产部署 Checklist:安全加固与容灾

把 Redis 裸奔在内网甚至公网,等于把数据送人。以下清单在生产环境逐项核对:

1. 安全加固

# 只监听内网地址,禁止 0.0.0.0
bind 10.0.0.10 127.0.0.1
protected-mode yes

# 设置访问密码(集群/哨兵各节点要一致)
requirepass YourStrongPassword!
masterauth YourStrongPassword!   # 从节点连主时也要用

# 禁用危险命令,防误删和渗透
rename-command FLUSHALL ""
rename-command FLUSHDB  ""
rename-command CONFIG   ""
rename-command KEYS     ""

# 限制最大连接,防资源耗尽
maxclients 10000

额外提醒:protected-mode yes 配合 bind 是底线;生产绝不能用默认密码或无密码直连;若必须对外,请置于 VPN/防火墙之后,而非暴露公网。

2. 备份策略

#!/bin/bash
# backup_redis.sh:每日 RDB 备份 + 保留最近7天
BACKUP_DIR=/data/redis_backup
mkdir -p $BACKUP_DIR
# 触发 RDB 快照(或直接拷贝最新 dump.rdb)
redis-cli -a $REDIS_PASS BGSAVE
sleep 2
cp /data/redis/dump.rdb $BACKUP_DIR/redis_$(date +%F).rdb
# 只保留最近 7 天
find $BACKUP_DIR -name "redis_*.rdb" -mtime +7 -delete
# 可选:同步到异地/对象存储
# scp $BACKUP_DIR/redis_$(date +%F).rdb backup-server:/backup/

恢复:停止 Redis,把备份的 dump.rdb 放到数据目录,重启即可。若开了 AOF,重启时以 AOF 为准(更新)。

3. 容灾方案

  • 同机房高可用:哨兵或 Cluster 的主从分布在不同物理机/机柜,避免单机宕机拖垮全部。
  • 跨机房灾备:异地从节点(replicaof 指向异地主节点),或定期把 RDB 备份同步到异机房,满足 RPO 要求。
  • 多级缓存兜底:Redis 之上再加本地缓存(如 Caffeine),Redis 挂了应用仍能抗住短时读请求,避免雪崩打到数据库。
  • 故障演练:定期手动 kill 主节点,验证哨兵/Cluster 能否在预定时间内完成切换,别等真出事才发现脚本有问题。

结语:方案对比总表

最后用一张表收束全文。选型没有绝对最好,只有”当前阶段最合适”。把复杂度留给需要它的阶段,是运维最大的智慧。

方案 复杂度 可用性 扩展性 适用规模 典型用途
单机 ★☆☆☆☆ 低(宕机即挂) < 20GB / 5万 QPS 开发、小流量缓存
主从 + 读写分离 ★★☆☆☆ 中(手动切换) 读可扩展 20~80GB / 20万 QPS 读多写少的业务
哨兵 (Sentinel) ★★★☆☆ 高(自动切换,99.99%) 读可扩展,写仍单点 80~150GB / 50万 QPS 关键业务高可用
Cluster 分片 ★★★★★ 高(自动故障转移) 读写均可水平扩展 > 150GB / 百万级 QPS 海量数据、多机房、高并发写

希望这份从单机到 Cluster 的实战手册,能帮你把 Redis 的高可用、可扩展、可观测三条路一次性走通。生产环境多一分演练,上线就少一分惊魂。