你的位置:首页 > Java教程

[Java教程]第十章 Redis持久化


注:本文主要参考自《Redis设计与实现》

1、Redis两种持久化方式

  • RDB
    • 执行机制:快照,直接将databases中的key-value的二进制形式存储在了rdb文件中
    • 优点:性能较高(因为是快照,且执行频率比aof低,而且rdb文件中直接存储的是key-values的二进制形式,对于恢复数据也快)
    • 缺点:在save配置条件之间若发生宕机,此间的数据会丢失
  • AOF
    • 执行机制:将对数据的每一条修改命令追加到aof文件
    • 优点:数据不容易丢失
    • 缺点:性能较低(每一条修改操作都要追加到aof文件,执行频率较RDB要高,而且aof文件中存储的是命令,对于恢复数据来讲需要逐行执行命令,所以恢复慢)

 

2、RDB

实际中使用的配置(在redis.conf)

#发生以下三种的任何一种都会将数据库的缓存内容写入到rdb文件中去(写入的方式是bgsave)#若将下述的三条命令都注释掉,则禁止使用rdbsave 900 1   #900s后至少有一个key发生了变化save 300 10   #300s后至少有10个key发生了变化save 60 10000  #60s后至少有10000个key发生了变化#当后台RDB进程导出快照(一部分的key-value)到rdb文件这个过程出错时(即最后一次的后台保存失败时),#redis主进程是否还接受向数据库写数据#该种方式会让用户知道在数据持久化到硬盘时出错了(相当于一种监控);#如果安装了很好的redis持久化监控,可设置为"no"stop-writes-on-bgsave-error yes#使用LZF压缩字符串,然后写到rdb文件中去#如果希望RDB进程节省一点CPU时间,设置为no,但是可能最后的rdb文件会很大rdbcompression yes#在redis重启后,从rdb文件向内存写数据之前,是否先检测该rdb文件是否损坏(根据rdb文件中的校验和check_sum)rdbchecksum yes#设置rdb文件名dbfilename dump.rdb#设置rdb文件的存储目录dir ./

说明

  • 具体每一项配置的详细说明看注释

注意

  • 对于save命令而言,配置了该命令,后台是以bgsave来执行的
    • bgsave:Redis主进程进行数据读写操作,RDB子进程进行数据的持久化操作,在进行持久化操作时,不阻塞主进程的读写操作
  • 以上三条save命令只要发生任一条,bgsave命令都会发生,这就有两个问题,假设60s内有10000个key发生了改变(写入、删除、更新),那么是否会立即进行持久化呢?在这次持久化之后,假设又过了240s,而在此期间没有任何的key的改变操作,此时是否要发生一次持久化(因为满足300s发生了10个key的改变,这里是改变了10000个key)?
    • 不会立即进行持久化:redis默认每隔100ms使用serverCron函数检查一次save配置的条件是否满足,满足则进行bgsave,这样的话,如果在100ms内,我已经满足了bgsave的条件,那么我真正执行bgsave的时候也要等到serverCron执行过来的时候
    • 不会再发生持久化:redis有两个参数dirty(记录上一次bgsave之后的key的修改数,上边的在240s内例子就是0)和lastsave(上一次成功执行bgsave命令的时间),配置中的每一个save配置的修改数指的就是dirty,而每一个时间段就是以lastsave为起点计算的。
  • 注释掉所有的save命令,RDB将不起作用
  • rdbcompression yes:配置成这样是不是每一个字符串在存储到rdb文件中时,都要进行一次压缩操作?
    • 不是:设置为yes之后,只有当字符串的长度大于等于21个字节时,才会进行压缩
  • rdbchecksum yes:这个校验和存储在哪里?为什么通过比对校验和可以判断文件是否损坏?
    • 校验和(check_sum)存储在RDB文件的最后八个字节中(详细的RDB文件结构,查看《Redis这基于实现》"第10章 RDB持久化"),简单的RDB文件结构如下:
      • RDB文件开头的前五个字节"REDIS"是判断一个文件是不是RDB文件的标准(类似于class文件中的"魔数")
      • 接下来的4个字节:RDB文件版本号(db_version)
      • databases(注意是复数):这里存放各个库redisDb中存储的key-value信息(是整个数据持久化和恢复的核心)
      • EOF(1个字节):RDB文件正文的结束
      • check_sum(8个字节):检验和,该值是根据前边四部分值算出来的,在持久化的时候将该值算出来并写入rdb文件的末尾;在根据rdb文件恢复数据的时候,再根据rdb文件中的前边四部分值计算出一个校验和,然后与当前rdb文件中的check_sum(即后八个字节)的内容进行比对,如果一样,说明没损坏,如果不一样,说明前四部分有数据损坏(即该文件损坏)
  • 在Redis服务器启动时,redis会自动检测是否有rdb文件(前提是没有aof的时候),如果有,则根据rdb文件恢复数据,此时在恢复数据完成之前,会阻塞客户端对redis的读写操作

 

3、AOF

实际中使用的配置(在redis.conf)

# 是否打开aof日志功能(appendonly yes)appendonly no# aof文件的存放路径与文件名称# appendfilename appendonly.aof#每一个命令,都立即同步到aof文件中去(很安全,但是速度慢,因为每一个命令都会进行一次磁盘操作)# appendfsync always#每秒将数据写一次到aof文件appendfsync everysec#将写入工作交给操作系统,由操作系统来判断缓冲区大小,统一写到aof文件(速度快,但是同步频率低,容易丢数据) # appendfsync no# 在RDB持久化数据的时候,此时的aof操作是否停止,若为yes则停止# 在停止的这段时间内,执行的命令会写入内存队列,等RDB持久化完成后,统一将这些命令写入aof文件# 该参数的配置是考虑到RDB持久化执行的频率低,但是执行的时间长,而AOF执行的频率高,执行的时间短,# 若同时执行两个子进程(RDB子进程、AOF子进程)效率会低(两个子进程都是磁盘读写)# 但是若改为yes可能造成的后果是,由于RDB持久化执行时间长,在这段时间内有很多命令写入了内存队列,# 最后导致队列放不下,这样AOF写入到AOF文件中的命令可能就少了很多# 在恢复数据的时候,根据aof文件恢复就会丢很多数据# 所以,选择no就好no-appendfsync-on-rewrite no# AOF重写:把内存中的数据逆化成命令,然后将这些命令重新写入aof文件# 重写的目的:假设在我们在内存中对同一个key进行了100次操作,最后该key的value是100,# 那么在aof中就会存在100条命令日志,这样的话,有两个缺点:# 1)AOF文件过大,占据硬盘空间 2)根据AOF文件恢复数据极慢(需要执行100条命令)# 如果我们将内存中的该key逆化成"set key 100",然后写入aof文件,# 那么aof文件的大小会大幅度减少,而且根据aof文件恢复数据很快(只需要执行1条命令)# 注意:下边两个约束都要满足的条件下,才会发生aof重写;# 假设没有第二个,那么在aof的前期,只要稍微添加一些数据,就发生aof重写# 当aof的增长的百分比是原来的100%(即是原来大小的2倍,例如原来是100m,下一次重写是当aof文件是200m的时候),AOF重写auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb  #AOF重写仅发生在当aof文件大于64m时

说明:

  • 具体每一项配置的详细说明看注释

注意:

  • 每一个客户端命令在执行时都会直接将命令写入AOF缓冲区
    • appendfsync always:每一个命令进入缓冲区后,都会立即再从缓冲区追加到AOF文件中
    • appendfsync everysec:每一秒后将缓冲区中的所有命令追加到AOF文件中
    • appendfsync no:每一个命令进入缓冲区后,由操作系统来判断什么时候(主要是缓冲区快满的时候)将缓冲区中的所有命令追加到AOF文件中
  • aof重写需要满足配置文件中的两个条件
  • aof重写采用后台子进程执行
    • aof的重写会进行大量的写入操作,如果用单线程来做这个事儿,就会长时间阻塞主进程(redis是单线程),这个时候客户端的读写就会失效
    • 采用aof后台重写进程造成的问题:
      • 如果在后台进程重写期间,有新的命令对数据库进行了读写,新的aof文件就与数据库的存储内容不同了。(注意:在后台进程重写期间,对数据库的读写操作还会进入aof缓冲区,还是会执行aof文件的命令写入,但需要注意的是,虽然这个期间所有的命令还是写入aof文件了,但是这个aof文件会被重写后的新的aof文件所替换,新的aof文件可没有这些命令,那么如果在下次重写发生之前发生宕机,采用aof恢复数据库的时候,那就丢失了很多命令了)
      • 解决方案:设置一个aof重写缓冲区,仅仅用于在后台进程重写期间,将发生的数据库读写命令写入到重写缓冲区中(当然,此时的服务器进程除了将发生的数据库读写命令写入到重写缓冲区中,还会写入到aof缓冲区,来保证正常的aof操作),之后当重写子进程完成重写后,向服务器主进程发送一个信号,此时服务器主进程将aof重写缓冲区中的命令追加到新的aof文件中去,用新的aof文件替换掉旧的aof文件。
    • 注意:
      • 在后台进程重写期间,将发生的数据库读写命令写入到重写缓冲区中时,为什么还要将这些命令写入到aof缓冲区(因为这些命令会写入旧的aof文件)?
        • 如果能百分之百保证在最后的主进程用新的aof文件替换了旧的aof文件(就是在这之前不宕机),那么写入到aof缓冲区没用(因为会被覆盖),但是如果在这之前宕机,那么我们的aof文件还是旧的aof文件,这时候命令写入到aof缓冲区就可以保证该aof文件尽可能多的保存命令,将来用于恢复数据库最多丢失的也就是1s的数据。(appendfsync everysec)
      • 在"服务器主进程将aof重写缓冲区中的命令追加到新的aof文件中去"这段操作期间,如果有新的客户端读写命令,都将被阻塞(因为是主进程在做上述操作)。这也是整个aof重写过程中唯一被阻塞的部分。

总结:

  • 如果既配置了RDB,又配置了AOF,则在进行数据持久化的时候,都会进行,但是在根据文件恢复数据的时候,以AOF文件为准,RDB文件作废
    • 需要注意:数据的恢复是阻塞操作(此间所到来的任何客户端读写请求都失效)
  • bgsave和bgrewriteaof(后台aof重写)这两个命令不可以同时发生
    • 如果bgsave在执行,此间到来的bgrewriteaof在bgsave执行之后,再执行
    • 如果bgrewriteaof在执行,此间到来的bgsave丢弃
  • RDB和AOF可以同时配置,但是最后还原数据库的时候是以aof文件来还原的