主从复制概述
Redis 主从复制是一种数据冗余和读写分离的解决方案。通过将数据从主服务器(Master)复制到一个或多个从服务器(Slave),实现数据备份、读写分离和负载均衡。
主从复制架构
🔴 Master
主服务器
读写操作
→
🟢 Slave 1
从服务器
只读操作
→
🟢 Slave 2
从服务器
只读操作
特点:一主多从、异步复制、读写分离
🔧 复制配置
配置 Redis 主从复制的方法:
主服务器配置:
# redis-master.conf # 绑定IP地址 bind 0.0.0.0 port 6379 # 设置密码(可选) requirepass masterpassword # 持久化配置 save 900 1 save 300 10 save 60 10000 appendonly yes # 复制相关配置 # 设置复制积压缓冲区大小 repl-backlog-size 1mb # 设置复制积压缓冲区TTL repl-backlog-ttl 3600 # 慢查询日志 slowlog-log-slower-than 10000 slowlog-max-len 128
从服务器配置:
# redis-slave.conf # 绑定IP地址 bind 0.0.0.0 port 6380 # 指定主服务器 slaveof 192.168.1.100 6379 # 或使用新的配置项 replicaof 192.168.1.100 6379 # 主服务器密码 masterauth masterpassword # 从服务器只读 slave-read-only yes # 或使用新的配置项 replica-read-only yes # 从服务器密码(可选) requirepass slavepassword # 复制相关配置 # 复制超时时间 repl-timeout 60 # 禁用TCP_NODELAY repl-disable-tcp-nodelay no # 当主从连接断开时的行为 slave-serve-stale-data yes replica-serve-stale-data yes
动态配置复制:
# 在从服务器上执行 # 设置主服务器 SLAVEOF 192.168.1.100 6379 # 或使用新命令 REPLICAOF 192.168.1.100 6379 # 取消复制(变为独立服务器) SLAVEOF NO ONE REPLICAOF NO ONE # 查看复制信息 INFO replication # 查看主从延迟 INFO replication | grep lag
🔄 复制原理
Redis 主从复制的工作流程:
1. 连接
从服务器连接主服务器
→
2. 握手
发送PING命令验证连接
→
3. 认证
发送AUTH命令认证
→
4. 同步
全量同步或增量同步
→
5. 复制
持续接收命令流
全量同步(Full Resynchronization):
# 全量同步流程 1. 从服务器发送 PSYNC ? -1 命令 2. 主服务器执行 BGSAVE 生成 RDB 文件 3. 主服务器将 RDB 文件发送给从服务器 4. 从服务器清空数据库并载入 RDB 文件 5. 主服务器将复制积压缓冲区的命令发送给从服务器 # 触发全量同步的情况: - 首次连接 - 复制ID不匹配 - 偏移量不在积压缓冲区范围内
增量同步(Partial Resynchronization):
# 增量同步流程 1. 从服务器发送 PSYNC命令 2. 主服务器检查复制ID和偏移量 3. 如果偏移量在积压缓冲区范围内,发送 +CONTINUE 4. 主服务器发送积压缓冲区中的命令 # 增量同步的优势: - 减少网络传输 - 降低主服务器负载 - 缩短同步时间
📊 复制监控
监控主从复制状态的重要指标:
| 监控指标 | 命令 | 说明 |
|---|---|---|
| 复制状态 | INFO replication |
查看主从复制详细信息 |
| 复制延迟 | INFO replication | grep lag |
查看主从延迟时间 |
| 连接状态 | INFO replication | grep state |
查看连接状态 |
| 复制偏移量 | INFO replication | grep offset |
查看复制进度 |
| 从服务器数量 | INFO replication | grep slaves |
查看从服务器数量 |
复制状态监控示例:
# 主服务器信息 redis-cli INFO replication # role:master # connected_slaves:2 # slave0:ip=192.168.1.101,port=6379,state=online,offset=1234567,lag=0 # slave1:ip=192.168.1.102,port=6379,state=online,offset=1234567,lag=1 # master_repl_offset:1234567 # repl_backlog_active:1 # repl_backlog_size:1048576 # 从服务器信息 redis-cli -p 6380 INFO replication # role:slave # master_host:192.168.1.100 # master_port:6379 # master_link_status:up # master_last_io_seconds_ago:0 # master_sync_in_progress:0 # slave_repl_offset:1234567 # slave_priority:100 # slave_read_only:1
复制监控脚本:
#!/bin/bash
# Redis 主从复制监控脚本
MASTER_HOST="192.168.1.100"
MASTER_PORT="6379"
SLAVE_HOSTS=("192.168.1.101" "192.168.1.102")
SLAVE_PORT="6379"
LOG_FILE="/var/log/redis-replication.log"
# 检查主服务器状态
master_info=$(redis-cli -h $MASTER_HOST -p $MASTER_PORT INFO replication)
connected_slaves=$(echo "$master_info" | grep "connected_slaves" | cut -d: -f2)
echo "$(date): Master connected slaves: $connected_slaves" >> $LOG_FILE
# 检查从服务器状态
for slave_host in "${SLAVE_HOSTS[@]}"; do
slave_info=$(redis-cli -h $slave_host -p $SLAVE_PORT INFO replication)
link_status=$(echo "$slave_info" | grep "master_link_status" | cut -d: -f2)
lag=$(echo "$slave_info" | grep "master_last_io_seconds_ago" | cut -d: -f2)
echo "$(date): Slave $slave_host - Link: $link_status, Lag: ${lag}s" >> $LOG_FILE
# 告警检查
if [ "$link_status" != "up" ]; then
echo "ALERT: Slave $slave_host is disconnected!" >> $LOG_FILE
fi
if [ "$lag" -gt 10 ]; then
echo "ALERT: Slave $slave_host lag is too high: ${lag}s" >> $LOG_FILE
fi
done
⚡ 读写分离
利用主从复制实现读写分离,提升系统性能:
应用层读写分离:
# Python 示例
import redis
class RedisCluster:
def __init__(self):
# 主服务器(写操作)
self.master = redis.Redis(
host='192.168.1.100',
port=6379,
password='masterpassword',
decode_responses=True
)
# 从服务器(读操作)
self.slaves = [
redis.Redis(
host='192.168.1.101',
port=6379,
password='slavepassword',
decode_responses=True
),
redis.Redis(
host='192.168.1.102',
port=6379,
password='slavepassword',
decode_responses=True
)
]
self.slave_index = 0
def write(self, key, value):
"""写操作,使用主服务器"""
return self.master.set(key, value)
def read(self, key):
"""读操作,使用从服务器(负载均衡)"""
slave = self.slaves[self.slave_index]
self.slave_index = (self.slave_index + 1) % len(self.slaves)
return slave.get(key)
def read_with_fallback(self, key):
"""读操作,带故障转移"""
for slave in self.slaves:
try:
return slave.get(key)
except redis.ConnectionError:
continue
# 所有从服务器都不可用,使用主服务器
return self.master.get(key)
# 使用示例
cluster = RedisCluster()
cluster.write('user:1001', 'Alice') # 写入主服务器
value = cluster.read('user:1001') # 从从服务器读取
Java 示例(使用 Jedis):
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisCluster {
private JedisPool masterPool;
private JedisPool[] slavePools;
private int slaveIndex = 0;
public RedisCluster() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(20);
// 主服务器连接池
masterPool = new JedisPool(config, "192.168.1.100", 6379, 2000, "masterpassword");
// 从服务器连接池
slavePools = new JedisPool[]{
new JedisPool(config, "192.168.1.101", 6379, 2000, "slavepassword"),
new JedisPool(config, "192.168.1.102", 6379, 2000, "slavepassword")
};
}
public String write(String key, String value) {
try (Jedis jedis = masterPool.getResource()) {
return jedis.set(key, value);
}
}
public String read(String key) {
JedisPool slavePool = slavePools[slaveIndex];
slaveIndex = (slaveIndex + 1) % slavePools.length;
try (Jedis jedis = slavePool.getResource()) {
return jedis.get(key);
} catch (Exception e) {
// 从服务器异常,使用主服务器
try (Jedis jedis = masterPool.getResource()) {
return jedis.get(key);
}
}
}
}
主从复制优缺点
| 方面 | 优点 | 缺点 |
|---|---|---|
| 可用性 | 数据备份,故障转移 | 主服务器单点故障 |
| 性能 | 读写分离,负载分担 | 复制延迟 |
| 扩展性 | 水平扩展读能力 | 写能力无法扩展 |
| 一致性 | 最终一致性 | 可能读到旧数据 |
| 复杂性 | 配置简单 | 需要应用层支持 |
🚨 故障处理
主从复制环境中的故障处理策略:
主服务器故障:
⚠️ 主服务器故障处理:
- 检测故障:监控主服务器状态
- 选择新主:从从服务器中选择一个作为新主
- 提升为主:执行
SLAVEOF NO ONE - 重新配置:其他从服务器指向新主
- 应用切换:修改应用配置指向新主
故障转移脚本:
#!/bin/bash
# Redis 主服务器故障转移脚本
MASTER_HOST="192.168.1.100"
MASTER_PORT="6379"
NEW_MASTER_HOST="192.168.1.101"
NEW_MASTER_PORT="6379"
SLAVE_HOSTS=("192.168.1.102")
# 检查主服务器状态
if ! redis-cli -h $MASTER_HOST -p $MASTER_PORT ping > /dev/null 2>&1; then
echo "Master server is down, starting failover..."
# 提升从服务器为主服务器
redis-cli -h $NEW_MASTER_HOST -p $NEW_MASTER_PORT SLAVEOF NO ONE
echo "Promoted $NEW_MASTER_HOST to master"
# 重新配置其他从服务器
for slave_host in "${SLAVE_HOSTS[@]}"; do
redis-cli -h $slave_host -p $MASTER_PORT SLAVEOF $NEW_MASTER_HOST $NEW_MASTER_PORT
echo "Reconfigured $slave_host to follow new master"
done
echo "Failover completed"
else
echo "Master server is healthy"
fi
从服务器故障:
💡 从服务器故障处理:
- 自动检测:应用层检测从服务器连接状态
- 流量切换:将读请求转发到其他从服务器
- 故障恢复:修复从服务器后自动重新加入
- 数据同步:从服务器重启后自动同步数据
网络分区处理:
# 配置最小从服务器数量 # 当连接的从服务器少于指定数量时,主服务器停止写入 min-slaves-to-write 1 min-slaves-max-lag 10 # 这样可以避免网络分区时的数据不一致问题
🔧 性能优化
主从复制的性能优化建议:
网络优化:
- 禁用 TCP_NODELAY:设置
repl-disable-tcp-nodelay no - 调整缓冲区:增大
repl-backlog-size - 网络带宽:确保主从之间有足够的网络带宽
- 就近部署:主从服务器部署在同一数据中心
内存优化:
- 复制积压缓冲区:根据网络延迟调整大小
- 输出缓冲区:调整
client-output-buffer-limit - 内存策略:合理设置
maxmemory-policy
磁盘优化:
- RDB 优化:调整 RDB 生成频率
- AOF 优化:选择合适的 AOF 同步策略
- SSD 存储:使用 SSD 提升 I/O 性能
性能优化配置:
# 主服务器优化配置 # 复制积压缓冲区(根据网络延迟调整) repl-backlog-size 10mb repl-backlog-ttl 3600 # 输出缓冲区限制 client-output-buffer-limit slave 256mb 64mb 60 # 禁用慢查询对复制的影响 repl-disable-tcp-nodelay no # 从服务器优化配置 # 复制超时时间 repl-timeout 60 # 当主从连接断开时继续提供服务 slave-serve-stale-data yes # 从服务器优先级(用于故障转移) slave-priority 100
💡 最佳实践:
- 监控复制延迟:设置告警阈值,及时发现问题
- 定期备份:除了复制,还要定期备份 RDB 文件
- 读写分离:在应用层实现读写分离逻辑
- 故障转移:准备自动化的故障转移方案
- 容量规划:根据业务增长预估从服务器数量
- 安全配置:设置密码,限制网络访问