zoukankan      html  css  js  c++  java
  • Redis10:RDB持久化与AOF持久化

    RDB持久化

    redis中的内容可以通过RDB持久化保存在磁盘中,RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成该文件对应的redis数据库。RDB持久化可以通过命令手动执行,也可以根据配置定期执行。

    RDB文件的创建与载入

    save命令和bgsave命令都可以生成RDB文件,前者会造成服务器进程的阻塞,在此期间服务器不能处理任何请求,后者不会造成阻塞,会派生出一个子进程来负责创建RDB文件。

    在bgsave命令执行期间,客户端发送的save命令会被服务器拒绝,这是服务器在避免父进程和子进程同时进行磁盘写入产生竞争条件。且在bgsave命令执行期间,客户端发送的bgsave命令也会被拒绝,同样是为了避免产生竞争条件。

    bgsave命令和bgrewriteaof两个命令不能同时执行:如果在执行bgsave的时候执行了bgrewriteaof,那么该命令会被延迟到bgsave执行完毕后执行;如果在执行bgrewriteaof的过程中客户端发送执行bgsave的请求,那么服务器会拒绝执行bgsave命令。这是因为服务器为了性能考虑,避免两个写入磁盘操作的命令执行。

    RDB文件的载入是在服务器启动时自动执行的,redis没有专门用于载入RDB文件的命令,在载入过程中服务器会一直处于阻塞状态直到载入完成。因为AOF文件的更新频率较高,所以如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态,如果没有开启AOF持久化功能,服务器才会使用RDB文件来还原数据库状态。

    自动保存RDB文件

    服务器可以通过在redis.conf文件中加入如下配置来设置自动保存RDB:

    save 900 1
    save 300 10
    save 60 10000
    

    表示服务器只要满足如下三个条件中的任意一个就会自动执行bgsave命令:

    1、服务器在900秒之内对数据库进行了至少1次修改

    2、服务器在300秒之内对数据库进行了至少10次修改

    3、服务器在60秒之内对数据库进行了至少10000次修改

    在redisServer结构中有一个saveparam类型的数组,该类型包含了save的保存条件,对应上面的两个参数:

    struct saveparam{
    	//秒数
    	time_t seconds;
    	//修改数
    	int changes;
    };
    

    在redisServer结构中维护着两个相关的属性:

    struct redisServer{
    	//修改计数器
    	long long dirty;
    	//上一次执行保存的时间
    	time_t lastsave;
    }
    

    当服务器每对数据库修改一次,dirty计数器就会加1。lastsave属性记录了服务器上次执行保存操作的日期时间戳。

    在redis中有一个服务器周期性操作函数,它默认每隔100毫秒就会执行一次,它的其中一个功能就是检查save选项设置的条件是否满足。具体方法是遍历saveparam数组,相当于检查每一个save设置,根据lastsave计算得到距离上次保存操作的秒数,如果该数值大于save设置中的seconds,且修改器大于save设置中的changes,就执行besave命令。

    RDB文件结构

    RDB文件结构大致如下:

    REDIS是固定的5个字节,是RDB文件的标识。

    db_version长4个字节,代表一个整数,它是RDB文件的版本号,这里介绍的是第6版RDB文件的结构,也就是0006

    databases部分包括零个或多个数据库,如果一个数据库为空那么它就不会出现在这里。

    EOF为1字节,标志着RDB文件正文内容的结束。

    check_sum是一个8字节长的无符号整数,保存着一个校验和,它是程序通过计算签名四个部分的内容得出的,用于判断RDB文件是否完整。

    databases部分

    如果服务器中的0号和3号数据库非空,那么服务器的RDB文件大致如下:

    每个非空数据库的结构如下:

    SELECTDB是一个1字节的常量,它代表一个新的数据库部分的开始。

    db_number代表数据库号。

    key_value_pairs保存了数据库中的所有键值对。

    key_value_pairs部分

    这部分可能有两种结构,第一种是不带过期时间的键值对,第二种是带有过期时间的键值对。

    不带过期时间的键值对结构如下:

    TYPE记录了value的类型,是下面几种常量的其中一个:

    每一种类型都代表了一种底层编码。

    key总是一个字符串对象,value的类型根据TYPE的不同来解释。

    带有过期时间的键值对结构如下:

    EXPIRETIME_MS是一个长度为1字节的常量,它告知程序接下来将读入一个以毫秒为时间单位的过期时间。

    ms是一个8字节长的带符号整数,记录着一个以毫秒位单位的时间戳。

    value部分

    value部分的类型不同,保存方法也不同。

    字符串对象

    TYPE的值如果后缀为STRING,那么value保存的就是字符串对象。

    如果字符串对象的编码是REDIS_ENCODING_INT,说明对象中保存的是长度不超过32位的整数,此时应该这样表示:

    REDIS_RDB_ENC_INT8代表编码方式为使用8位来保存整数,同样的有INT16、INT32。

    如果字符串的编码是REDIS_ENCODING_RAW,说明对象保存的是字符串值,如果该字符串长度小于等于20字节,那么字符串会被原样保存;如果字符串长度大于20字节,会被压缩之后保存。(如果redis关闭了压缩功能,那么就只能无压缩)

    无压缩的字符串分为两个部分保存,一个是字符串长度,一个是字符串本身:

    压缩的字符串保存形式如下:

    第一个字段是一个常量,代表该字符串已经被LZF算法压缩过了。

    compressed_len是字符串被压缩后的长度,origin_len是字符串原来的长度,compressed_string是被压缩后的字符串。

    列表对象

    TYPE的值如果后缀为LIST,那么value对应的就是一个REDIS_ENCODING_LINKEDLIST编码的列表对象,这种对象的结构如下:

    第一个字段代表列表的长度,后面代表列表项,每一个列表项都是一个字符串对象。如下面就是一个包含三个元素的列表:

    集合对象

    TYPE的值如果后缀是SET,那么value对应的就是一个REDIS_ENCODING_HT编码的集合对象,这种对象的结构如下:

    第一个字段代表集合的大小,后面是各元素,每一个元素都是字符串对象,这种表示方式和列表很像。

    哈希表对象

    TYPE的值如果后缀是HASH,那么value对应的就是一个REDIS_ENCODING_HT编码的哈希对象,这种对象的结构如下:

    结构中的每个键值对都紧挨着,如下面就是一个包含两个键值对的哈希表:

    有序集合对象

    TYPE的值如果后缀是ZSET,那么value就是一个REDIS_ENCODING_SKIPLIST编码的有序集合对象,这种对象结构如下:

    每个元素的成员和分值都紧挨在一起,如下面就是带有两个元素的有序集合:

    INTSET编码的集合

    如果TYPE的值后缀为INTSET,那么value保存的就是整数集合对象,redis会先把整数集合转换为字符串对象,然后将字符串对象保存在RDB文件中。

    ZIPLIST编码的列表、哈希表、有序集合

    如果TYPE的值后缀为ZIPLIST,那么value保存的就是一个压缩列表对象,RDB文件会先把压缩列表转换成字符串对象,然后把字符串对象保存在RDB文件中。列表、哈希、有序集合三种类型都可以使用压缩列表在存储,所以在载入RDB文件的过程中,会根据不同的类型还原成对应的类型。

    AOF持久化

    AOF持久化通过保存redis服务器执行的写命令来记录数据库状态的,因为redis中的命令请求协议是纯文本协议,所以AOF文件是一个纯文本文件,可以直接打开分析。在AOF文件中不仅有修改服务器的命令,默认还会有select命令来确定具体哪个数据库。

    AOF持久化的过程

    AOF持久化的实现分为三个步骤:命令追加、文件写入、文件同步。

    当AOF持久化功能处于开启状态时,服务器在执行完一个写命令之后,就会以协议的形式将写命令追加到服务器的AOF缓冲区,它是在redisServer类中的一个sds类型的缓冲区。

    服务器每次结束一个事件循环之前,都会调用一个函数检查是否要将缓冲区中的内容写入和保存到AOF文件中,这个函数的行为和配置文件中关于AOF的设置有关,在redis.conf中,可以设置以下三种中的任一种:

    appendfsync always
    appendfsync everysec
    appendfsync no
    

    函数和对应配置产生的不同行为如下:

    这里注意,文件的写入和同步是两个不同的操作,写入时会将数据暂时保存在一个内存缓冲区中,等到缓冲区被占满或者超过指定的期限才会真正写入磁盘中,这种做法提高了效率,但也带来了数据不安全的隐患,系统提供了fsync和fdatasync函数来强制让操作系统将缓冲区中的内容同步到磁盘上。

    appendfsync的设置中,always让数据最安全,但是效率最低;no效率最高,但数据安全性较差;everysec比较折中,它也是默认设置。

    AOF文件的载入和数据还原

    读取AOF文件并还原数据库状态的详细步骤如下:

    1、创建一个不带网络连接的伪客户端,因为redis的命令只能是客户端发送给服务器的,现在要让一个AOF文件作为为客户端来执行其中的写命令。

    2、把AOF文件中的命令逐个取出并执行,直到所有命令都处理完毕。

    AOF重写

    随着服务器运行,AOF文件会越来越大,此时就需要进行AOF重写,新的AOF文件要比原来的文件要小。

    AOF重写的原理很简单,就是读取当前服务器中的数据信息,然后将数据信息尽可能用一条命令来记录,用合并后的一条命令替代原来的多条命令。如对于一个包含六个元素的列表对象,之前可能是经过多次写命令才达到现在的状态,而现在只需要执行一条添加命令即可,当元素数量很多时,会自动执行多条添加指令。

    redis在子进程中执行AOF重写,这样服务器进程就能在重写AOF期间继续处理其他请求,而且子进程会获得服务器进程的数据副本,避免使用锁的前提下保证数据的安全性。

    子进程在进行AOF重写的时候,服务器还需要继续处理命令请求,redis服务器设置了AOF缓冲区和AOF重写缓冲区,每次都要把命令追加到这两个缓冲区,AOF缓冲区中的内容会根据配置写入和同步到现有的AOF文件,而当AOF重写完成后,子进程会向父进程发送一个信号,父进程执行以下工作:

    1、将AOF重写缓冲区中的内容写入新AOF文件中。

    2、将新AOF文件原子地覆盖现有的AOF文件。

    整个AOF重写过程只有信号处理函数执行会对服务器进程造成阻塞,其他时候都不会造成阻塞。

    总结

    快照RDB

    RDB可能丢失数据,可以设置改1次key存一次,改10次,改1万次。RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来(操作系统会维护共享页面和修改后的分离页面),所以在快照持久化期间修改的数据不会被保存,可能丢失数据。

    日志AOF

    AOF不会丢失数据,但是数据多时文件很大,可以设置为改了就存、一秒存一次、不存几种策略。AOF存的是redis的顺序指令序列,长期运行的过程中AOF日志会变得很巨大,此时如果重放AOF日志会非常耗时。此时可以采取AOF重写,AOF重写就是开启一个子进程将内存遍历转换成一系列的redis操作指令序列化一个新的AOF日志文件中,然后再把转换期间的增量AOF日志追加到新的日志文件,然后替换原来的AOF文件即完成了AOF重写。写AOF文件时其实是写到一个内存缓存中然后异步地将脏数据刷新到磁盘,刷新频率一般为1s一次。

    设置持久化的位置

    RDB是遍历整个内存的操作,而AOF会降低redis性能,所以持久化一般发生在从节点(备份节点)进行,它没有来自客户端的压力。

    混合持久化

    redis4.0有一个混合持久化的方法,就是将RDB文件的内容和新增的AOF文件存在一起,这样既克服了AOF恢复慢,又克服了RDB容易丢失数据的缺点,redis重启时先加载RDB,后加载AOF,效率大幅度提升。

  • 相关阅读:
    TCP三次握手四次挥手
    TCP与UDP的区别
    mysql从库设置全局只读,并创建普通账号
    docker安装mysql主从
    mysql 更换数据目录
    Docker 部署 ElasticSearch
    js获取地址栏传参
    PHP-MySQL基本操作
    基于element-tree-table树型表格点击节点请求数据展开树型表格
    拖拽读取文件
  • 原文地址:https://www.cnblogs.com/yinyunmoyi/p/11522419.html
Copyright © 2011-2022 走看看