一.Redis 具有高可靠性
- 数据尽量少丢失。AOF和RDB保证数据少丢失。
- 服务尽量少中断。增加副本冗余量,将一份数据同时保存在多个实例,即使一个实例出现故障,其他实例可以对外提供服务。
多实例之间的数据如何保证一致性
Redis提供了主从库模式,以保证数据副本的一致,主从库之间采用读写分离的方式。
读操作 | 主库和从库都可以接收 |
写操作 | 主库执行,主库将写操作同步至从库 |
为什么采取读写分离的方式?
如果主库和从库都能接收客户端读写操作,那么一个数据前后修改三次,每一次修改请求都发送到不同实例上,在不同实例上执行,那么这个数据在这三个实例上的副本就不一致,在读取这个数据的时候就可能读取到旧值。如果非要保持数据在三个实例上一致那就要加锁,实例间协调修改,这会带来额外的开销。
读写分离的模式,所有数据的修改都只会在主库上进行,不用协调三个实例。主库有了最新数据后,会同步给从库,这样主从库的数据是一致的。
二.主从库间第一次同步
replicaof(Redis 5.0之前使用slave of)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。
例如,现有实例1(ip:127.0.0.1:6379)和实例2(ip:127.0.0.1:6380)
replicaof 127.0.0.1 6379这个命令以后,实例2就变成实例1的从库,并从实例1上复制数据。
- 第一阶段是主从库建立连接,协商同步的过程,就是为全量复制做准备。在这一步,主库和从库建立连接,并告诉主库即将进行同步,主库确认回复以后,主库就可以开始同步了。
- 从库给主库发送pysnc命令,表示要进行数据同步,主库根据证明了参数启动复制。
- psync命令包含主库的runID和复制进度offset两个参数。runId是每个Redis实例启动时生成的一个随机id,用来标识这个实例。
- 当从库和主库第一次复制时,不知道主库runId,所以将runId设为"?"。offset此时设为-1,标识第一次复制。
- 主库收到psync命令以后,会用FULLRESYNC响应命令带上两个参数:主库runId和主库目前的复制进度offset,返回给从库。
- 从库响应后,会记录下这两个参数。FULLRESYNC响应表示第一次复制采用全量复制,主库会把当前所有的数据都复制给从库。
第二个阶段
- 内存快照生成RDB文件。
- 主库执行bgsave命令,生成RDB文件,将文件发给从库,从库接收到RDB文件后,先清空当前数据库,再加载RDB文件,这就是从库在通过replication of命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把数据库清空。
- 主库将数据同步给从库过程中主库不会被阻塞,让可以正常接收请求,但是这些请求中的写操作并没有记录刚刚并不会记录给生成的RDB文件。为保证主从一致性,主库会在内存中用专门的replication buffer,记录RDB文件生成后收到的所有写操作。
第三阶段:
- 主库会把第二阶段执行过程中新收到的写命令写入replication buffer,再发送给从库。
- replication buffer 中的修改操作发给从库,从库执行这些操作,实现主从同步。
三.主从级联模式分担全量复制时主库压力
生成RDB文件和传输RDB文件。
主库忙于fork子进程生成RDB文件,进行数据全量同步。
阻塞主线程处理正常请求,从而导致主库相应应用程序请求变慢。此时,传输RDB文件会占用主库的网络带宽,同样会给主库资源使用带来压力。
主 - 从 - 从
主-从-从模式将主库生成RDB和传输RDB的压力,以级联的方式分散到从库上。
内存资源配置较高的从库,用于级联其他从库。然后,再选择一些从库,让他们与刚才所选从库建立主从关系。
replicaof 所选从库IP 6379
第一阶段的全量同步以后,会一直维护一个网络连接,主库会通过这个连接将后续收到的命令同步给从库。这个过程被称为长连接的命令传播,避免频繁建立连接多的开销。
网络断连或阻塞。如果网络断连,主从库之间无法进行命令传播,从库数据无法与主库保持一致,客户端可能从从库读到旧数据。
四.主从库间网络断了重连
Redis2.8以前 | 从库重新全量复制,开销大。全量复制是同步所有数据 |
Redis2.8以后 | 主从库采用增量复制的方式继续同步。增量复制将主从库网络断连期间主库收到的写操作同步给从库。 |
增量复制时,主从库如何保持同步:repl_backlog_buffer缓冲区
- 写入replication buffer,同时将这些命令也写入repl_backlog_buffer这个缓冲区。
- repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库会记录自己读到的位置。
- 主库和从库的写读位置在一起,这算是他们的起始位置。随着主库不断接收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置。我们通常用偏移量来衡量这个偏移距离的大小。
- 主库对应的偏移量是master_repl_offset。主库接收的写操作越多,这个值就越大。
- 从库在复制完写操作命令后,它在缓冲区中读位置也开始逐步便宜刚才起始位置,此时,从库已复制的偏移量slave_repl_offset也在不断增加。正常情况下两者偏移量基本相等。
主从库恢复连接:
- psync命令,并把自己当前的slave_repl_offset发给主库,主库会判断自己的master_repl_offset和salve_repl_offset之间的差距。
- 主库把master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库。
因为repl_backlog_buffer是一个环形缓冲区,所以缓冲区写满以后,主库会继续写入,此时,就会覆盖掉之前写入的操作,如果从库读取速度比较慢,就会导致从库还未读取的操作被主库新写的操作覆盖掉。导致主从库间的数据不一致。
如何避免?
repl_backlog_size这个参数;
缓冲空间大小=主库写入命令速度*操作大小-主从库网络传输命令速度*操作大小。
repl_backlog_size=2*缓冲空间大小
相关练习:
主库每秒写入2000个操作,每个操作大小为2KB,网络每秒传输1000个操作,那么1000个操作需要缓存起来,就至少需要2MB缓冲空间,否则新写的命令就会覆盖掉旧操作。我们为了应对可能的突发压力,把repl_backlog_size设置为4MB。主从不一致风险大大降低。
如果并发请求量增大,两倍缓存都存不下新的操作请求,主从库数据让然不一致。
- 根据Redis内存值增加repl_backlog_size
- 使用切片集群分担单个主库的请求压力
几GB级别合适,减少RDB文件生成传输与重新加载的开销。
课后问题
AOF记录的操作命令更全,相比于RDB丢失的数据更少,那么,为什么主从库间的复制不使用AOF呢?
- 压缩的二进制文件(不同数据类型数据做了针对性优化),文件很小,而AOF文件记录的是每一次写操作的命令,写操作越多文件就会变得很大,其中还包括对同一个key的冗余操作。
- 主从全量数据同步时,传输RDB文件可以尽量降低对主库机器网络带宽的消耗,从库里加载RDB文件时,一是文件小,读取整个文件速度快,二是因为RDB文件存储的都是二进制数据,直接根据RDB协议解析还原数据即可,速度快,AOF需要依次执行每个写命令,过程冗长恢复速度慢,所以使用RDB主从全量同步成本最低。
- 使用AOF做全量同步,意味着必须打开AOF功能,打开AOF选择文件刷盘策略,选择不当会严重影响Redis性能。而RDB只需要定时备份和主从全量同步数据时触发快照生成。很多数据丢失不敏感的业务场景,其实不需要开启AOF。
课外相关问题:
1.不是主从库断开连接后,主库才会把写操作写入repl_backlog_buffer,只要从库存在,这个repl_backlog_buffer就会存在。主库的所有写命令除了传播给从库外都会在repl_backlog_buffer中记录一份,缓存起来,只有预先缓存了这些命令,当从库断开连接后,从库重新发送psync $master_runid $offset主库从$offset中在repl_backlog_buffer中找到从库断开的位置发送从$offset之后的增量数据给从库即可。