主从复制概述
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 文件
- 读写分离:在应用层实现读写分离逻辑
- 故障转移:准备自动化的故障转移方案
- 容量规划:根据业务增长预估从服务器数量
- 安全配置:设置密码,限制网络访问