Redis 高可用集群方案实战指南
Redis 架构演进之路
单机模式的局限性
单机 Redis 虽然简单易用,但在生产环境中面临诸多挑战:
- 单点故障:服务器宕机后服务完全不可用
- 内存瓶颈:单机内存上限制约数据容量
- 性能瓶颈:单线程模型下 QPS 存在天花板
从单机到哨兵再到集群
Redis 架构经历了三个阶段的演进:
| 阶段 | 架构 | 高可用 | 水平扩展 | 适用场景 |
|---|---|---|---|---|
| 1 | Standalone | ❌ | ❌ | 开发/测试 |
| 2 | Sentinel | ✅ | ❌ | 中小规模生产 |
| 3 | Cluster | ✅ | ✅ | 大规模生产 |
Redis Sentinel 哨兵模式
哨兵架构原理
Redis Sentinel 是 Redis 官方提供的高可用方案,由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器及其从服务器:
# sentinel.conf — 哨兵配置示例
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel auth-pass mymaster your_strong_password
故障转移机制
Sentinel 故障转移的完整流程:
- 主观下线(SDOWN):单个 Sentinel 认为主节点不可用
- 客观下线(ODOWN):超过 quorum 数量的 Sentinel 认为主节点不可用
- 选举 Leader Sentinel:通过 Raft 算法选出执行故障转移的 Sentinel
- 选举新主节点:按照优先级 → 复制偏移量 → Run ID 排序选举
- 执行故障转移:将从节点提升为主节点,其他从节点指向新主
# 启动 Sentinel 集群(3 个实例)
redis-sentinel /etc/redis/sentinel-26379.conf
redis-sentinel /etc/redis/sentinel-26380.conf
redis-sentinel /etc/redis/sentinel-26381.conf
# 查看主节点状态
redis-cli -p 26379 sentinel master mymaster
# 查看从节点列表
redis-cli -p 26379 sentinel slaves mymaster
Sentinel 部署最佳实践
- 至少部署 3 个 Sentinel 节点实现多数派
- Sentinel 节点应部署在不同物理机上
down-after-milliseconds不宜设置过小,避免网络抖动误判- 客户端必须实现 Sentinel 感知,自动获取新主节点地址
Redis Cluster 集群模式
Hash Slot 哈希槽原理
Redis Cluster 将数据划分为 16384 个哈希槽,每个主节点负责一部分槽:
slot = CRC16(key) % 16384
集群节点分配示例:
| 节点 | 槽范围 | 槽数量 |
|---|---|---|
| Node A | 0 ~ 5460 | 5461 |
| Node B | 5461 ~ 10922 | 5462 |
| Node C | 10923 ~ 16383 | 5462 |
集群配置与部署
# redis.conf — 集群节点配置
port 6379
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.1.101
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
requirepass your_strong_password
masterauth your_strong_password
逐步部署集群
# 步骤 1:启动 6 个 Redis 实例(3 主 3 从)
for port in 6379 6380 6381 6382 6383 6384; do
redis-server /etc/redis/redis-${port}.conf
done
# 步骤 2:创建集群
redis-cli --cluster create \
192.168.1.101:6379 192.168.1.102:6380 192.168.1.103:6381 \
192.168.1.101:6382 192.168.1.102:6383 192.168.1.103:6384 \
--cluster-replicas 1 -a your_strong_password
# 步骤 3:验证集群状态
redis-cli -c -p 6379 cluster info
redis-cli -c -p 6379 cluster nodes
# 步骤 4:检查槽分配
redis-cli -c -p 6379 cluster slots
数据迁移与 Resharding
在线 Resharding
Redis Cluster 支持在线重新分片,无需停机:
# 将 1000 个槽从 Node A 迁移到 Node C
redis-cli --cluster reshard 192.168.1.101:6379 \
--cluster-from <node-a-id> \
--cluster-to <node-c-id> \
--cluster-slots 1000 \
-a your_strong_password
使用 Hash Tag 控制数据分布
当需要将相关 Key 分配到同一节点时,使用 Hash Tag:
# 花括号内的内容决定槽分配
SET user:{1000}:profile "profile_data"
SET user:{1000}:orders "orders_data"
# 两个 Key 会被分配到同一个槽
批量迁移注意事项
- 迁移期间目标节点会进入导入状态(importing)
- 源节点会进入迁移状态(migrating)
- 客户端访问迁移中的 Key 会收到 ASK 重定向
- 建议在低峰期执行大规模 resharding
常用数据结构优化
String vs Hash 存储对象
存储用户对象时,Hash 结构通常优于 String:
# 方式 1:String + JSON(简单但内存开销大)
SET user:1000 '{"name":"张三","age":30,"city":"北京"}'
# 方式 2:Hash(节省内存,支持部分读写)
HSET user:1000 name "张三" age 30 city "北京"
HGET user:1000 name
# => "张三"
内存对比(存储 100 万个用户对象,每个 5 个字段):
| 存储方式 | 内存占用 | 部分更新 | 过期控制 |
|---|---|---|---|
| String + JSON | ~320MB | ❌ 需要全量 | ✅ 整体过期 |
| Hash | ~160MB | ✅ 单字段更新 | ❌ 不能单字段过期 |
使用 ziplist 优化小集合
# Redis 7.0+ 使用 listpack 替代 ziplist
hash-max-listpack-entries 512
hash-max-listpack-value 64
zset-max-listpack-entries 128
zset-max-listpack-value 64
缓存策略与模式
Cache-Aside 旁路缓存
最常用的缓存模式,读和写分离处理:
# Cache-Aside 模式
def get_user(user_id):
# 1. 先查缓存
data = redis.get(f"user:{user_id}")
if data:
return json.loads(data)
# 2. 缓存未命中,查数据库
data = db.query("SELECT * FROM users WHERE id = %s", user_id)
if data:
# 3. 写入缓存,设置过期时间
redis.setex(f"user:{user_id}", 3600, json.dumps(data))
return data
def update_user(user_id, data):
# 1. 更新数据库
db.update("UPDATE users SET ... WHERE id = %s", user_id)
# 2. 删除缓存(而非更新缓存)
redis.delete(f"user:{user_id}")
Write-Through 写穿透
所有写操作先经过缓存层,由缓存层同步写入数据库:
# Write-Through 模式
def write_through(key, value):
# 缓存层负责同步写入数据库
redis.set(key, value)
db.sync_write(key, value) # 同步写库
Write-Behind 异步回写
写操作只更新缓存,由后台异步批量写入数据库:
# Write-Behind 模式(异步回写)
def write_behind(key, value):
redis.set(key, value)
# 标记为脏数据,等待异步刷盘
dirty_key_queue.append(key)
async def flush_to_db():
while True:
keys = batch_get_dirty_keys(100)
for key in keys:
value = redis.get(key)
db.async_write(key, value)
await asyncio.sleep(1)
缓存三大问题及解决方案
缓存穿透
查询不存在的数据,请求直达数据库:
# 方案 1:布隆过滤器
def get_with_bloom(key):
if not bloom_filter.might_contain(key):
return None # 一定不存在
return cache_aside_get(key)
# 方案 2:缓存空值
def get_with_null_cache(key):
data = redis.get(key)
if data == "NULL":
return None # 空值缓存命中
if data:
return data
data = db.query(key)
if not data:
redis.setex(key, 60, "NULL") # 短时间缓存空值
return data
缓存击穿
热点 Key 过期瞬间大量请求穿透到数据库:
# 方案:互斥锁 + 逻辑过期
def get_with_mutex(key):
data = redis.get(key)
if data:
return data
# 获取互斥锁
lock_key = f"lock:{key}"
if redis.set(lock_key, 1, nx=True, ex=5):
try:
data = db.query(key)
redis.setex(key, 3600, data)
return data
finally:
redis.delete(lock_key)
else:
time.sleep(0.1)
return get_with_mutex(key) # 重试
缓存雪崩
大量 Key 同时过期,导致数据库压力骤增:
# 方案:过期时间加随机偏移
import random
def set_with_jitter(key, value, base_ttl=3600):
jitter = random.randint(0, 300) # 0~5 分钟随机偏移
redis.setex(key, base_ttl + jitter, value)
内存优化技巧
关键配置项
# 内存优化相关配置
maxmemory 8gb
maxmemory-policy allkeys-lru
# 开启 lazy-free 异步删除
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
# 共享整数对象池(0-9999 默认共享)
# 超出范围的整数不再共享
内存淘汰策略选择
| 策略 | 说明 | 适用场景 |
|---|---|---|
| noeviction | 不淘汰,写入报错 | 数据不能丢失 |
| allkeys-lru | 所有 Key LRU | 通用缓存 |
| volatile-lru | 有 TTL 的 Key LRU | 混合使用场景 |
| allkeys-lfu | 所有 Key LFU | 热点数据明显 |
| volatile-ttl | 淘汰 TTL 最短的 | 业务有明确优先级 |
持久化策略
RDB vs AOF vs 混合持久化
# RDB 快照配置
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
# AOF 追加配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# Redis 4.0+ 混合持久化
aof-use-rdb-preamble yes
| 特性 | RDB | AOF | 混合 |
|---|---|---|---|
| 文件体积 | 小 | 大 | 中 |
| 恢复速度 | 快 | 慢 | 较快 |
| 数据安全 | 可能丢数据 | 最多丢 1 秒 | 最多丢 1 秒 |
| 性能影响 | fork 时影响 | 写入时有影响 | 折中 |
监控与运维
使用 Redis Insight 监控
# 安装 Redis Insight
docker run -d --name redis-insight \
-p 8001:8001 \
redis/redisinsight:latest
# 通过 CLI 获取关键指标
redis-cli info memory | grep used_memory_human
redis-cli info stats | grep instantaneous_ops_per_sec
redis-cli info replication | grep connected_slaves
关键监控指标
- 内存使用率:
used_memory / maxmemory> 80% 需要关注 - 命中率:
keyspace_hits / (keyspace_hits + keyspace_misses) - 连接数:
connected_clients接近maxclients时告警 - 慢查询:
SLOWLOG GET 10获取最近慢查询 - 主从延迟:
master_repl_offset - slave_repl_offset
常见错误排查
CLUSTERDOWN 错误
# 错误信息
# (error) CLUSTERDOWN The cluster is not available
# 排查步骤
redis-cli -p 6379 cluster info
# cluster_state:fail 表示有槽未覆盖
# 修复:检查所有节点状态
redis-cli --cluster fix 192.168.1.101:6379 -a your_strong_password
MOVED 与 ASK 重定向
# MOVED:槽已永久迁移到新节点
# (error) MOVED 3999 192.168.1.103:6381
# ASK:槽正在迁移中(临时重定向)
# (error) ASK 3999 192.168.1.103:6381
# 解决:客户端需实现智能重定向
redis-cli -c -p 6379 # -c 参数启用集群模式自动跟随重定向
常见连接错误
# NOAUTH Authentication required
redis-cli -a your_strong_password -p 6379
# CLUSTERDOWN Hash slot not served
redis-cli --cluster check 192.168.1.101:6379
# BUSY Redis is busy running a script
CONFIG SET lua-time-limit 5000 # 调整 Lua 脚本超时
生产环境 Checklist
部署前检查
- 至少 3 主 3 从,分布在不同物理机/可用区
- 开启
appendonly yes和aof-use-rdb-preamble yes - 设置合理的
maxmemory和淘汰策略 - 配置
requirepass和masterauth - 调整系统
vm.overcommit_memory=1 - 禁用 THP:
echo never > /sys/kernel/mm/transparent_hugepage/enabled - 设置合理的文件描述符限制:
ulimit -n 65535 - 客户端实现连接池和重试机制
- 监控告警配置就绪
运维规范
- 禁止使用
KEYS *等阻塞命令 - Key 设置合理 TTL,避免永久缓存
- 大 Value(>10KB)考虑压缩或拆分
- 批量操作使用 Pipeline
- 集群模式下注意 Key 的 Hash Tag 使用
常见问题 FAQ
Q: Sentinel 和 Cluster 该选哪个? A: 数据量不大(< 单机内存)且只需高可用选 Sentinel;需要水平扩展选 Cluster。两者不要混用。
Q: Cluster 中可以执行 MGET 等多 Key 操作吗?
A: 只有当所有 Key 属于同一 Hash Slot 时才可以。使用 Hash Tag {prefix} 确保相关 Key 在同一槽。
Q: 集群最大支持多少节点? A: 官方推荐最多 1000 个主节点。实际生产中建议控制在几十个主节点以内。
Q: RDB 和 AOF 该用哪个?
A: 生产环境推荐使用混合持久化(aof-use-rdb-preamble yes),兼顾恢复速度和数据安全。
Q: 如何估算集群所需内存? A: 总内存 = 单节点数据量 × 主节点数 × 1.5(预留 50% 给缓冲和开销)。建议单节点数据量不超过可用内存的 70%。
更多 Redis 工具和在线编解码,请访问 工具库 JSON 格式化、哈希计算、Base64 编解码。
本站提供浏览器本地工具,免注册即可试用 →