Redis知识整合(二):数据的持久化

如果重启redis服务,或者重启redis所在的机器,那么对于将数据全部保存在内存中的redis而言,这两个操作无疑会让其数据全部丢失,此时如果我们没有做更多的容错方案,比如横向做集群、纵向做主从,此时就会造成缓存雪崩,所以我们或许需要给redis内的数据做个持久化,让其在恢复时读取持久化数据,恢复原始数据,redis有两种持久化方式,分别是RDBAOF,RDB的实时性不如AOF,AOF恢复速度不如RDB

一、RDB

1.1:概览

RDB持久化会将当前redis进程内的数据生成快照保存到硬盘中(rdb文件),相关指令为bgsave

我们可以通过配置redis.conf使其自动持久化:

1
2
save <seconds> <changes> #表示seconds秒内存在changes次修改时,自动触发bgsave命令
dbfilename dump.rdb #这里指定保存的文件名

在没有开启AOF的情况下shutdown掉redis,也会自动触发一次bgsave;

还有一种情况会自动触发bgsave,从节点发起全量复制时,主节点会自动触发bgsave并将生成的rdb文件发送给从节点。

1.2:过程

bgsave过程如下:

图1-1

这是个重操作,好在不影响主进程,即便如此,bgsave也不能频繁执行,这也是rdb持久化方式无法做到秒级持久化的原因,采用这种方式持久化就要承担部分数据丢失的风险。

二、AOF

2.1:概览

针对rdb不适合实时持久化的问题,redis提供了aof持久化方案来解决。

开启aof持久化的redis.conf配置:

1
2
appendonly yes #这一项置为true(默认false)
appendfilename "appendonly.aof" #这里指定保存的文件名

2.2:流程

aof的大体流程如下:

图2-1

主要分为同步aof文件和定期刷新aof文件两大块,定期刷新文件时保证数据不丢失的做法是增量双写(1-1)和旧数据同步(1-2),其中1-2中批量同步新aof文件时,可以通过aof-rewrite-incremental-fsync来控制每次同步的数据大小,默认32M。

aof重写文件的触发时机主要由以下两种方式控制:

  • 手动调用bgrewriteaof命令
  • 根据auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage参数控制自动触发时机

auto-aof-rewrite-min-size:运行aof重写时aof文件的最小体积,默认64M

auto-aof-rewrite-percentage:当前aof文件的增量大小和上一次重写后aof文件大小的比值

根据以上描述,重写aof文件的自动触发条件为:

图2-2

三、重启加载

不管是aof还是rdb,都是为了让redis重启时可以恢复原来的数据,整个过程如下:

图3-1

可以看到,当rdb和aof同时启用时,优先使用aof.

四、优化

4.1:fork操作

无论是aof还是rdb,涉及到的fork操作都是重操作,其耗时取决于redis主进程中的内存大小(每次fork都需要复制主进程的内存页表),主进程内存每增加1GB将拖慢fork操作20ms,可以在info stats统计中查看latest_fork_usec指标获取最近一次fork操作的耗时。

改善fork耗时:

  • 优先使用物理机或高效支持fork操作的虚拟化技术,避免使用Xen
  • 控制redis的最大可用内存(因为fork耗时跟内存量息息相关),生产环境建议redis实例内存控制在10G内
  • 合理配置Linux内存分配策,避免物理内存不足导致的fork失败
  • 降低fork操作的频率,如适当放宽aof的触发时机

4.2:子进程开销监控&优化

通过前面的了解,我们知道redis中的子进程主要负责aof和rdb文件的重写,它的运行过程主要涉及CPU、内存、磁盘三部分的消耗:

4.2.1:CPU

开销:子进程将进程内的数据分批次写入磁盘属于CPU密集型操作(对单核CPU的利用率可达90%)

优化:主要以减少资源竞争为优化点,比如:

  • redis为CPU密集型服务,所以不要绑定单核CPU操作,因为子进程非常耗CPU,会严重影响父进程(单核上下文竞争)
  • 要避免和其他CPU密集型服务部署在一起,避免过渡竞争CPU
  • 多实例部署redis时,应保证同一时刻只有一个子进程在运行

4.2.2:硬盘

开销:子进程主要负责将aof或rdb文件写入硬盘,也就造成了硬盘写入压力(可通过系统工具sar、iostat、iotop等分析出重写期间硬盘的负载情况)

优化:

  • 不要和其他高硬盘负载的服务部署在一起(比如存储服务、消息队列等)
  • aof重写会消耗大量的硬盘IO,所以在重写期间应减少aof缓冲区往aof里fsync的操作(设置no-appendfsync-no-rewrite为true即可,表示在重写期间不做fsync操作,但这样也可能会丢失整个aof重写期间的数据)
  • 在开启aof且redis处于高流量写入场景时,若使用普通机械硬盘,则在aof同步硬盘时会发生瓶颈
  • 对于单机多实例的部署,可以配置不同实例分盘存储aof,分摊硬盘写入压力

4.2.3:AOF追加阻塞

通过图2-1我们知道,被写入aof缓冲区的数据在everysec策略下会每隔1s调用系统的fsync写一次磁盘,既然是一次调用,那必然存在耗时,fsync是通过delay的方式定期触发的,每次redis主线程在发起fsync调用前都会判断上次fsync的时间,如果耗时超出2s,redis主线程便会陷入阻塞,等待上次fsync执行完,所以aof理论上最多会丢失2s的数据(通过info Persistence中的aof_delayed_fsync可以查看aof同步任务是否发生了delay)。

避免这种情况发生的办法就是优化硬盘(参考4.2.2)