引言

Redis主从架构是Redis高可用性方案的基础,通过主节点(Master)和从节点(Slave)的协作,实现数据的复制和读写分离。本文将深入探讨Redis主从架构的原理,并提供详细的搭建实操指南。

Redis主从架构概述

什么是主从架构

Redis主从架构是一种数据复制方案,其中:

  • 主节点(Master):负责处理写操作,并将数据同步到从节点
  • 从节点(Slave):负责处理读操作,接收主节点的数据同步

主从架构的优势

  1. 读写分离:主节点处理写操作,从节点处理读操作,提升系统性能
  2. 数据冗余:多个从节点提供数据备份,提高数据安全性
  3. 负载分担:读请求分散到多个从节点,减轻主节点压力
  4. 故障恢复:主节点故障时,可以快速切换到从节点

主从复制原理

复制流程

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;

// 创建socket连接
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
// 从节点向主节点发送PING
void replicationSendPing(void) {
if (server.masterhost == NULL) return;

// 发送PING命令
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;

// 发送AUTH命令
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
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install redis-server

# CentOS/RHEL
sudo yum install redis
# 或者使用EPEL源
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
# 创建Redis数据目录
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

# AOF配置
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
# 从节点1配置文件
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

# AOF配置
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
# 从节点2配置文件
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

# AOF配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

# 内存配置
maxmemory 2gb
maxmemory-policy allkeys-lru

3. 启动从节点

1
2
3
4
5
6
7
8
9
# 启动从节点1
redis-server /opt/redis/slave1/redis-slave1.conf

# 启动从节点2
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
# 1. 停止主节点
redis-cli -p 6379 -a master123 shutdown

# 2. 提升从节点为主节点
redis-cli -p 6380 -a master123
> REPLICAOF NO ONE

# 3. 配置其他从节点指向新的主节点
redis-cli -p 6381 -a master123
> REPLICAOF 127.0.0.1 6380

自动切换(使用Sentinel)

1
2
3
4
5
6
7
# Sentinel配置文件
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连接
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
# Python客户端实现读写分离
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
# 监控脚本
#!/bin/bash

# 检查主从复制状态
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
# 电商系统Redis主从配置
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) # 1小时过期

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系统的基础:

核心要点

  1. 复制机制:通过全量复制和增量复制实现数据同步
  2. 读写分离:主节点处理写操作,从节点处理读操作
  3. 故障处理:支持手动和自动的主从切换
  4. 性能优化:通过连接池、监控等手段提升性能

最佳实践

  • 合理配置主从节点参数
  • 实施监控和告警机制
  • 定期测试故障切换
  • 做好数据备份和恢复

注意事项

  • 从节点默认只读,不能执行写操作
  • 主从复制存在延迟,需要根据业务需求调整
  • 网络分区可能导致数据不一致
  • 需要定期监控主从复制状态

理解Redis主从架构的原理和搭建方法,有助于我们构建高可用、高性能的Redis系统。