漫画理解redis持久化和主从复制

内存和磁盘

为了方便理解,假设某公司暂存文件资料的柜子,有多个格子,柜子还有一个专门的管理员小张,他的工作就是按公司员工的命令在某个格子(key)存资料(value),取资料(value)。
这个柜子在办公室内为了方便快速存取,也不关门也不锁,总之就是方便。
漫画理解redis持久化和主从复制
文章图片

公司除了这个柜子可以存储资料,还有一个专门的资料仓库也可以存储资料,而且仓库有专人守护,保证资料一般情况肯定丢不了。
【漫画理解redis持久化和主从复制】这个小张柜子就是是redis,其中柜子就是内存,存取很快,但一重启数据就清空。
这个资料仓库就是磁盘,存取比较慢,但文件不会丢失。
公司员工就相当于客户端,可以给小张发命令存取资料。
柜子只有一个管理员小张对应redis的单线程。
本文例子里的所有人物就相当于执行程序的cpu,脑子记性为0,只能靠纸笔记录。
RDB
某天有个领导说这个柜子用的很方便,但希望资料不会丢失。
于是有人提出方案,方案名叫RDB,思路比较简单,每隔一段时间,把当前资料存储的位置和内容全都记录到一个快照文件里,并把快照文件放到存储仓库,快照文件的格式类似如下
漫画理解redis持久化和主从复制
文章图片

某天保洁来了把柜子清空了,资料都丢失了(相当于redis重启),按仓库中的快照文件还原资料即可。
具体隔多久存一次呐?可以有多种原则,比如 "60 秒内有至少有 100 个格子被改动"就快照一次。
redis:
RDB快照(snapshot)是redis默认的持久化方案,只要设置好保存的时间策略(可以多个)就默认开启rdb,除此之外还可以设置快照文件名和保存的路径等:
save 3600 1 #3600 秒1次key变更 save 300 100 #300秒100次key变更 save 60 10000 #60 秒10000次key变更 dbfilename dump.rdb ##存储文件名 dir ./##存储路径

存储好之后,下次重启会直接按rdb文件恢复数据(实现了持久化)。
漫画理解redis持久化和主从复制
文章图片

内容如下(以二级制的形式压缩保存)
漫画理解redis持久化和主从复制
文章图片

刚开始小张自己去记录快照并去资料仓库存储,但是这个过程很慢,导致这期间其他人想存资料只能等。
为了解决这个问题,公司新给他配加一个人小李,这样小张还是干之前的活,小李负责在规定条件下记录并存储快照文件,在小李工作的期间(记录并去资料仓库存储),如果有新的存储变动,小张还会通知小李
漫画理解redis持久化和主从复制
文章图片
redis:
上面小张相当于主线程,由主线程去快照并持久化的方式叫save,该过程是同步的阻塞客户端请求,小李加入相当于新fork一个子线程专门处理rdb,这种叫bgsave(background save),该过程是异步的不阻断客户请求,而且过程中有新的变化还会同步(Copy-On-Write),redis默认使用bgsave
总结一下:bgsave肯定在性能上优于save,因为不阻塞请求,但是新开一个进程肯定要耗费更多资源。
RDB方案有一个致命的问题,小李需要触发一定条件才回去做快照工作,比如十分钟做一次,但在这十分钟内保洁来了把柜子清掉了(相当于redis重启),这十分种内新增的资料小李还没来得及记录就丢了,而且再也找不回来了。
redis:
与其对应,rdb存储最大的问题就是容易造成数据丢失,比如新数据还没来得及做快照,redis就重启了,那么这些新增的数据就丢了。
AOF
为了解决文件丢失的问题,公司提出一个新方案,名曰AOF(append-only-file),就是让柜子管理员小张柜子里放一张纸,每次收到员工的存储请求,处理完就把请求内容用记录到纸上,并在恰当的时机把记录转到仓库(落盘)。
漫画理解redis持久化和主从复制
文章图片

那么什么是恰当的时机呐?公司提出三种方案:
PlanA.每次有存储请求就记录一次。
PlanB.每隔一秒记录一次。
PlanC.公司下发的命令就记录一次。
小张可以根据公司要求切换方案。
有了这份AOF文件,当保洁清理掉所有柜子文件后,只要按照AOF文件的记录一条一条执行就会恢复柜子原来的样子。
再看一眼AOF的记录
漫画理解redis持久化和主从复制
文章图片

第一条和第四五条分别是01格存了A,B,C,这样显得文件有太多没用的指令,因为最终目的是为了恢复数据,所以只要存第五条:“在01格存储了C”即可,前两条数据多余,而且会造成文件大,读写时间慢,恢复数据也慢。
这时候本分案另一个闲置人员小李登场了,他负责整理AOF文件,也就是重写,结果如下:
漫画理解redis持久化和主从复制
文章图片
redis:
以上过程就是redis的AOF,开启AOF持久化方式,需要修改配置
appendonly yes # 开启AOF appendfsync always # 每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。 appendfsync everysec# 每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。 appendfsync no # 从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。

其中appendfsync三选一,分别对应实时同步,每秒同步,操作系统同步三种存储方式,安全性从高到低,效率从低到高,默认是everysec。
开启之后执行一条指令
set pq 217

查看AOF文件
漫画理解redis持久化和主从复制
文章图片

里面的内容如下
漫画理解redis持久化和主从复制
文章图片

其实就是记录每条命令。
上例中小李的工作就是AOF重写,目的就是删除多余指令,redis是通过新fork一个线程来做重写的,可以通过配置auto-aof-rewrite-参数来调整重写的策略。
对比一下: RDB方式不安全容易丢失数据,AOF比较安全,但是文件大、一条条命令执行来恢复数据时间长(即使有重写也达不到rdb的效果)
混合持久化
RDB、AOF两种方案各有优缺点,于是很快就有人发现了,为什么不两种方式混合使用呐。
方案如下:
小李再做重写的时候看一眼AOF文件,比如当前行是10行,记住,然后新做一个文件,存储RDB格式的快照数据,在这期间如果有新的指令来了小张会存入原AOF文件,小李做完快照看一眼原AOF文件,比如到了12行,代表这期间新增了两行,那么把这两行剪切下来粘到新文件中,最后用新的文件替换并覆盖原AOF文件(最终的AOF文件两种格式混搭),恢复时候先恢复RDB部分,再执行少量的AOF命令,很快就恢复了数据而且还保证了安全。
漫画理解redis持久化和主从复制
文章图片
redis:
使用redis很少使用 RDB方式来进行持久化,因为会丢失大量数据。通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB来说要非常慢,所以使用混合持久化方式最好,开启混合持久化需要配置(默认是开启的)
aof-use-rdb-preamble yes # 开启混合持久化

从上面的例子可以看出混合持久化是基于AOF的重写(只是使用RDB技术),所以AOF必须开启(并且配置重写策略),重写后的AOF一般如下
漫画理解redis持久化和主从复制
文章图片

开启之后的好处上面也说了,安全的同时又重启快。
主从复制
公司不断壮大,用柜子的人也越来越多,而且大部分是为了查看,柜子倒是够用,但小张及其疲惫,为了减轻小张的压力,公司决定多上几个柜子,每个柜子都配置不同的管理员,为了资源统一,每个柜子存放的文件一模一样,大家需要存资料统一找小张,需要读资料去找其它柜子的管理员即可,这样大大减轻了小张的压力。
按以上方案,小张是主,其它管理员是从,这就是一个典型的主从结构。
漫画理解redis持久化和主从复制
文章图片

那么问题来了,要保证每个柜子存放的文件一模一样,怎么实现主从复制?
注意:上面介绍了持久化的方案,但是在主从复制时不一定开启了持久化,资料库可能不存在AOF和RDB文件,所以不能依靠持久化来解决
方案如下:
比如下面的某个柜子管理员叫小美
1.小美告诉小张我要同步数据
2.小张通知下属小李做一个RDB快照文件(bgsave),与此同时小张继续接受请求,并记录请求的内容(类似AOF,但不存入资料仓库)
3.小李做完RDB快照也不用存库,直接发给小美
4.小美清空柜子,按RDB快照重新布置柜子
5.小张把这段时间新增的请求指令发给小美
6.小美按新增命令修改柜子数据,此时两个柜子数据一致
7.之后小张接受新命令时自己存完,再告诉小美存一下,此时两个柜子实时同步
漫画理解redis持久化和主从复制
文章图片
redis:
redis实现主从复制就是按照上面例子的思路来的:
master收到同步命令后,会通过bgsave异步生成最新的rdb快照文件,这期间期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。rdb完成以后,master会把这份rdb发送给slave,slave会把接收到的数据加载到内存中。然后,master再将之前缓存在内存中的指令集发送给slave。
漫画理解redis持久化和主从复制
文章图片

主从复制(断点续传)
还是上面的例子,小美的柜子通过主从复制一直保持和小张一致,但是问题出现了:
小美有时候要去上厕所(从节点掉了),那么这段时间小张柜子里的新增的数据就没办法同步到小美的柜子里了。
为了解决这个问题,小张每次接收到新的写命令都会记录在一个小本上存到柜子里(内存),而且只保留最近的几条记录,比如10条(不能存太多,一是查找费劲,二是占用柜子空间),其它都删掉,并且每条记录都有序号01,02,03...,再同步命令的时候,小美也记录下自己当前的序号(偏移量),这样小美上厕所回来后只要告诉小张序号,小张把序号后面的命令发给小美,就完成了断点续传。
漫画理解redis持久化和主从复制
文章图片

如果小美身体不舒服,去厕所去了很久,回来之后把序号报给小张小张发现这个序号早就不在自己的小本本上了,这时候就按照之前的初始复制方案整体复制一遍即可。
redis:
断点续传基本就是如上例这么个方式,master会在其内存中开辟缓存队列,缓存最近一段时间的指令,master和它所有的slave都维护了复制的数据下标offset,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
漫画理解redis持久化和主从复制
文章图片

总结 其实持久化也好,主从复制也好,redis的诉求都一样,就是不阻塞用户请求,在jvm垃圾回收也有类似的诉求,即尽量避免stw(stop-the-world)。
而解决的方案简单概括:增量更新,也就是对于同一数据后台线程一边记,用户请求一边改,在过程中记录改了什么,最后把结果按改动内容调整一下就完成了最终的数据备份。
over~新人求赞!

    推荐阅读