Redis高可用
Redis高可用架构模式详解
目录
主从模式
1. 原理概述
主从模式是Redis最基础的高可用方案,通过数据复制实现读写分离和数据备份。
┌─────────────┐ 复制 ┌─────────────┐
│ Master │─────────→ │ Slave 1 │
│ (读写) │ │ (只读) │
└─────────────┘ └─────────────┘
│
│ 复制
▼
┌─────────────┐
│ Slave 2 │
│ (只读) │
└─────────────┘
2. 复制过程详解
2.1 全量复制 (Full Resynchronization)
时序图:
Master Slave
│ │
│◄─────── PSYNC ? -1 ──────────│ 1. Slave发送同步请求
│ │
│────── +FULLRESYNC ──────────►│ 2. Master响应全量同步
│ runid offset │
│ │
│────────── RDB ──────────────►│ 3. Master发送RDB快照
│ │
│─────── 增量数据 ─────────────►│ 4. 发送复制期间的新数据
│ │
2.2 增量复制 (Partial Resynchronization)
# Redis 2.8+ 支持增量复制机制
# 基于复制偏移量和复制积压缓冲区
# Master维护:
replication_backlog = CircularBuffer(size=1MB) # 复制积压缓冲区
master_repl_offset = 12345 # 主复制偏移量
# Slave维护:
slave_repl_offset = 12340 # 从复制偏移量
master_runid = "abc123..." # 主服务器运行ID
3. 配置实现
3.1 Master配置 (redis-master.conf)
# 基本配置
port 6379
bind 0.0.0.0
protected-mode no
# 持久化配置
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
# 复制配置
# 设置从服务器密码验证
requirepass "master_password"
# 主服务器密码(如果主服务器也需要认证)
masterauth "master_password"
# 复制积压缓冲区大小
repl-backlog-size 1mb
# 复制积压缓冲区超时时间
repl-backlog-ttl 3600
# 网络配置
tcp-keepalive 300
timeout 0
3.2 Slave配置 (redis-slave.conf)
# 基本配置
port 6380
bind 0.0.0.0
protected-mode no
# 从服务器配置
slaveof 192.168.1.100 6379
# 主服务器密码
masterauth "master_password"
# 从服务器密码
requirepass "slave_password"
# 从服务器只读
slave-read-only yes
# 复制相关配置
# 当主从连接断开时,从服务器是否继续提供服务
slave-serve-stale-data yes
# 复制超时时间
repl-timeout 60
# 复制期间禁用TCP_NODELAY
repl-disable-tcp-nodelay no
# 从服务器优先级(用于故障转移)
slave-priority 100
4. 命令操作
# 启动Master
redis-server redis-master.conf
# 启动Slave
redis-server redis-slave.conf
# 动态设置主从关系
redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 192.168.1.100 6379
OK
# 查看复制信息
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.1.101,port=6380,state=online,offset=12345,lag=0
slave1:ip=192.168.1.102,port=6380,state=online,offset=12345,lag=0
# 取消主从关系
127.0.0.1:6380> SLAVEOF NO ONE
OK
5. 主从模式的限制
- 故障转移需要人工干预
- 写操作单点瓶颈
- 主节点故障时服务不可用
哨兵模式
1. 原理概述
哨兵模式在主从模式基础上增加了自动故障转移功能,通过多个哨兵节点监控Redis集群状态。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Sentinel 1 │ │ Sentinel 2 │ │ Sentinel 3 │
│ (监控) │ │ (监控) │ │ (监控) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└───────────────────┼───────────────────┘
│ 监控和故障转移
▼
┌─────────────┐ 复制 ┌─────────────┐
│ Master │─────────→│ Slave 1 │
│ (读写) │ │ (只读) │
└─────────────┘ └─────────────┘
│
│ 复制
▼
┌─────────────┐
│ Slave 2 │
│ (只读) │
└─────────────┘
2. 哨兵工作机制
2.1 监控 (Monitoring)
# 哨兵定期向Master和Slave发送PING命令
def monitor_redis_instances():
for instance in redis_instances:
try:
response = instance.ping()
if response != "PONG":
mark_as_down(instance)
except Exception:
mark_as_down(instance)
2.2 故障检测和判断
# 主观下线 (Subjectively Down, SDOWN)
def check_sdown(instance):
if instance.last_ping_time + down_after_milliseconds < current_time():
instance.flags |= SRI_S_DOWN
return True
return False
# 客观下线 (Objectively Down, ODOWN)
def check_odown(master):
sdown_count = 0
for sentinel in sentinels:
if sentinel.is_master_down(master):
sdown_count += 1
if sdown_count >= quorum:
master.flags |= SRI_O_DOWN
return True
return False
2.3 故障转移流程
故障转移步骤:
1. 检测Master客观下线
2. 选举Leader Sentinel
3. 选择新的Master
4. 执行故障转移
5. 更新配置并通知
时序图:
Sentinel1 Sentinel2 Sentinel3 Master Slave1 Slave2
│ │ │ │ │ │
│─────── PING ──────────────────►│ │ │
│ │ │ X │ │
│◄─── timeout ──────────────────│ │ │
│ │ │ │ │
│───── is-master-down-by-addr ──►│ │ │
│◄───── +odown ─────────────────│ │ │
│ │ │ │ │
│──── Leader选举 ──────────────►│ │ │
│◄─── Leader确认 ──────────────│ │ │
│ │ │ │ │
│─────── SLAVEOF NO ONE ─────────────────►│ │
│◄────── OK ────────────────────────────│ │
│ │ │ │ │
│─────── SLAVEOF new_master ─────────────────────►│
│◄────── OK ──────────────────────────────────────│
3. 配置实现
3.1 哨兵配置 (sentinel.conf)
# 哨兵端口
port 26379
# 绑定地址
bind 0.0.0.0
# 工作目录
dir /var/lib/redis
# 监控Master配置
# sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 192.168.1.100 6379 2
# 认证配置
sentinel auth-pass mymaster master_password
# 故障转移配置
# 判断Master下线的时间(毫秒)
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时时间
sentinel failover-timeout mymaster 15000
# 同时从新Master同步数据的Slave数量
sentinel parallel-syncs mymaster 1
# 日志配置
logfile "/var/log/redis/sentinel.log"
loglevel notice
# 通知脚本(可选)
sentinel notification-script mymaster /scripts/notify.sh
sentinel client-reconfig-script mymaster /scripts/reconfig.sh
3.2 启动哨兵集群
# 启动3个哨兵节点
redis-sentinel /etc/redis/sentinel-26379.conf
redis-sentinel /etc/redis/sentinel-26380.conf
redis-sentinel /etc/redis/sentinel-26381.conf
# 或使用systemd
systemctl start redis-sentinel@26379
systemctl start redis-sentinel@26380
systemctl start redis-sentinel@26381
4. 客户端连接
4.1 Java客户端 (Jedis)
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
public class RedisSentinelClient {
private JedisSentinelPool sentinelPool;
public void initSentinelPool() {
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.1.100:26379");
sentinels.add("192.168.1.101:26379");
sentinels.add("192.168.1.102:26379");
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(20);
poolConfig.setMaxIdle(10);
poolConfig.setMinIdle(5);
sentinelPool = new JedisSentinelPool(
"mymaster", // Master名称
sentinels, // 哨兵地址集合
poolConfig, // 连接池配置
"master_password" // Redis密码
);
}
public void useRedis() {
try (Jedis jedis = sentinelPool.getResource()) {
jedis.set("key", "value");
String value = jedis.get("key");
System.out.println("Value: " + value);
}
}
}
4.2 Python客户端 (redis-py)
import redis.sentinel
# 配置哨兵
sentinels = [
('192.168.1.100', 26379),
('192.168.1.101', 26379),
('192.168.1.102', 26379)
]
# 创建哨兵实例
sentinel = redis.sentinel.Sentinel(
sentinels,
socket_timeout=0.1,
password='master_password'
)
# 获取Master连接
master = sentinel.master_for('mymaster', socket_timeout=0.1)
# 获取Slave连接(读操作)
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
# 使用示例
master.set('key', 'value')
value = slave.get('key')
print(f'Value: {value}')
5. 哨兵命令
# 连接哨兵
redis-cli -p 26379
# 查看Master信息
127.0.0.1:26379> SENTINEL masters
# 查看Slave信息
127.0.0.1:26379> SENTINEL slaves mymaster
# 查看哨兵信息
127.0.0.1:26379> SENTINEL sentinels mymaster
# 手动故障转移
127.0.0.1:26379> SENTINEL failover mymaster
# 重置Master
127.0.0.1:26379> SENTINEL reset mymaster
集群模式
1. 原理概述
Redis Cluster是Redis官方的分布式解决方案,支持数据分片和自动故障转移。
集群拓扑结构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ (Master) │ │ (Master) │ │ (Master) │
│ Slots: │ │ Slots: │ │ Slots: │
│ 0-5460 │ │ 5461-10922 │ │ 10923-16383 │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Node 4 │ │ Node 5 │ │ Node 6 │
│ (Slave of 1)│ │ (Slave of 2)│ │ (Slave of 3)│
└─────────────┘ └─────────────┘ └─────────────┘
2. 数据分片机制
2.1 哈希槽 (Hash Slots)
# Redis Cluster使用16384个哈希槽
CLUSTER_SLOTS = 16384
def calculate_slot(key):
"""计算key对应的槽位"""
# 检查是否有哈希标签 {tag}
start = key.find('{')
if start != -1:
end = key.find('}', start + 1)
if end != -1 and end != start + 1:
key = key[start + 1:end]
# 使用CRC16算法计算槽位
crc = crc16(key.encode('utf-8'))
return crc % CLUSTER_SLOTS
# 示例
print(calculate_slot("user:1000")) # 槽位: 8842
print(calculate_slot("user:{tag}:1000")) # 使用tag计算
print(calculate_slot("user:{tag}:2000")) # 相同tag,相同槽位
2.2 槽位分配
# 集群槽位分配示例
Node 192.168.1.100:7000 - Slots: 0-5460 (5461 slots)
Node 192.168.1.101:7000 - Slots: 5461-10922 (5462 slots)
Node 192.168.1.102:7000 - Slots: 10923-16383 (5461 slots)
3. 配置实现
3.1 集群节点配置 (redis-cluster.conf)
# 基本配置
port 7000
bind 0.0.0.0
protected-mode no
# 集群模式启用
cluster-enabled yes
# 集群配置文件(自动生成)
cluster-config-file nodes-7000.conf
# 节点超时时间
cluster-node-timeout 5000
# 集群require-full-coverage
cluster-require-full-coverage no
# 数据持久化
appendonly yes
appendfilename "appendonly-7000.aof"
# 内存配置
maxmemory 1gb
maxmemory-policy allkeys-lru
# 日志配置
logfile "/var/log/redis/redis-7000.log"
loglevel notice
# 密码配置(可选)
requirepass "cluster_password"
masterauth "cluster_password"
3.2 创建集群脚本
#!/bin/bash
# create-cluster.sh
# 创建6个节点的目录
for port in 7000 7001 7002 7003 7004 7005; do
mkdir -p /etc/redis/cluster/${port}
cp redis-cluster.conf /etc/redis/cluster/${port}/redis.conf
sed -i "s/port 7000/port ${port}/g" /etc/redis/cluster/${port}/redis.conf
sed -i "s/nodes-7000.conf/nodes-${port}.conf/g" /etc/redis/cluster/${port}/redis.conf
sed -i "s/appendonly-7000.aof/appendonly-${port}.aof/g" /etc/redis/cluster/${port}/redis.conf
sed -i "s/redis-7000.log/redis-${port}.log/g" /etc/redis/cluster/${port}/redis.conf
done
# 启动所有节点
for port in 7000 7001 7002 7003 7004 7005; do
redis-server /etc/redis/cluster/${port}/redis.conf &
done
sleep 5
# 创建集群(Redis 5.0+)
redis-cli --cluster create \
192.168.1.100:7000 \
192.168.1.100:7001 \
192.168.1.100:7002 \
192.168.1.100:7003 \
192.168.1.100:7004 \
192.168.1.100:7005 \
--cluster-replicas 1 \
--cluster-yes
echo "Cluster created successfully!"
3.3 多机部署配置
# 机器1 (192.168.1.100) - 运行节点 7000, 7003
# 机器2 (192.168.1.101) - 运行节点 7001, 7004
# 机器3 (192.168.1.102) - 运行节点 7002, 7005
# 创建集群
redis-cli --cluster create \
192.168.1.100:7000 \
192.168.1.101:7001 \
192.168.1.102:7002 \
192.168.1.100:7003 \
192.168.1.101:7004 \
192.168.1.102:7005 \
--cluster-replicas 1
4. 集群管理
4.1 集群信息查看
# 连接集群(任意节点)
redis-cli -c -h 192.168.1.100 -p 7000
# 查看集群信息
127.0.0.1:7000> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
# 查看集群节点
127.0.0.1:7000> CLUSTER NODES
a1b2c3d4... 192.168.1.100:7000 myself,master - 0 0 1 connected 0-5460
e5f6g7h8... 192.168.1.101:7001 master - 0 1623456789 2 connected 5461-10922
i9j0k1l2... 192.168.1.102:7002 master - 0 1623456789 3 connected 10923-16383
m3n4o5p6... 192.168.1.100:7003 slave e5f6g7h8... 0 1623456789 4 connected
q7r8s9t0... 192.168.1.101:7004 slave i9j0k1l2... 0 1623456789 5 connected
u1v2w3x4... 192.168.1.102:7005 slave a1b2c3d4... 0 1623456789 6 connected
# 查看槽位分配
127.0.0.1:7000> CLUSTER SLOTS
1) 1) (integer) 0
2) (integer) 5460
3) 1) "192.168.1.100"
2) (integer) 7000
3) "a1b2c3d4..."
4) 1) "192.168.1.102"
2) (integer) 7005
3) "u1v2w3x4..."
4.2 集群扩容
# 1. 添加新的Master节点
redis-cli --cluster add-node 192.168.1.103:7006 192.168.1.100:7000
# 2. 重新分配槽位
redis-cli --cluster reshard 192.168.1.100:7000
# 按提示输入要迁移的槽位数量和目标节点ID
# 3. 添加Slave节点
redis-cli --cluster add-node 192.168.1.103:7007 192.168.1.100:7000 --cluster-slave --cluster-master-id <master-node-id>
4.3 集群缩容
# 1. 迁移槽位到其他节点
redis-cli --cluster reshard 192.168.1.100:7000 \
--cluster-from <node-id> \
--cluster-to <target-node-id> \
--cluster-slots 1000
# 2. 删除空的Master节点
redis-cli --cluster del-node 192.168.1.103:7006 <node-id>
# 3. 删除Slave节点
redis-cli --cluster del-node 192.168.1.103:7007 <slave-node-id>
5. 客户端连接
5.1 Java客户端 (Jedis)
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
public class RedisClusterClient {
private JedisCluster jedisCluster;
public void initCluster() {
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.100", 7000));
nodes.add(new HostAndPort("192.168.1.101", 7001));
nodes.add(new HostAndPort("192.168.1.102", 7002));
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(20);
poolConfig.setMaxIdle(10);
poolConfig.setMinIdle(5);
jedisCluster = new JedisCluster(
nodes, // 集群节点
2000, // 连接超时
2000, // 读取超时
5, // 最大重定向次数
"cluster_password", // 密码
poolConfig // 连接池配置
);
}
public void useCluster() {
// 自动处理槽位路由和重定向
jedisCluster.set("user:1000", "John");
jedisCluster.set("user:1001", "Jane");
String user1 = jedisCluster.get("user:1000");
String user2 = jedisCluster.get("user:1001");
// 批量操作需要在同一槽位
Map<String, String> users = new HashMap<>();
users.put("user:{group1}:1000", "John");
users.put("user:{group1}:1001", "Jane");
jedisCluster.mset(users);
}
}
5.2 Python客户端 (redis-py-cluster)
from rediscluster import RedisCluster
# 配置集群节点
startup_nodes = [
{"host": "192.168.1.100", "port": "7000"},
{"host": "192.168.1.101", "port": "7001"},
{"host": "192.168.1.102", "port": "7002"}
]
# 创建集群连接
cluster = RedisCluster(
startup_nodes=startup_nodes,
decode_responses=True,
password="cluster_password",
skip_full_coverage_check=True,
max_connections=20
)
# 使用集群
cluster.set("user:1000", "John")
cluster.set("product:2000", "Laptop")
user = cluster.get("user:1000")
product = cluster.get("product:2000")
# 使用哈希标签确保相关数据在同一槽位
cluster.mset({
"order:{user1000}:detail": "order details",
"order:{user1000}:items": "order items",
"order:{user1000}:payment": "payment info"
})
6. 故障转移机制
6.1 自动故障检测
# 集群节点故障检测机制
def detect_node_failure():
for node in cluster_nodes:
if node.last_ping_time + NODE_TIMEOUT < current_time():
node.flags |= REDIS_NODE_PFAIL # 标记为可能失败
# 询问其他节点的意见
fail_reports = 0
for other_node in cluster_nodes:
if other_node.get_node_flag(node.id) & REDIS_NODE_PFAIL:
fail_reports += 1
# 如果大多数节点认为该节点失败
if fail_reports > len(cluster_nodes) // 2:
node.flags |= REDIS_NODE_FAIL
trigger_failover(node)
6.2 Slave提升为Master
# 故障转移过程
1. 检测Master下线
2. Slave节点开始选举
3. 选举出新Master
4. 更新集群配置
5. 重新分配连接
# 故障转移日志示例
[WARNING] Node 192.168.1.100:7000 is now possibly failing (PFAIL)
[WARNING] Node 192.168.1.100:7000 is now failing (FAIL)
[NOTICE] Start of election delayed for 500 milliseconds
[NOTICE] Failover election started by 192.168.1.102:7005
[NOTICE] Failover election won: new configuration epoch is 7
[NOTICE] configEpoch set to 7 after successful failover
[NOTICE] Setting secondary replication ID
三种模式对比
1. 功能对比表
| 特性 | 主从模式 | 哨兵模式 | 集群模式 |
|---|---|---|---|
| 数据分片 | ❌ | ❌ | ✅ |
| 自动故障转移 | ❌ | ✅ | ✅ |
| 读写分离 | ✅ | ✅ | ❌ (需手动实现) |
| 横向扩展 | ❌ | ❌ | ✅ |
| 配置复杂度 | 低 | 中 | 高 |
| 客户端支持 | 简单 | 中等 | 复杂 |
| 最小节点数 | 2 | 3+3 | 6 |
2. 性能对比
2.1 写性能
主从模式: 单Master写入,性能受限于单机
哨兵模式: 单Master写入,性能受限于单机
集群模式: 多Master并行写入,性能线性扩展
2.2 读性能
主从模式: Master+Slave读取,可配置读写分离
哨兵模式: Master+Slave读取,可配置读写分离
集群模式: 所有节点都可读取,但需要正确路由
3. 使用场景建议
3.1 主从模式适用场景
- 小型应用,数据量不大
- 读多写少的场景
- 对一致性要求高,可接受手动故障恢复
- 预算有限,硬件资源少
3.2 哨兵模式适用场景
- 中型应用,需要高可用
- 读写比例均衡
- 需要自动故障转移
- 数据量在单机可承受范围内
3.3 集群模式适用场景
- 大型应用,大数据量
- 需要水平扩展
- 写操作频繁
- 可以接受分布式系统的复杂性
4. 迁移路径
4.1 主从 → 哨兵模式
# 1. 停止应用写入
# 2. 部署哨兵节点
# 3. 修改应用连接配置
# 4. 重启应用
# 5. 测试故障转移
# 配置变更示例
# 原配置
redis_host = "192.168.1.100"
redis_port = 6379
# 新配置
sentinels = [
("192.168.1.100", 26379),
("192.168.1.101", 26379),
("192.168.1.102", 26379)
]
master_name = "mymaster"
4.2 哨兵 → 集群模式
# 1. 备份现有数据
redis-cli --rdb /backup/dump.rdb
# 2. 创建新集群
# 3. 导入数据到集群
redis-cli --cluster import 192.168.1.100:7000 --cluster-from 192.168.1.100:6379
# 4. 验证数据完整性
# 5. 切换应用连接
# 6. 下线旧环境
5. 监控和运维
5.1 关键监控指标
# 主从模式监控
- 主从延迟 (replication lag)
- 复制缓冲区使用率
- 主从连接状态
# 哨兵模式监控
- 哨兵节点状态
- 故障转移次数
- Master切换历史
# 集群模式监控
- 集群节点状态
- 槽位迁移状态
- 集群性能指标
- 数据分布均匀性
5.2 运维脚本示例
#!/bin/bash
# redis-health-check.sh
check_redis_cluster() {
echo "=== Redis Cluster Health Check ==="
# 检查集群状态
cluster_state=$(redis-cli -c -h 192.168.1.100 -p 7000 cluster info | grep "cluster_state" | cut -d: -f2)
echo "Cluster State: $cluster_state"
# 检查节点状态
echo "=== Node Status ==="
redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes | while read line; do
node_id=$(echo $line | awk '{print $1}')
node_addr=$(echo $line | awk '{print $2}')
node_flags=$(echo $line | awk '{print $3}')
echo "Node: $node_addr, Flags: $node_flags"
done
# 检查槽位分配
echo "=== Slot Distribution ==="
redis-cli -c -h 192.168.1.100 -p 7000 cluster slots | grep -E "^\d+" | wc -l
echo "Active slot ranges: $(redis-cli -c -h 192.168.1.100 -p 7000 cluster slots | grep -E "^\d+" | wc -l)"
}
check_redis_cluster
总结
Redis的三种高可用架构各有特点:
- 主从模式:简单易用,适合小型应用
- 哨兵模式:自动故障转移,适合中型应用
- 集群模式:分布式架构,适合大型应用
选择合适的架构需要考虑:
- 数据量大小
- 并发要求
- 可用性要求
- 运维复杂度
- 硬件成本
建议根据业务发展阶段逐步演进:单机 → 主从 → 哨兵 → 集群
本文来自博客园,作者:MadLongTom,转载请注明原文链接:https://www.cnblogs.com/madtom/p/19044665
浙公网安备 33010602011771号