Redis 高可用架构对比:主从 vs 哨兵 vs Cluster
Redis 提供多种高可用架构方案:主从复制、哨兵模式、Cluster 集群。本文将深入对比这三种方案,帮助你选择最适合的架构。
一、架构概述
1.1 主从复制
┌─────────────┐
│ Master │ 写操作
│ 192.168.1.1 │
│ port 6379 │
└──────┬──────┘
│ 复制
┌───┴───┐
▼ ▼
┌─────┐ ┌─────┐
│Slave│ │Slave│ 读操作
│192.168│ │192.168│
│.1.2 │ │.1.3 │
└─────┘ └─────┘
特点:
1.2 哨兵模式
┌─────────────┐
│ Master │
│ 192.168.1.1 │
└──────┬──────┘
│
┌───┴───┐
▼ ▼
┌─────┐ ┌─────┐
│Slave│ │Slave│
└─────┘ └─────┘
▲ ▲
│ │
┌──┴──┬────┴──┬──┐
│ S1 │ S2 │ S3│ 哨兵集群
└─────┘ └─────┘ └───┘
特点:
1.3 Cluster 集群
┌─────────┐ ┌─────────┐ ┌─────────┐
│Master 1 │ │Master 2 │ │Master 3 │
│0-5460 │ │5461-10922│ │10923-16383│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Slave 1 │ │ Slave 2 │ │ Slave 3 │
└─────────┘ └─────────┘ └─────────┘
特点:
二、功能对比
2.1 基本功能
| 功能 | 主从 | 哨兵 | Cluster |
|---|
| 数据冗余 | ✅ | ✅ | ✅ |
| 读写分离 | ✅ | ✅ | ✅ |
| 自动故障转移 | ❌ | ✅ | ✅ |
| 自动分片 | ❌ | ❌ | ✅ |
| 水平扩展 | ❌ | ❌ | ✅ |
| 去中心化 | ❌ | ✅ | ✅ |
2.2 高可用能力
| 能力 | 主从 | 哨兵 | Cluster |
|---|
| 故障检测 | 手动 | 自动 | 自动 |
| 故障转移 | 手动 | 自动 | 自动 |
| 恢复时间 | 分钟级 | 秒级 | 秒级 |
| 数据丢失 | 可能 | 秒级 | 秒级 |
| 可用性 | 99% | 99.9% | 99.99% |
2.3 扩展能力
| 能力 | 主从 | 哨兵 | Cluster |
|---|
| 读扩展 | ✅ | ✅ | ✅ |
| 写扩展 | ❌ | ❌ | ✅ |
| 存储扩展 | ❌ | ❌ | ✅ |
| 最大节点数 | 无限制 | 无限制 | 1000 |
| 最大数据量 | 单机限制 | 单机限制 | 多机 |
三、Java 实现对比
3.1 主从模式
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class MasterSlaveExample {
private static JedisPool masterPool;
private static JedisPool slavePool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
// 主节点(写)
masterPool = new JedisPool(config, "192.168.1.1", 6379);
// 从节点(读)
slavePool = new JedisPool(config, "192.168.1.2", 6379);
}
public static void main(String[] args) {
// 写操作(主节点)
try (Jedis master = masterPool.getResource()) {
master.set("user:1001:name", "Alice");
}
// 读操作(从节点)
try (Jedis slave = slavePool.getResource()) {
String name = slave.get("user:1001:name");
System.out.println("Name: " + name);
}
}
}
3.2 哨兵模式
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
public class SentinelExample {
private static JedisSentinelPool pool;
static {
// 哨兵节点
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.2.1:26379");
sentinels.add("192.168.2.2:26379");
sentinels.add("192.168.2.3:26379");
// 连接池配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
// 创建哨兵连接池
pool = new JedisSentinelPool(
"mymaster",
sentinels,
config,
"your_password"
);
}
public static void main(String[] args) {
// 自动连接主节点(写)
try (Jedis jedis = pool.getResource()) {
jedis.set("user:1001:name", "Alice");
}
// 自动连接从节点(读)
try (Jedis jedis = pool.getResource()) {
String name = jedis.get("user:1001:name");
System.out.println("Name: " + name);
}
}
}
3.3 Cluster 模式
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
public class ClusterExample {
private static JedisCluster cluster;
static {
// 集群节点
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.1", 7000));
nodes.add(new HostAndPort("192.168.1.2", 7001));
nodes.add(new HostAndPort("192.168.1.3", 7002));
// 连接池配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
// 创建集群连接
cluster = new JedisCluster(nodes, config);
}
public static void main(String[] args) {
// 自动路由到正确节点
cluster.set("user:1001:name", "Alice");
String name = cluster.get("user:1001:name");
System.out.println("Name: " + name);
}
}
四、Golang 实现对比
4.1 主从模式
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
ctx := context.Background()
// 主节点(写)
master := redis.NewClient(&redis.Options{
Addr: "192.168.1.1:6379",
Password: "your_password",
})
// 从节点(读)
slave := redis.NewClient(&redis.Options{
Addr: "192.168.1.2:6379",
Password: "your_password",
})
// 写操作
master.Set(ctx, "user:1001:name", "Alice", 0)
// 读操作
name, _ := slave.Get(ctx, "user:1001:name").Result()
fmt.Println("Name:", name)
}
4.2 哨兵模式
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
ctx := context.Background()
// 哨兵客户端
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "mymaster",
SentinelAddrs: []string{
"192.168.2.1:26379",
"192.168.2.2:26379",
"192.168.2.3:26379",
},
Password: "your_password",
})
// 自动路由
rdb.Set(ctx, "user:1001:name", "Alice", 0)
name, _ := rdb.Get(ctx, "user:1001:name").Result()
fmt.Println("Name:", name)
}
4.3 Cluster 模式
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
ctx := context.Background()
// 集群客户端
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"192.168.1.1:7000",
"192.168.1.2:7001",
"192.168.1.3:7002",
},
Password: "your_password",
})
// 自动路由
rdb.Set(ctx, "user:1001:name", "Alice", 0)
name, _ := rdb.Get(ctx, "user:1001:name").Result()
fmt.Println("Name:", name)
}
五、性能对比
5.1 基准测试
# 主从模式
redis-benchmark -h 192.168.1.1 -p 6379 -q
# SET: 100000.00 requests per second
# GET: 100000.00 requests per second
# 哨兵模式
# 性能与主从相当,故障转移时有短暂影响
# Cluster 模式
redis-benchmark -h 192.168.1.1 -p 7000 -q
# SET: 80000.00 requests per second
# GET: 80000.00 requests per second
# 略低,因为有路由开销
5.2 延迟对比
| 操作 | 主从 | 哨兵 | Cluster |
|---|
| GET | ~1ms | ~1ms | ~1.5ms |
| SET | ~1ms | ~1ms | ~1.5ms |
| 故障转移 | 手动 | ~5s | ~5s |
| 跨节点 | N/A | N/A | ~2ms |
5.3 吞吐量对比
| 场景 | 主从 | 哨兵 | Cluster |
|---|
| 单节点 | 10w QPS | 10w QPS | 10w QPS |
| 3 节点读 | 30w QPS | 30w QPS | 30w QPS |
| 3 节点写 | 10w QPS | 10w QPS | 30w QPS |
六、成本对比
6.1 硬件成本
| 架构 | 最少节点 | 推荐节点 | 硬件成本 |
|---|
| 主从 | 2 | 3 | 低 |
| 哨兵 | 5 | 6 | 中 |
| Cluster | 6 | 9 | 高 |
6.2 运维成本
| 架构 | 部署难度 | 运维难度 | 故障恢复 |
|---|
| 主从 | 低 | 低 | 手动 |
| 哨兵 | 中 | 中 | 自动 |
| Cluster | 高 | 高 | 自动 |
6.3 云成本
| 服务商 | 主从 | 哨兵 | Cluster |
|---|
| 阿里云 | ¥500/月 | ¥1000/月 | ¥2000/月 |
| AWS | $100/月 | $200/月 | $400/月 |
| 自建 | ¥200/月 | ¥500/月 | ¥1000/月 |
七、选型指南
7.1 选择主从的场景
✅ 推荐:
- 数据量小(< 单机容量)
- 读多写少
- 预算有限
- 运维能力弱
❌ 不推荐:
- 需要自动故障转移
- 写操作频繁
- 数据量大
7.2 选择哨兵的场景
✅ 推荐:
- 需要高可用
- 数据量中等
- 自动故障转移
- 读写分离
❌ 不推荐:
- 需要水平扩展
- 数据量超大
- 写操作频繁
7.3 选择 Cluster 的场景
✅ 推荐:
- 数据量大(> 单机容量)
- 需要水平扩展
- 高并发写入
- 大规模部署
❌ 不推荐:
- 数据量小
- 预算有限
- 运维能力弱
八、架构演进
8.1 从小到大的演进路径
阶段 1:单机 Redis
┌─────────┐
│ Redis │
└─────────┘
阶段 2:主从复制
┌─────────┐
│ Master │
└────┬────┘
│
┌────┴────┐
│ Slave │
└─────────┘
阶段 3:哨兵模式
┌─────────┐
│ Master │
└────┬────┘ ┌─────┐
│ │ S1 │
┌────┴────┐ ├─────┤
│ Slave │ │ S2 │
└─────────┘ ├─────┤
│ S3 │
└─────┘
阶段 4:Cluster 集群
┌─────┐ ┌─────┐ ┌─────┐
│ M1 │ │ M2 │ │ M3 │
└──┬──┘ └──┬──┘ └──┬──┘
│ │ │
┌──┴──┐ ┌──┴──┐ ┌──┴──┐
│ S1 │ │ S2 │ │ S3 │
└─────┘ └─────┘ └─────┘
8.2 迁移方案
主从迁移到哨兵
# 1. 部署哨兵节点
# 2. 配置哨兵监控
# 3. 验证故障转移
# 4. 切换客户端配置
哨兵迁移到 Cluster
# 1. 部署 Cluster 集群
# 2. 使用 redis-shake 迁移数据
# 3. 验证数据完整性
# 4. 切换客户端配置
# 5. 下线哨兵集群
九、最佳实践
9.1 主从最佳实践
✅ 推荐:
- 至少 1 主 2 从
- 配置合理的超时时间
- 定期备份
- 监控主从延迟
❌ 避免:
- 主从同机
- 无监控
- 无备份
9.2 哨兵最佳实践
✅ 推荐:
- 至少 3 个哨兵
- quorum = 2
- 哨兵与 Redis 分离部署
- 配置告警通知
❌ 避免:
- 哨兵数量过少
- quorum 设置过高
- 哨兵与主节点同机
9.3 Cluster 最佳实践
✅ 推荐:
- 至少 3 主 3 从
- 节点跨机房部署
- 配置合理的超时时间
- 定期备份和演练
❌ 避免:
- 单机房部署
- 超时时间过短
- 无备份
十、故障处理
10.1 主从故障
# 1. 主节点故障
# 手动提升从节点为主
redis-cli -p 6379 SLAVEOF NO ONE
# 2. 更新应用配置
# 3. 重新配置从节点
redis-cli -p 6379 SLAVEOF new_master_ip 6379
10.2 哨兵故障
# 1. 哨兵节点故障
# 自动故障转移
# 2. 检查新主节点
redis-cli -p 26379 SENTINEL MASTER mymaster
# 3. 恢复故障哨兵
redis-server /etc/redis/sentinel.conf
10.3 Cluster 故障
# 1. 主节点故障
# 自动故障转移
# 2. 检查集群状态
redis-cli --cluster check 192.168.1.1:7000
# 3. 如果需要,手动修复
redis-cli --cluster fix 192.168.1.1:7000
总结
Redis 高可用架构对比总结:
| 架构 | 优势 | 劣势 | 适用场景 |
|---|
| 主从 | 简单、成本低 | 无自动故障转移 | 小规模、读多写少 |
| 哨兵 | 自动故障转移、中等成本 | 无法水平扩展 | 中等规模、高可用 |
| Cluster | 水平扩展、高可用 | 复杂、成本高 | 大规模、高并发 |
选型建议:
- 小规模 → 主从
- 中等规模 → 哨兵
- 大规模 → Cluster
- 读多写少 → 主从/哨兵
- 写多 → Cluster
- 预算有限 → 主从
- 高可用要求 → 哨兵/Cluster
根据业务规模和需求选择合适的架构!
参考资料