主从架构#
- 单节点 Redis 的并发能力是有上限的,要进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离。
同步原理#
全量同步#
- 主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点。
- replication id 和 offset。
- Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid。
- offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
- master如何得知salve是第一次来连接?
- slave做数据同步,必须向master声明自己的replication id和offset,master才可以判断到底需要同步哪些数据。
- slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
- master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。
- master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
- 因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致。
- 完整流程描述:
- slave节点请求增量同步。
- master节点判断replid,发现不一致,拒绝增量同步。
- master将完整内存数据生成RDB,发送RDB到slave。
- slave清空本地数据,加载master的RDB。
- master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave。
- slave执行接收到的命令,保持与master之间的同步。
增量同步#
- 全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
- 什么是增量同步?就是只更新slave与master存在差异的部分数据。
repl_backlog#
- repl_backlog 文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
- repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset。
- 随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset。
- 但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset。
- 棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
- 注意:repl_backlog 文件大小是有上线的,写满后会覆盖最早的数据。如果slave断开时间太久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。
- 主从同步可以保证主从数据的一致性,非常重要。
- 可以从以下几个方面来优化Redis主从就集群:
- 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
- Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO。
- 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步。
- 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力。
- 全量同步和增量同步区别?
- 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
- 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave。
- 什么时候执行全量同步?
- slave节点第一次连接master节点时。
- slave节点断开时间太久,repl_baklog中的offset已经被覆盖时。
- 什么时候执行增量同步?
- slave节点断开又恢复,并且在repl_baklog中能找到offset时。
- 解决了单机版并发量大,导致亲求延迟或者redis宕机服务停止的问题。
- 从数据库分担主数据库的读压力,若是主数据库只是写模式,那么实现读写分离,主数据库就没有读压力了。
- 解决了单机版单点故障的问题,若是主数据库挂了,那么从数据库可以随时顶上来。
- 数据的一致性问题,假如主数据库写操作完成,那么他的数据会被复制到从数据库,若是还没有及时复制到从数据库,读请求又来了,此时读取的数据就不是最新的数据。
- 主从同步的过程网络出故障了,导致主从同步失败,也会出现数据一致性的问题。
- 不具备自动容错和恢复的功能,一旦主数据库挂掉,从节点晋升为主数据库的过程需要人为操作,维护的成本就会升高,并且主节点的写能力,存储能力都会受到限制。
主从集群搭建#
- 准备三台redis,一台master,两台slave。
- 开启主从关系命令。配置主从可以使用 replicaof 或者 slaveof(5.0以前)命令。
- 有临时和永久两种模式:
- 修改配置文件(永久生效):在redis.conf中添加一行配置
slaveof <masterip> <masterport>
。
- 使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):
slaveof <masterip> <masterport>
。
- 注意:在5.0以后新增命令replicaof,与salveof效果一致。
- 在从服务器上执行
slaveof <masterip> <masterport>
命令,masterip 主ip,masterport 主端口。
- 在主节点上查看集群信息命令,info replication。
slave 配置#
- slave 节点 redis.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
# 监听来自任意网络接口的连接
bind 0.0.0.0
# 关闭保护模式,接收远程连接
protected-mode no
# 监听端口
port 6379
# 指定客户端空闲多少秒后关闭连接
# 设置为 0,则不会因为客户端空闲而关闭连接
# 设置为正整数,表示客户端在指定的时间内没有发送任何指令,连接将被关闭
time 0
# 默认数据库数量
databases 1
#用守护线程的方式启动
daemonize no
# yes : RDB快照保存失败后 客户端不可写入redis,只可读。
# no : 禁用此功能
stop-writes-on-bgsave-error yes
# 是否检查rdb快照的完整性,损失大概 百分之十 的性能
rdbchecksum yes
#您可以配置副本实例以接受或不接受写入。
#就是主从复制中,slave节点是否可以写入数据(yes:不能写入;no:可以写入)
replica-read-only yes
# 配置RDB持久化模式
# 900s内至少一次写操作则执行bgsave进行RDB持久化
# asve ""
save 900 1
save 300 10
save 60 10000
# RDB是否压缩 ,建议不开启
# 压缩也会消耗cpu,磁盘的话不值钱
rdbcompression no
# 备份的RDB文件名称
dbfilename dump.rdb
# 备份文件保存的路径目录
dir ./
# 开启 AOF 持久化
appendonly yes
# AOF 每秒刷盘
appendfsync everysec
# AOF文件的名称
appendfilename "appendonly.aof"
# 节点登录口令
requirepass 12345678
# 配置主从,当前从节点加入主节点
slaveof redis-master 6379
# 主节点口令
masterauth 12345678
|
master 配置#
- master 节点 redis.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
# 监听来自任意网络接口的连接
bind 0.0.0.0
# 关闭保护模式,接收远程连接
protected-mode no
# 监听端口
port 6379
# 指定客户端空闲多少秒后关闭连接
# 设置为 0,则不会因为客户端空闲而关闭连接
# 设置为正整数,表示客户端在指定的时间内没有发送任何指令,连接将被关闭
time 0
# 默认数据库数量
databases 1
#用守护线程的方式启动
daemonize no
#yes : RDB快照保存失败后 客户端不可写入redis,只可读。
#no : 禁用此功能
stop-writes-on-bgsave-error yes
# 是否检查rdb快照的完整性,损失大概 百分之十 的性能
rdbchecksum yes
#当使用无盘复制时,master 在开始传输之前等待一段可配置的时间(以秒为单位),
#希望多个副本到达并且传输可以并行化。对于慢速磁盘和快速(大带宽)网络,无盘复制效果更好。
repl-diskless-sync yes
#您可以配置副本实例以接受或不接受写入。
#就是主从复制中,slave节点是否可以写入数据(yes:不能写入;no:可以写入)
replica-read-only yes
# 配置RDB持久化模式
# 900s内至少一次写操作则执行bgsave进行RDB持久化
# asve ""
save 900 1
save 300 10
save 60 10000
# RDB是否压缩 ,建议不开启
# 压缩也会消耗cpu,磁盘的话不值钱
rdbcompression no
# 备份的RDB文件名称
dbfilename dump.rdb
# 备份文件保存的路径目录
dir ./
# 开启 AOF 持久化
appendonly yes
# AOF 每秒刷盘
appendfsync everysec
# AOF文件的名称
appendfilename "appendonly.aof"
# 节点登录口令
requirepass 12345678
|
docker-compose.yml#
- redis.conf 配置文件可以到 github 下载,https://github.com/redis/redis/tree/6.2.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
version: '3'
services:
redis-master:
container_name: redis-master
image: redis:6.2.3
privileged: true
restart: unless-stopped
command: redis-server /etc/redis/redis.conf
ports:
- "7001:6379"
volumes:
- /app/docker/redis/master/data:/data
- ./docker/redis/master/redis.conf:/etc/redis/redis.conf
- /etc/localtime:/etc/localtime:ro
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "3"
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 1s
retries: 3
networks:
redis-network:
ipv4_address: 172.30.1.2
redis-slave1:
container_name: redis-slave1
image: redis:6.2.3
privileged: true
restart: unless-stopped
command: redis-server /etc/redis/redis.conf
ports:
- "7002:6379"
volumes:
- /app/docker/redis/slave1/data:/data
- ./docker/redis/slave1/redis.conf:/etc/redis/redis.conf
- /etc/localtime:/etc/localtime:ro
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "3"
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 1s
retries: 3
networks:
redis-network:
ipv4_address: 172.30.1.3
redis-slave2:
container_name: redis-slave2
image: redis:6.2.3
privileged: true
restart: unless-stopped
command: redis-server /etc/redis/redis.conf
ports:
- "7003:6379"
volumes:
- /app/docker/redis/slave2/data:/data
- ./docker/redis/slave2/redis.conf:/etc/redis/redis.conf
- /etc/localtime:/etc/localtime:ro
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "3"
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 1s
retries: 3
networks:
redis-network:
ipv4_address: 172.30.1.4
networks:
redis-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.30.1.0/24
|
- 使用 docker-compose 启动服务。
$ docker-compose -p redis-cluster up -d