从 0 到 1 搭建高可用 Redis Cluster:踩坑、优化与生产实践

Redis Cluster 是 Redis 的分布式解决方案,但"能用"和"好用"之间隔着一百个坑。这篇文章基于我们团队维护的 200+ 节点 Redis Cluster 集群的实战经验,讲清楚从搭建到优化的完整路径。


一、Cluster 架构的核心概念

Redis Cluster 采用无中心架构,16384 个哈希槽(slot)均匀分布在各节点上。每个 key 通过 CRC16(key) % 16384 计算归属槽,再路由到对应节点。

plain

css 复制代码
节点 A:槽 0-5460
节点 B:槽 5461-10922
节点 C:槽 10923-16383

无中心意味着没有单点瓶颈,但也意味着客户端必须"集群感知"------能缓存槽映射、能处理 MOVED/ASK 重定向。


二、搭建:别用 redis-trib.rb 了

老教程让你用 redis-trib.rb 创建集群,但 Redis 5.0+ 已经内置 redis-cli --cluster

bash

lua 复制代码
# 创建 3 主 3 从的集群
redis-cli --cluster create \
  192.168.1.10:6379 \
  192.168.1.11:6379 \
  192.168.1.12:6379 \
  192.168.1.13:6379 \
  192.168.1.14:6379 \
  192.168.1.15:6379 \
  --cluster-replicas 1

--cluster-replicas 1 表示每个主节点配一个从节点。Redis 会自动分配槽和主从关系。

但生产环境别这么干。上面的命令把主从节点放在同一台物理机上,物理机挂了,主从一起死,高可用形同虚设。

正确的拓扑:跨机架部署

plain

css 复制代码
机架 1:主 A,从 B
机架 2:主 B,从 C
机架 3:主 C,从 A

每个主节点的从节点放在不同机架,机架级故障不会导致数据丢失。


三、客户端配置:集群感知是关键

Jedis 和 Lettuce 都支持 Cluster 模式,但默认配置在生产环境会出问题。

Jedis Cluster 的正确打开方式:

java

javascript 复制代码
Set<HostAndPort> nodes = Set.of(
    new HostAndPort("192.168.1.10", 6379),
    new HostAndPort("192.168.1.11", 6379),
    new HostAndPort("192.168.1.12", 6379)
);

JedisCluster jedis = new JedisCluster(nodes,
    2000,   // connectionTimeout
    2000,   // soTimeout
    3,      // maxAttempts
    "password",
    new JedisPoolConfig() {{
        setMaxTotal(100);
        setMaxIdle(50);
        setMinIdle(10);
    }}
);

关键参数:

  • maxAttempts=3:遇到 MOVED/ASK 重试 3 次,避免瞬时故障导致请求失败
  • connectionTimeout=2000:连接超时 2 秒,太长会阻塞业务线程
  • soTimeout=2000:读取超时 2 秒,防止慢查询拖垮客户端

Lettuce 的集群模式更智能:

java

less 复制代码
RedisClusterClient client = RedisClusterClient.create(
    RedisURI.builder()
        .withHost("192.168.1.10")
        .withPort(6379)
        .withPassword("password")
        .build()
);

StatefulRedisClusterConnection<String, String> connection = client.connect();
connection.setReadFrom(ReadFrom.REPLICA_PREFERRED); // 优先读从节点

ReadFrom.REPLICA_PREFERRED 让读请求优先路由到从节点,分担主节点压力。但注意:从节点的数据有延迟,对一致性要求高的读操作(比如读后立即写)必须走主节点。


四、热点 Key 的识别与拆分

Redis Cluster 最大的性能杀手不是节点宕机,是热点 Key。

假设你有一个 product:10086:stock 的库存 key,秒杀时每秒 10 万次访问,这个 key 所在的槽和节点会被打爆,其他槽空闲,集群利用率严重不均。

识别热点 Key:

bash

perl 复制代码
# Redis 4.0+ 支持 hotkeys 参数
redis-cli --hotkeys

# 或者通过 INFO 命令查看
redis-cli INFO stats | grep keyspace

拆分策略:虚拟 key + 本地缓存

java

ini 复制代码
// 把热点 key 拆成 10 个虚拟 key
String virtualKey = "product:10086:stock:" + (userId % 10);
int stock = jedis.get(virtualKey);

// 应用层做本地缓存(Caffeine)
LoadingCache<String, Integer> localCache = Caffeine.newBuilder()
    .expireAfterWrite(100, TimeUnit.MILLISECONDS)
    .build(key -> jedis.get(key));

虚拟 key 把流量分散到 10 个槽,本地缓存进一步减少对 Redis 的访问。但本地缓存的过期时间必须短(100ms 以内),否则数据不一致。


五、大 Key 的治理

另一个隐形杀手是大 Key。一个 Hash 里有 100 万个 field,或者一个 String 存了 10MB 的 JSON,都会导致:

  • 序列化/反序列化耗时
  • 网络传输阻塞
  • 主从复制时 RDB 生成卡顿

扫描大 Key:

bash

css 复制代码
redis-cli --bigkeys

或者自定义脚本扫描:

bash

bash 复制代码
redis-cli --eval scan_bigkeys.lua

治理方案:拆分 + 压缩

java

sql 复制代码
// 大 Hash 拆成多个小 Hash
// 原:HSET user:10086 profile "{...10MB JSON...}"
// 拆:
HSET user:10086:profile:basic name "张三" age 25
HSET user:10086:profile:extra tags "vip,premium"

String 类型的大 value 用 Snappy 或 LZ4 压缩:

java

ini 复制代码
byte[] compressed = Snappy.compress(jsonString.getBytes());
jedis.set("user:10086:profile", compressed);

读取时先解压再反序列化。压缩率通常在 60%-80%,网络传输和内存占用大幅降低。


六、持久化与容灾的权衡

Redis Cluster 的持久化有两种:RDB 和 AOF。

RDB: 定时快照,文件紧凑,恢复速度快,但会丢数据(两次快照之间的数据丢失)。

AOF: 记录每条写命令,数据安全性高,但文件大、恢复慢。

生产环境的推荐配置:

conf

bash 复制代码
# 同时开启 RDB 和 AOF
save 900 1
save 300 10
save 60 10000

appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes  # Redis 4.0+ 混合持久化

aof-use-rdb-preamble yes 开启混合持久化:AOF 文件前半部分是 RDB 快照,后半部分是增量 AOF 命令。恢复时先加载 RDB 快速恢复大部分数据,再重放 AOF 补全,兼顾速度和安全性。

跨机房容灾:

主从复制只能防单节点故障,防不了机房级灾难。我们的做法是多活架构:

plain

css 复制代码
北京机房:主 A,主 B,主 C
上海机房:从 A,从 B,从 C

写操作走北京,读操作可以走上海。两个机房之间通过专线同步,延迟控制在 5ms 以内。

但跨机房复制有带宽成本,且网络抖动时从节点会被误判为故障。需要调整:

conf

bash 复制代码
cluster-node-timeout 30000  # 默认 15 秒,跨机房调到 30 秒

七、监控与告警

没有监控的 Redis 集群是定时炸弹。我们监控的核心指标:

表格

指标 阈值 说明
used_memory / maxmemory > 85% 内存告警,考虑扩容或淘汰策略
instantaneous_ops_per_sec > 100000 单节点 QPS 过高,检查热点
rejected_connections > 0 连接数超限,检查客户端池配置
keyspace_hits / keyspace_misses 命中率 < 80% 缓存失效严重,检查 key 设计和过期策略
slowlog 命令耗时 > 10ms 慢查询告警,优化命令或拆分大 key

Prometheus + Grafana 是标配,配合 Redis Exporter 采集指标。


八、总结

Redis Cluster 不是搭起来就能跑的生产系统。从拓扑设计、客户端配置、热点治理、大 key 拆分、持久化策略到跨机房容灾,每个环节都有坑。

几个核心原则:

  • 主从跨机架部署,别放同一台物理机
  • 客户端必须集群感知,配置重试和超时
  • 热点 key 拆虚拟 key + 本地缓存
  • 大 key 必须拆分或压缩
  • 混合持久化(RDB + AOF)是生产标配
  • 监控比调优更重要,先监控再优化

Redis 很快,但快的前提是"用对"。希望这些实战经验能帮你少踩几个坑。

相关推荐
掘金安东尼2 小时前
Agent Loop 深度调研:把决定权交给模型的一次换代,为什么发生在现在
前端
亿元程序员2 小时前
Cocos视频拼图,终于支持微信小游戏了!
前端
JarvanMo2 小时前
Flutter 的默认颜色
前端
IT_陈寒2 小时前
Vite打包时踩的坑:静态资源为啥突然404了?
前端·人工智能·后端
神奇的程序员12 小时前
我的软件冲进苹果商店下载榜前 50 了
前端
阳光是sunny12 小时前
别再被 worktree 绕晕了!AI 编程时代你必须掌握的 Git 隔离神器
前端·人工智能·后端
万少13 小时前
万少的博客 - 技术分享与解决方案
前端·javascript·后端
尘世中一位迷途小书童16 小时前
用 Cesium 撸了一个森林火情监控大屏,弧线、粒子、发光效果都齐了
前端·javascript
IT_陈寒16 小时前
垃圾回收器选错了,我的Java服务内存炸了
前端·人工智能·后端