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节点负责提供写服务,而将数据读取的压力进行分流和负载,分摊给所有的从节点。
作用
- 数据副本(备份)
- 扩展读性能(读写分离)
小结
- 一个master可以有多个slave
- 一个slave只能有一个master
- 数据流向是单向的,master到slave
主从同步
Master节点接收到写请求并处理后,需要告知Slave节点数据发生了改变,保持主从节点数据一致的行为称为主从同步,所有的Slave都和Master通信去同步数据也会加大Master节点的负担,实际上,除了主从同步,redis也可以从从同步,我们在这里统一描述为主从同步。
主从同步的方法
增量同步
redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了 (偏移量,这是redis-2.8之后才有的特性)。从节点同步数据的时候不会影响主节点的正常工作,也不会影响自己对外提供读服务的功能,从节点会用旧的数据来提供服务,当同步完成后,需要删除旧数据集,加载新数据,这个时候才会暂停对外服务。
因为内存的 buffer 是有限的,所以 redis 主节点不能将所有的指令都记录在内存 buffer 中。redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满 了,就会从头开始覆盖前面的内容
适用场景
用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销,需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制
部分复制是 Redis 2.8 以后出现的,之所以要加入部分复制,是因为全量复制会产生很多问题,比如像上面的时间开销大、无法隔离等问题, Redis 希望能够在 master 出现抖动(相当于断开连接)的时候,可以有一些机制将复制的损失降低到最低
- 如果网络抖动(连接断开 connection lost)
- 主机master 还是会写 replbackbuffer(复制缓冲区)
- 从机slave 会继续尝试连接主机
- 从机slave 会把自己当前 runid 和偏移量传输给主机 master,并且执行 pysnc 命令同步
- 如果 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 传输从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。
主从同步的详细流程
在从节点的配置文件中的
slaveof
配置项中配置了主节点的IP和port后,从节点就知道自己要和那个主节点进行连接了。从节点内部有个定时任务,会每秒检查自己要连接的主节点是否上线,如果发现了主节点上线,就跟主节点进行网络连接。注意,此时仅仅是取得连接,还没有进行主从数据同步。
从节点发送ping命令给主节点进行连接,如果设置了口令认证(主节点设置了requirepass),那么从节点必须发送正确的口令(masterauth)进行认证。
主从节点连接成功后,主从节点进行一次快照同步。事实上,是否进行快照同步需要判断主节点的
run id
,当从节点发现已经连接过某个run id
的主节点,那么视此次连接为重新连接,就不会进行快照同步。相同IP和port的主节点每次重启服务都会生成一个新的run id
,所以每次主节点重启服务都会进行一次快照同步,如果想重启主节点服务而不改变run id
,使用redis-cli debug reload
命令。当开始进行快照同步后,主节点在本地生成一份rdb快照文件,并将这个rdb文件发送给从节点,如果复制时间超过60秒(配置项:repl-timeout),那么就会认为复制失败,如果数据量比较大,要适当调大这个参数的值。主从节点进行快照同步的时候,主节点会把接收到的新请求命令写在缓存 buffer 中,当快照同步完成后,再把 buffer 中的指令增量同步到从节点。如果在快照同步期间,内存缓冲区大小超过256MB,或者超过64MB的状态持续时间超过60s(配置项:
client-output-buffer-limit slave 256MB 64MB 60
),那么也会认为快照同步失败。从节点接收到RDB文件之后,清空自己的旧数据,然后重新加载RDB到自己的内存中,在这个过程中基于旧的数据对外提供服务。如果主节点开启了AOF,那么在快照同步结束后会立即执行BGREWRITEAOF,重写AOF文件。
主节点维护了一个backlog文件,默认是1MB大小,主节点向从节点发送全量数据(RDB文件)时,也会同步往backlog中写,这样当发送全量数据这个过程意外中断后,从backlog文件中可以得知数据有哪些是发送成功了,哪些还没有发送,然后当主从节点再次连接后,从失败的地方开始增量同步。这里需要注意的是,当快照同步连接中断后,主从节点再次连接并非是第一次连接,所以进行增量同步,而不是继续进行快照同步。
快照同步完成后,主节点后续接收到写请求导致数据变化后,将和从节点进行增量同步,遇到 buffer 溢出则再触发快照同步。
主从节点都会维护一个offset,随着主节点的数据变化以及主从同步的进行,主从节点会不断累加自己维护的offset,从节点每秒都会上报自己的offset给主节点,主节点也会保存每个从节点的offset,这样主从节点就能知道互相之间的数据一致性情况。从节点发送
psync runid offset
命令给主节点从而开始主从同步,主节点会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,也可能是CONTINUE触发增量复制。主从节点因为网络原因导致断开,当网络接通后,不需要手工干预,可以自动重新连接。
主节点如果发现有多个从节点连接,在快照同步过程中仅仅会生成一个RDB文件,用一份数据服务所有从节点进行快照同步。
从节点不会处理过期key,当主节点处理了一个过期key,会模拟一条del命令发送给从节点。
主从节点会保持心跳来检测对方是否在线,主节点默认每隔10秒发送一次heartbeat,从节点默认每隔1秒发送一个heartbeat。
建议在主节点使用AOF+RDB的持久化方式,并且在主节点定期备份RDB文件,而从节点不要开启AOF机制,原因有两个,一是从节点AOF会降低性能,二是如果主节点数据丢失,主节点数据同步给从节点后,从节点收到了空的数据,如果开启了AOF,会生成空的AOF文件,基于AOF恢复数据后,全部数据就都丢失了,而如果不开启AOF机制,从节点启动后,基于自身的RDB文件恢复数据,这样不至于丢失全部数据。
slaveof命令
如图,想让6380节点成为6379的从节点,只需要执行 slaveof
命令即可,此复制命令是异步进行的,redis会自动进行后续数据复制的操作。
注:一般生产环境不允许主从节点都在一台机器上,因为没有任何的价值。
redis 主从架构搭建
master节点配置
使用3个虚拟机搭建一主二从的redis主从架构集群。在每台机器上安装redis,然后修改redis配置文件,其中一个master节点的配置如下(未列出的保持默认即可):
1 | # basic |
两个slave节点的配置如下
1 | # basic |
启动3个redis服务:
1 | $ redis-server ~/apps/redis-4.0.12/redis_6379.conf |
查看master日志
1 | # master 已经准备就绪 |
看日志发现一个问题,我们在原理中介绍说:
主节点如果发现有多个从节点连接,在快照同步过程中仅仅会生成一个RDB文件,用一份数据服务所有从节点进行快照同步。
然而这里master的日志显示写了两次RDB文件,这里我查一些资料再来更新。(!!!待完善)
查看slave日志
这里只列出一个slave的日志
1 | 7112:S 15 Feb 18:08:54.796 * DB loaded from disk: 0.027 seconds |
测试主从架构
- 使用
redis-cli
访问3个redis服务
1 | 这里由于我们配置的时候设置了认证口令,所以 redis-cli 连接服务也需要认证,不认证可以进入命令行,但是无法进行操作,比如 set |
- 在 master 节点上 set 一个数据
1 | 192.168.239.101:6379> set name tom |
- 从节点上获取数据
1 | 192.168.239.102:6379> get name |
- 尝试在slave上写入数据
1 | 192.168.239.103:6379> set age 20 |
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 | node01:6379> set name bob |