11月30日云栖社区在线培训,云栖社区请来了阿里云资深开发工程师夏德军为大家带来阿里云redis内核优化的分享。本文从两大方面介绍阿里云redis服务,一是redis内核支持基于时间点的备份恢复,一是redis基于aof日志的增量同步机制设计,并分别通过假设场景,详细的分析了备份恢复流程和aof psync流程。一起来了解下吧。
<a href="https://yq.aliyun.com/edu/lesson/play/420">直播视频回顾</a>
<a></a>
redis内核支持基于时间点的备份恢复
redis内存数据库,须有一种机制能够把内存中的数据持久化到硬盘上,再将硬盘中数据备份到备份系统中,才能去做恢复。redis原生的持久化机制包括rdb持久化和aof持久化两种。
<b>rdb</b><b>持久化</b>
rdb持久化触发方式有两种:
手动触发:执行bgsave命令;
自动触发:配置save选项,在指定时间内发生指定次数的key修改,自动进行后台rdb save。
rdb持久化流程如下:

在做rdb save时需要fork一个子进程,每次rdb save生成一个对应时间点的内存快照文件。
<b>aof</b><b>持久化</b>
<b></b>
配置appendonly选项,可以动态开关;
和rdb持久化不太一样的是,每一次的写操作命令都会追加到aof文件中,根据配置的appendfsync选项不同,会有不同aof文件刷新策略;
redis通过aof rewrite的方式来“紧凑”aof,解决持续追加导致aof文件过大的问题。
<b>当前备份恢复方式</b>
现在redis内核能够支持的备份方式有两种:
1. 备份rdb:执行bgsave命令 -> fork子进程 -> 生成rdb文件 -> 备份rdb文件;
2. 备份aof:根据aof大小判断是否需要先做一次aof rewrite,备份aof,aof rewrite同样需要fork子进程。
两种方式都是备份一个全量数据文件,通常不会选择备份aof,rdb格式更为紧凑,备份开销更小。
恢复时,创建实例,从备份系统拉取全量的aof或rdb文件到实例数据目录,恢复数据。
当前内核备份恢复方式只能做全量数据的备份和恢复,fork子进程生成全量快照的方式开销比较大。
<b>应用场景举例</b>
如果能够不管几点上线,都能恢复redis数据到那个时间点,场景中的问题将不再存在。
<b>redis</b><b>基于时间点的备份恢复</b>
我们的目标就是要恢复数据到任意时间点,我们做了基于aof改造的增量日志,禁用aof rewrite,日志按照配置大小自动切分。除了每个命令执行的历史日志要保存下来,还需要添加一些额外的信息,比如:
根据系统时间戳命名aof,aof中除了包含原有的redis协议格式的命令(oplog),每个oplog还对应了一个header;
header中包含多个字段,其中timestamp记录了命令执行时的时间戳,基于任意时间点恢复时就依赖于该信息;
server_id、opid和dbid是为后面讲的主从同步优化所设计,reserved为保留字段;
oplog header以二进制的形式用redis命令封装,保持aof协议格式不变。
<b>aof</b><b>日志管理</b>
aof会按固定大小做切分,我们需要将aof日志进行管理,主要有以下三点:
1. 使用aof index索引文件记录redis当前维护的aof文件;
2. 使用专门的清理命令删除aof,同时维护aof index文件的正确性;
3. bgsave完成时需要记录rdb文件对应的aof日志名和文件写入偏移,这个信息保存在rdb index文件中。
<b>本地定时</b><b>bgsave</b>
我们也需要本地定时bgsave机制,aof会越变越大,仍然需要某种方式来“紧凑”aof日志,比如aof rewrite方式复合式进程重写aof日志,redis实例在本地也需要保存一份持久化的全量数据,重启时从aof日志恢复数据是不现实的。
对此,我们引入cron bgsave,当aof日志的增长量满足一定条件时,触发bgsave,生成rdb文件,该rdb文件对应的时间点之前的aof日志在本地都是不需要的,只要已经上传到备份系统,即可删除。
<b>备份恢复流程</b>
时间点备份恢复的流程如图所示,左侧假如有一个redis-server实例,追加写aof6文件,接着实时备份aof日志,上传到备份空间中,同时也有定时全量备份,每一个rdb文件会对应一个rdb文件写入偏移,每个aof文件会有一个起始时间;右侧指定要恢复的时间点,redis顺序加载rdb、aof到指定地点。
备份和原来全量备份的区别在于,需要实时持续地把redis生成的aof增量日志不断的备份下来,我们也要有删除的机制,比如保存一个星期或一个月的情况。
redis基于aof日志的增量同步机制设计
redis原生同步机制主要有两种,全量同步和增量同步。
<b>全量同步</b>
全量同步代价非常大,全量同步意味着有一个空的redis实例挂上来后,master需要做一次bgsave,还需要网络传输rdb文件,发送给slave,大量占用主库带宽、cpu资源,大内存时fork会导致redis hang住,虽然fork是写实复制,但需要copy页表,如果内存比较大,页表也会比较大。
<b>增量同步</b>
redis在2.8中引入了增量同步机制,是为了解决网络偶尔的抖动导致需要全量同步的问题。
增量同步机制实现比较简单,master和slave之间维护一个一致的同步offset,此外,master还在内存里面维护了一个同步buffer,保存了最近一段时间内的更新命令日志,当slave断链重连时,会根据自己的同步偏移从master的同步buffer直接拉取数据,完成同步。
理想情况下,这个流程同步很快,代价很低,但是当网络断开时间较长时,或主库写入非常大时,同步buffer会将历史数据覆盖掉,仍然需要全量同步。而且,现有的增量同步机制,也没有解决一主多从场景下,主从切换,其他从需要从新主全量同步的问题。
有不少使用redis的业务的重要性很高,譬如说金融业务,都有跨机房,甚至跨地域灾备的需求,而跨机房或跨地域的网络质量往往很难保证。
假设有一天,redis业务的杭州主机房和深圳的备机房之间的光纤被人挖断了,网络中断数小时,恢复后,深圳机房所有的备实例同时发起全量同步,导致的后果很可能是:主库所在机器带宽、cpu打满,甚至有些机器会直接oom,而备又还没有完成同步,后果可能是灾难性的,对此,我们该如何处理呢?
<b>基于</b><b>aof</b><b>日志的增量同步(</b><b>aof psync</b><b>)</b>
由于现有机制存在的问题,以及真实场景中业务需求的推动,我们想到不依赖于具体内存的buffer,用增量日志做增量同步,那么,基于aof日志的增量同步是怎么实现的呢?具体原理解释如下:
当主从同步断开,需要拉取增量时,会先选择尝试基于同步偏移从主库同步buffer获取数据,如果同步buffer缺少增量数据,会开始从aof日志获取增量;
aof psync以aof日志中的opid作为同步基准,slave继承从主库同步过来的opid,并记录在自己的aof日志中;
使用opid作为同步基准是因为对于带过期时间的命令,redis写aof和写同步buffer的字节序列不一样,redis在写aof时会转换设置过期时间的命令为pexpireat;
aof日志中的dbid,决定了aof中的每条命令写入的db是哪个。aof psync同步完成后,会同步master和slave间的同步偏移。
<b>aof psync</b><b>流程</b>
1. master收到slave的aof psync请求后,会使用后台线程来查找offset,避免server hang住;
2. 根据slave发送的opid查找到起始的aof文件后会异步的通知主线程;
3. 主线程会异步发送一个或多个aof文件给slave,发送完成后,需要同步aof buffer;
4. 如果slave给定的opid在master的aof中不存在(aof可能被异常删除),会发起全量同步。
阿里云redis服务
与其它redis对比,阿里云redis服务具备高可靠、高性能、高扩展、易用的特点。