第7集Redis主从架构原理及搭建实操指南
|字数总计:2.9k|阅读时长:13分钟|阅读量:
引言
Redis主从架构是Redis高可用性方案的基础,通过主节点(Master)和从节点(Slave)的协作,实现数据的复制和读写分离。本文将深入探讨Redis主从架构的原理,并提供详细的搭建实操指南。
Redis主从架构概述
什么是主从架构
Redis主从架构是一种数据复制方案,其中:
- 主节点(Master):负责处理写操作,并将数据同步到从节点
- 从节点(Slave):负责处理读操作,接收主节点的数据同步
主从架构的优势
- 读写分离:主节点处理写操作,从节点处理读操作,提升系统性能
- 数据冗余:多个从节点提供数据备份,提高数据安全性
- 负载分担:读请求分散到多个从节点,减轻主节点压力
- 故障恢复:主节点故障时,可以快速切换到从节点
主从复制原理
复制流程
Redis主从复制分为以下几个阶段:
1. 建立连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void replicationConnectMaster(void) { int fd;
fd = anetTcpConnect(server.neterr, server.masterhost, server.masterport, NET_FIRST_BIND_ADDR); if (fd == -1) { serverLog(LL_WARNING,"Unable to connect to MASTER: %s", server.neterr); return; }
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE, syncWithMaster,NULL) == AE_ERR) { close(fd); serverLog(LL_WARNING,"Can't create readable event for SYNC"); return; }
server.repl_transfer_fd = fd; server.repl_transfer_lastio = server.unixtime; server.repl_state = REPL_STATE_CONNECTING; serverLog(LL_NOTICE,"Connecting to MASTER %s:%d", server.masterhost, server.masterport); }
|
2. 发送PING命令
1 2 3 4 5 6 7 8 9 10 11 12 13
| void replicationSendPing(void) { if (server.masterhost == NULL) return;
if (write(server.repl_transfer_fd, "PING\r\n", 6) != 6) { serverLog(LL_WARNING,"Error writing PING to master"); return; }
server.repl_transfer_lastio = server.unixtime; server.repl_state = REPL_STATE_RECEIVE_PONG; }
|
3. 身份验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void replicationSendAuth(void) { if (server.masterauth == NULL) return;
if (write(server.repl_transfer_fd, "AUTH %s\r\n", server.masterauth) != (int)(6 + strlen(server.masterauth))) { serverLog(LL_WARNING,"Error writing AUTH to master"); return; }
server.repl_transfer_lastio = server.unixtime; server.repl_state = REPL_STATE_RECEIVE_AUTH; }
|
4. 发送端口信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void replicationSendPort(void) { char port[32]; int len;
len = snprintf(port, sizeof(port), "REPLCONF listening-port %d\r\n", server.port); if (write(server.repl_transfer_fd, port, len) != len) { serverLog(LL_WARNING,"Error writing REPLCONF listening-port to master"); return; }
server.repl_transfer_lastio = server.unixtime; server.repl_state = REPL_STATE_RECEIVE_PORT; }
|
5. 同步数据
1 2 3 4 5 6 7 8 9 10
| void replicationSendSync(void) { if (write(server.repl_transfer_fd, "SYNC\r\n", 6) != 6) { serverLog(LL_WARNING,"Error writing SYNC to master"); return; }
server.repl_transfer_lastio = server.unixtime; server.repl_state = REPL_STATE_RECEIVE_PSYNC; }
|
复制机制详解
全量复制(RDB)
全量复制是Redis主从复制的初始阶段,主节点将当前数据快照发送给从节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { listIter li; listNode *ln; client *slave;
if (server.masterhost != NULL) return;
listRewind(slaves, &li); while((ln = listNext(&li))) { slave = ln->value;
if (slave->replstate != SLAVE_STATE_WAIT_BGSAVE_START) continue;
if (rdbSaveBackground(server.rdb_filename) != C_OK) { continue; }
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_END; } }
|
增量复制(AOF)
增量复制通过AOF(Append Only File)实现,主节点将写操作命令发送给从节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { listIter li; listNode *ln; client *slave;
if (server.masterhost != NULL) return;
listRewind(slaves, &li); while((ln = listNext(&li))) { slave = ln->value;
if (slave->replstate != SLAVE_STATE_ONLINE) continue;
addReplyMultiBulkLen(slave, argc); for (int j = 0; j < argc; j++) { addReplyBulk(slave, argv[j]); } } }
|
搭建实操指南
环境准备
1. 安装Redis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| sudo apt-get update sudo apt-get install redis-server
sudo yum install redis
sudo yum install epel-release sudo yum install redis
wget http://download.redis.io/releases/redis-6.2.6.tar.gz tar xzf redis-6.2.6.tar.gz cd redis-6.2.6 make sudo make install
|
2. 创建目录结构
1 2 3
| sudo mkdir -p /opt/redis/{master,slave1,slave2} sudo chown -R redis:redis /opt/redis
|
主节点配置
1. 配置文件(redis-master.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| port 6379 bind 0.0.0.0 daemonize yes pidfile /var/run/redis-master.pid logfile /var/log/redis-master.log dir /opt/redis/master
save 900 1 save 300 10 save 60 10000 rdbcompression yes rdbchecksum yes dbfilename dump.rdb
appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
maxmemory 2gb maxmemory-policy allkeys-lru
requirepass master123
|
2. 启动主节点
1 2 3 4 5
| redis-server /opt/redis/master/redis-master.conf
redis-cli -p 6379 -a master123 ping
|
从节点配置
1. 配置文件(redis-slave1.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| port 6380 bind 0.0.0.0 daemonize yes pidfile /var/run/redis-slave1.pid logfile /var/log/redis-slave1.log dir /opt/redis/slave1
replicaof 127.0.0.1 6379 masterauth master123 replica-read-only yes
save 900 1 save 300 10 save 60 10000 rdbcompression yes rdbchecksum yes dbfilename dump.rdb
appendonly yes appendfilename "appendonly.aof" appendfsync everysec
maxmemory 2gb maxmemory-policy allkeys-lru
|
2. 配置文件(redis-slave2.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| port 6381 bind 0.0.0.0 daemonize yes pidfile /var/run/redis-slave2.pid logfile /var/log/redis-slave2.log dir /opt/redis/slave2
replicaof 127.0.0.1 6379 masterauth master123 replica-read-only yes
save 900 1 save 300 10 save 60 10000 rdbcompression yes rdbchecksum yes dbfilename dump.rdb
appendonly yes appendfilename "appendonly.aof" appendfsync everysec
maxmemory 2gb maxmemory-policy allkeys-lru
|
3. 启动从节点
1 2 3 4 5 6 7 8 9
| redis-server /opt/redis/slave1/redis-slave1.conf
redis-server /opt/redis/slave2/redis-slave2.conf
redis-cli -p 6380 -a master123 ping redis-cli -p 6381 -a master123 ping
|
验证主从复制
1. 检查主从状态
1 2 3 4 5 6
| redis-cli -p 6379 -a master123 info replication
redis-cli -p 6380 -a master123 info replication redis-cli -p 6381 -a master123 info replication
|
2. 测试数据同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| redis-cli -p 6379 -a master123 > SET test_key "Hello Redis" > SET counter 100 > HSET user:1 name "Alice" age 25
redis-cli -p 6380 -a master123 > GET test_key > GET counter > HGETALL user:1
redis-cli -p 6381 -a master123 > GET test_key > GET counter > HGETALL user:1
|
高级配置
1. 复制积压缓冲区
1 2 3
| repl-backlog-size 1mb repl-backlog-ttl 3600
|
2. 复制超时设置
1 2 3
| repl-timeout 60 repl-ping-slave-period 10
|
3. 无盘复制
1 2 3
| repl-diskless-sync yes repl-diskless-sync-delay 5
|
故障处理
1. 主节点故障
手动切换
1 2 3 4 5 6 7 8 9 10
| redis-cli -p 6379 -a master123 shutdown
redis-cli -p 6380 -a master123 > REPLICAOF NO ONE
redis-cli -p 6381 -a master123 > REPLICAOF 127.0.0.1 6380
|
自动切换(使用Sentinel)
1 2 3 4 5 6 7
| port 26379 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel auth-pass mymaster master123 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000
|
2. 从节点故障
1 2 3 4 5
| redis-server /opt/redis/slave1/redis-slave1.conf
redis-cli -p 6380 -a master123 info replication
|
3. 网络分区
1 2 3 4 5
| ping 127.0.0.1
redis-cli -p 6379 -a master123 ping
|
性能优化
1. 读写分离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import redis
master = redis.Redis(host='127.0.0.1', port=6379, password='master123')
slaves = [ redis.Redis(host='127.0.0.1', port=6380, password='master123'), redis.Redis(host='127.0.0.1', port=6381, password='master123') ]
def write_data(key, value): """写操作使用主节点""" return master.set(key, value)
def read_data(key): """读操作使用从节点""" import random slave = random.choice(slaves) return slave.get(key)
|
2. 连接池配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from redis import ConnectionPool
master_pool = ConnectionPool( host='127.0.0.1', port=6379, password='master123', max_connections=20, retry_on_timeout=True )
slave_pool = ConnectionPool( host='127.0.0.1', port=6380, password='master123', max_connections=50, retry_on_timeout=True )
master = redis.Redis(connection_pool=master_pool) slave = redis.Redis(connection_pool=slave_pool)
|
3. 监控和告警
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
check_replication() { local port=$1 local password=$2
replication_info=$(redis-cli -p $port -a $password info replication) master_link_status=$(echo "$replication_info" | grep "master_link_status" | cut -d: -f2 | tr -d '\r')
if [ "$master_link_status" != "up" ]; then echo "ERROR: Master link status is down on port $port" return 1 fi
echo "OK: Master link status is up on port $port" return 0 }
check_replication 6379 master123 check_replication 6380 master123 check_replication 6381 master123
|
最佳实践
1. 配置建议
- 内存配置:主从节点内存配置应该一致
- 持久化:建议同时开启RDB和AOF
- 网络:确保主从节点网络延迟低
- 安全:设置密码认证,限制访问IP
2. 监控指标
- 复制延迟:监控主从复制延迟
- 连接状态:监控主从连接状态
- 内存使用:监控内存使用情况
- 命令执行:监控命令执行性能
3. 故障预防
- 定期备份:定期备份Redis数据
- 监控告警:设置监控告警机制
- 测试切换:定期测试主从切换
- 文档记录:记录配置和操作文档
实际应用案例
案例1:电商系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class EcommerceRedis: def __init__(self): self.master = redis.Redis( host='master.redis.com', port=6379, password='ecommerce123', decode_responses=True )
self.slaves = [ redis.Redis( host='slave1.redis.com', port=6379, password='ecommerce123', decode_responses=True ), redis.Redis( host='slave2.redis.com', port=6379, password='ecommerce123', decode_responses=True ) ]
def set_product(self, product_id, product_info): """设置商品信息(写操作)""" key = f"product:{product_id}" return self.master.hset(key, mapping=product_info)
def get_product(self, product_id): """获取商品信息(读操作)""" key = f"product:{product_id}" import random slave = random.choice(self.slaves) return slave.hgetall(key)
def update_inventory(self, product_id, quantity): """更新库存(写操作)""" key = f"inventory:{product_id}" return self.master.set(key, quantity)
def get_inventory(self, product_id): """获取库存(读操作)""" key = f"inventory:{product_id}" import random slave = random.choice(self.slaves) return slave.get(key)
|
案例2:用户会话管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class SessionManager: def __init__(self): self.master = redis.Redis( host='master.redis.com', port=6379, password='session123', decode_responses=True )
self.slaves = [ redis.Redis( host='slave1.redis.com', port=6379, password='session123', decode_responses=True ) ]
def create_session(self, user_id, session_data): """创建会话(写操作)""" key = f"session:{user_id}" self.master.hset(key, mapping=session_data) self.master.expire(key, 3600)
def get_session(self, user_id): """获取会话(读操作)""" key = f"session:{user_id}" slave = self.slaves[0] return slave.hgetall(key)
def extend_session(self, user_id): """延长会话(写操作)""" key = f"session:{user_id}" self.master.expire(key, 3600)
|
总结
Redis主从架构是构建高可用Redis系统的基础:
核心要点
- 复制机制:通过全量复制和增量复制实现数据同步
- 读写分离:主节点处理写操作,从节点处理读操作
- 故障处理:支持手动和自动的主从切换
- 性能优化:通过连接池、监控等手段提升性能
最佳实践
- 合理配置主从节点参数
- 实施监控和告警机制
- 定期测试故障切换
- 做好数据备份和恢复
注意事项
- 从节点默认只读,不能执行写操作
- 主从复制存在延迟,需要根据业务需求调整
- 网络分区可能导致数据不一致
- 需要定期监控主从复制状态
理解Redis主从架构的原理和搭建方法,有助于我们构建高可用、高性能的Redis系统。