抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Redis主从方案

单机有什么问题

单机即在一台机器上部署一个redis节点,主要会存在以下问题

机器故障

​ 如果发生机器故障,例如磁盘损坏,主板损坏等,未能在短时间内修复好,客户端将无法连接redis。
当然如果仅仅是redis节点挂掉了,可以进行问题排查然后重启,姑且不考虑这段时间对外服务的可用性,那还是可以接受的。
而发生机器故障,基本是无济于事。除非把redis迁移到另一台机器上,并且还要考虑数据同步的问题。

容量瓶颈

​ 假如一台机器是16G内存,redis使用了12G内存,而其他应用还需要使用内存,假设我们总共需要60G内存要如何去做呢,是否有必要购买64G内存的机器?

QPS瓶颈

​ redis官方数据显示可以达到10w的QPS,如果业务需要100w的QPS怎么去做呢?

​ 关于容量瓶颈和QPS瓶颈是redis分布式需要解决的问题,而机器故障就是高可用的问题了。

redis 主从架构原理详解

一主一从

​ 如图所示左边是Master节点,右边是slave节点,即主节点和从节点。从节点也是可以对外提供服务的,主节点是有数据的,从节点可以通过复制操作将主节点的数据同步过来,并且随着主节点数据不断写入,从节点数据也会做同步的更新。

​ 整体起到的就是数据备份的效果。

一主多从

​ 除了一主一从模型之外,redis还提供了一主多从的模型,也就是一个master可以有多个slave,也就相当于有了多份的数据副本。
这样可以做一个更加高可用的选择,例如一个master和一个slave挂掉了,还能有其他的slave数据备份。

读写分离

​ 在redis主从架构中,Master节点负责处理写请求,Slave节点只处理读请求。对于写请求少,读请求多的场景,例如电商详情页,通过这种读写分离的操作可以大幅提高并发量,通过增加redis从节点的数量可以使得redis的QPS达到10W+。

​ 让master节点负责提供写服务,而将数据读取的压力进行分流和负载,分摊给所有的从节点。

作用
  1. 数据副本(备份)
  2. 扩展读性能(读写分离)
小结
  1. 一个master可以有多个slave
  2. 一个slave只能有一个master
  3. 数据流向是单向的,master到slave

主从同步

​ Master节点接收到写请求并处理后,需要告知Slave节点数据发生了改变,保持主从节点数据一致的行为称为主从同步,所有的Slave都和Master通信去同步数据也会加大Master节点的负担,实际上,除了主从同步,redis也可以从从同步,我们在这里统一描述为主从同步。

主从同步的方法

增量同步

​ redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了 (偏移量,这是redis-2.8之后才有的特性)。从节点同步数据的时候不会影响主节点的正常工作,也不会影响自己对外提供读服务的功能,从节点会用旧的数据来提供服务,当同步完成后,需要删除旧数据集,加载新数据,这个时候才会暂停对外服务。

​ 因为内存的 buffer 是有限的,所以 redis 主节点不能将所有的指令都记录在内存 buffer 中。redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满 了,就会从头开始覆盖前面的内容

适用场景

​ 用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销,需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制

​ 部分复制是 Redis 2.8 以后出现的,之所以要加入部分复制,是因为全量复制会产生很多问题,比如像上面的时间开销大、无法隔离等问题, Redis 希望能够在 master 出现抖动(相当于断开连接)的时候,可以有一些机制将复制的损失降低到最低

  1. 如果网络抖动(连接断开 connection lost)
  2. 主机master 还是会写 replbackbuffer(复制缓冲区)
  3. 从机slave 会继续尝试连接主机
  4. 从机slave 会把自己当前 runid 和偏移量传输给主机 master,并且执行 pysnc 命令同步
  5. 如果 master 发现你的偏移量是在缓冲区的范围内,就会返回 continue 命令同步了 offset 的部分数据,所以部分复制的基础就是偏移量 offset。
快照同步

​ 如果节点间网络通信不好,那么当从节点同步的速度不如主节点接收新写请求的速度时,buffer 中会丢失一部分指令,从节点中的数据将与主节点中的数据不一致,此时将会触发快照同步。

​ 快照同步是一个非常耗费资源的操作,它首先需要在主节点上进行一次 bgsave 将当前内存的数据全部快照到RDB文件中,然后再将快照文件的内容全部传送到从节点。从节点将RDB文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步。

​ 在整个快照同步进行的过程中,主节点的复制 buffer 还在不停的往前移动,如果快照同步的时间过长或者复制 buffer 太小,都会导致同步期间的增量指令在复制 buffer 中被覆盖,这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。所以需要配置一个合适的复制 buffer 大小参数,避免快照复制的死循环。

缓冲区大小调节

​ 由于缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size)来设置;例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。

全量复制的开销

实际上全量复制的开销是非常大的,主要体现在如下方面

1.bgsave的开销,每次bgsave需要fork子进程,对内存和CPU的开销很大

2.RDB文件网络传输的时间(网络带宽)

3.从节点清空数据的时间

4.从节点加载RDB的时间

5.可能的AOF重写时间(如果我们的从节点开启了AOF,则加载完RDB后会对AOF进行一个重写,保证AOF是最新的)

全量复制除了上述开销之外,还会有个问题:

假如master和slave网络发生了抖动,那一段时间内这些数据就会丢失,对于slave来说这段时间master更新的数据是不知道的。最简单的方式就是再做一次全量复制,从而获取到最新的数据,在redis2.8之前是这么做的。

无盘复制

​ 主节点在进行快照同步时,会进行大量的文件 IO 操作,特别是对于非 SSD 磁盘存储时,快照会对系统的负载产生较大影响。特别是当系统正在进行 AOF 的 fsync 操作时如果发生快照复制,fsync 将会被推迟执行,这就会严重影响主节点的服务效率。

​ 从 Redis 2.8.18 版开始支持无盘复制。所谓无盘复制是主节点会一边遍历内存,一遍将序列化的内容发送到从节点,而不是生成完整的 RDB 文件后才进行 IO 传输从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。

主从同步的详细流程

  1. 在从节点的配置文件中的slaveof配置项中配置了主节点的IP和port后,从节点就知道自己要和那个主节点进行连接了。

  2. 从节点内部有个定时任务,会每秒检查自己要连接的主节点是否上线,如果发现了主节点上线,就跟主节点进行网络连接。注意,此时仅仅是取得连接,还没有进行主从数据同步。

  3. 从节点发送ping命令给主节点进行连接,如果设置了口令认证(主节点设置了requirepass),那么从节点必须发送正确的口令(masterauth)进行认证。

  4. 主从节点连接成功后,主从节点进行一次快照同步。事实上,是否进行快照同步需要判断主节点的run id,当从节点发现已经连接过某个run id的主节点,那么视此次连接为重新连接,就不会进行快照同步。相同IP和port的主节点每次重启服务都会生成一个新的run id,所以每次主节点重启服务都会进行一次快照同步,如果想重启主节点服务而不改变run id,使用redis-cli debug reload命令。

  5. 当开始进行快照同步后,主节点在本地生成一份rdb快照文件,并将这个rdb文件发送给从节点,如果复制时间超过60秒(配置项:repl-timeout),那么就会认为复制失败,如果数据量比较大,要适当调大这个参数的值。主从节点进行快照同步的时候,主节点会把接收到的新请求命令写在缓存 buffer 中,当快照同步完成后,再把 buffer 中的指令增量同步到从节点。如果在快照同步期间,内存缓冲区大小超过256MB,或者超过64MB的状态持续时间超过60s(配置项:client-output-buffer-limit slave 256MB 64MB 60),那么也会认为快照同步失败。

  6. 从节点接收到RDB文件之后,清空自己的旧数据,然后重新加载RDB到自己的内存中,在这个过程中基于旧的数据对外提供服务。如果主节点开启了AOF,那么在快照同步结束后会立即执行BGREWRITEAOF,重写AOF文件。

  7. 主节点维护了一个backlog文件,默认是1MB大小,主节点向从节点发送全量数据(RDB文件)时,也会同步往backlog中写,这样当发送全量数据这个过程意外中断后,从backlog文件中可以得知数据有哪些是发送成功了,哪些还没有发送,然后当主从节点再次连接后,从失败的地方开始增量同步。这里需要注意的是,当快照同步连接中断后,主从节点再次连接并非是第一次连接,所以进行增量同步,而不是继续进行快照同步。

  8. 快照同步完成后,主节点后续接收到写请求导致数据变化后,将和从节点进行增量同步,遇到 buffer 溢出则再触发快照同步。

  9. 主从节点都会维护一个offset,随着主节点的数据变化以及主从同步的进行,主从节点会不断累加自己维护的offset,从节点每秒都会上报自己的offset给主节点,主节点也会保存每个从节点的offset,这样主从节点就能知道互相之间的数据一致性情况。从节点发送psync runid offset命令给主节点从而开始主从同步,主节点会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,也可能是CONTINUE触发增量复制。

  10. 主从节点因为网络原因导致断开,当网络接通后,不需要手工干预,可以自动重新连接。

  11. 主节点如果发现有多个从节点连接,在快照同步过程中仅仅会生成一个RDB文件,用一份数据服务所有从节点进行快照同步。

  12. 从节点不会处理过期key,当主节点处理了一个过期key,会模拟一条del命令发送给从节点。

  13. 主从节点会保持心跳来检测对方是否在线,主节点默认每隔10秒发送一次heartbeat,从节点默认每隔1秒发送一个heartbeat。

  14. 建议在主节点使用AOF+RDB的持久化方式,并且在主节点定期备份RDB文件,而从节点不要开启AOF机制,原因有两个,一是从节点AOF会降低性能,二是如果主节点数据丢失,主节点数据同步给从节点后,从节点收到了空的数据,如果开启了AOF,会生成空的AOF文件,基于AOF恢复数据后,全部数据就都丢失了,而如果不开启AOF机制,从节点启动后,基于自身的RDB文件恢复数据,这样不至于丢失全部数据。

slaveof命令

如图,想让6380节点成为6379的从节点,只需要执行 slaveof 命令即可,此复制命令是异步进行的,redis会自动进行后续数据复制的操作。
注:一般生产环境不允许主从节点都在一台机器上,因为没有任何的价值。

redis 主从架构搭建

master节点配置

​ 使用3个虚拟机搭建一主二从的redis主从架构集群。在每台机器上安装redis,然后修改redis配置文件,其中一个master节点的配置如下(未列出的保持默认即可):

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
# basic
daemonize yes
port 6379
logfile /home/hadoop/logs/redis/6379/redis_6379.log
pidfile /home/hadoop/pid/redis/6379/redis_6379.pid
dir /home/hadoop/data/redis/6379

# aof
# 主节点打开AOF机制
appendonly yes

# master
# 绑定本台机器的IP,否则主从节点无法通信
bind 192.168.239.101
# 设置master的认证口令为redis
requirepass redis
# backlog大小
repl-backlog-size 1mb
# 快照同步的超时时间
repl-timeout 60
# 开启无盘复制
repl-diskless-sync yes
# 无盘复制的延迟默认为5s,是为了等待更多的slave连接
repl-diskless-sync-delay 5
# 是否开启主从节点复制数据的延迟机制
# 当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小
# 但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景
# 当开启时,主节点会合并较小的TCP数据包从而节省带宽。
# 默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。
# 这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景
repl-disable-tcp-nodelay no
# 触发快照同步的条件
# 如果增量同步的缓存大于256MB,或者超过60s大于64MB,则触发快照同步
client-output-buffer-limit slave 256mb 64mb 60
# 主从节点进行心跳的时间间隔
repl-ping-slave-period 10

两个slave节点的配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# basic
daemonize yes
port 6379
logfile /home/hadoop/logs/redis/6379/redis_6379.log
pidfile /home/hadoop/pid/redis/6379/redis_6379.pid
dir /home/hadoop/data/redis/6379

# slave
# 绑定本机的IP,另一个为192.168.239.103
bind 192.168.239.102
# 绑定master的ip和port
slaveof 192.168.239.101 6379
# 从节点只读
slave-read-only yes
# 从节点在处于快照同步期间是否对外提供服务
slave-serve-stale-data yes
# 如果 master 检测到 slave 的数量小于这个配置设置的值,将拒绝对外提供服务,0 代表,无论 slave 有几个都会对外提供服务
min-slaves-to-write 0
# 如果 master 发现大于等于 ${min-slaves-to-write} 个 slave 与自己的心跳超过此处配置的时间(单位s)
# 就拒绝对外提供服务
min-slaves-max-lag 10
# master的认证口令
masterauth redis

启动3个redis服务:

1
2
3
> $ redis-server ~/apps/redis-4.0.12/redis_6379.conf
> $ redis-server ~/apps/redis-4.0.12/redis_6379.conf
> $ redis-server ~/apps/redis-4.0.12/redis_6379.conf

查看master日志

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
# master 已经准备就绪
7810:M 15 Feb 18:08:54.108 * Ready to accept connections
# slave 92.168.239.102:6379 请求主从同步
7810:M 15 Feb 18:08:54.770 * Slave 192.168.239.102:6379 asks for synchronization

# slave 92.168.239.102:6379 请求快照同步
7810:M 15 Feb 18:08:54.770 * Full resync requested by slave 192.168.239.102:6379
# master 开始写 RDB 文件到本地磁盘
7810:M 15 Feb 18:08:54.771 * Starting BGSAVE for SYNC with target: disk
# 子进程(7816)开始写 RDB 文件到磁盘
7810:M 15 Feb 18:08:54.771 * Background saving started by pid 7816
# RDB 文件写到本地磁盘成功
7816:C 15 Feb 18:08:54.774 * DB saved on disk
7816:C 15 Feb 18:08:54.774 * RDB: 6 MB of memory used by copy-on-write
7810:M 15 Feb 18:08:54.812 * Background saving terminated with success
# master 和 slave 192.168.239.102:6379 主从同步成功
7810:M 15 Feb 18:08:54.813 * Synchronization with slave 192.168.239.102:6379 succeeded

# slave 92.168.239.103:6379 请求快照同步
7810:M 15 Feb 18:08:55.564 * Slave 192.168.239.103:6379 asks for synchronization
7810:M 15 Feb 18:08:55.564 * Full resync requested by slave 192.168.239.103:6379
7810:M 15 Feb 18:08:55.564 * Starting BGSAVE for SYNC with target: disk
7810:M 15 Feb 18:08:55.564 * Background saving started by pid 7817
7817:C 15 Feb 18:08:55.567 * DB saved on disk
7817:C 15 Feb 18:08:55.567 * RDB: 6 MB of memory used by copy-on-write
7810:M 15 Feb 18:08:55.619 * Background saving terminated with success
# master 和 slave 192.168.239.103:6379 主从同步成功
7810:M 15 Feb 18:08:55.620 * Synchronization with slave 192.168.239.103:6379 succeeded

看日志发现一个问题,我们在原理中介绍说:
主节点如果发现有多个从节点连接,在快照同步过程中仅仅会生成一个RDB文件,用一份数据服务所有从节点进行快照同步。
然而这里master的日志显示写了两次RDB文件,这里我查一些资料再来更新。(!!!待完善)

查看slave日志

这里只列出一个slave的日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
7112:S 15 Feb 18:08:54.796 * DB loaded from disk: 0.027 seconds
7112:S 15 Feb 18:08:54.796 * Ready to accept connections
# 连接到 master 192.168.239.101:6379
7112:S 15 Feb 18:08:54.796 * Connecting to MASTER 192.168.239.101:6379
# 开始主从同步
7112:S 15 Feb 18:08:54.796 * MASTER <-> SLAVE sync started
7112:S 15 Feb 18:08:54.797 * Non blocking connect for SYNC fired the event.
7112:S 15 Feb 18:08:54.797 * Master replied to PING, replication can continue...
7112:S 15 Feb 18:08:54.798 * Partial resynchronization not possible (no cached master)
# 与run id 为 1b9f6081fa8cfd0d1c3771daa5224de2a734c5e5:0 的 master 进行快照同步
7112:S 15 Feb 18:08:54.800 * Full resync from master: 1b9f6081fa8cfd0d1c3771daa5224de2a734c5e5:0
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: receiving 176 bytes from master
# 删除旧数据
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: Flushing old data
# 加载 RDB 到内存中
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: Loading DB in memory
# 同步成功
7112:S 15 Feb 18:08:54.841 * MASTER <-> SLAVE sync: Finished with success

测试主从架构

  1. 使用redis-cli访问3个redis服务
1
2
3
4
5
6
7
8
9
# 这里由于我们配置的时候设置了认证口令,所以 redis-cli 连接服务也需要认证,不认证可以进入命令行,但是无法进行操作,比如 set
[hadoop@node01 ~]$ redis-cli -h 192.168.239.101 -p 6379 -a redis
192.168.239.101:6379>

[hadoop@node02 redis-4.0.12]$ redis-cli -h 192.168.239.102 -p 6379 -a redis
192.168.239.102:6379>

[hadoop@node03 bin]$ redis-cli -h 192.168.239.103 -p 6379 -a redis
192.168.239.103:6379>
  1. 在 master 节点上 set 一个数据
1
2
192.168.239.101:6379> set name tom
OK
  1. 从节点上获取数据
1
2
3
4
5
192.168.239.102:6379> get name
"tom"

192.168.239.103:6379> get name
"tom"
  1. 尝试在slave上写入数据
1
2
192.168.239.103:6379> set age 20
(error) READONLY You can't write against a read only slave.

redis主从架构搭建成功!

wait 命令(扩展,redis-3.0新增)

1
wait m t

wait 提供两个参数,第一个参数是从节点的数量 m,第二个参数是时间 t,以毫秒
为单位。它表示等待 wait 指令之前的所有写操作同步到 n 个子节点 (也就是确保
m 个子节点的同步没有滞后),最多等待时间 t。如果时间 t=0,表示无限等待直到
N 个从库同步完成达成一致。
假设此时某个子节点与主节点网络断开,wait 指令第二个参数时间 t = 0,主从同步无法继续
进行,wait 指令会永远阻塞,redis 服务器将丧失可用性。

1
2
3
4
node01:6379> set name bob
OK
node01:6379> wait 2 5000
(integer) 2

评论