redis持久化

来源:互联网 发布:加工中心铣槽编程 编辑:程序博客网 时间:2024/06/03 21:07

Redis持久化

redis是内存数据库,所有的数据库状态、键值对都存储在内存中,为了避免数据丢失,可以将数据持久化到磁盘上。redis服务器启动时可以根据持久化文件还原数据库的状态。redis的持久化有两种方式:RDB持久化和AOF持久化。

先来简单比较下这两种持久化的不同,RDB持久化是将内存键值对的映射写入到磁盘,AOF持久化是将对redis库的写命令写入磁盘。redis服务器启动的时候可以根据RDB或者AOF的持久化文件还原数据库,如果是RDB文件,将RDB的键值对映射载入到内存中即可,如果是AOF文件,服务器会产生一个伪客户端(无网络连接的客户端),这个伪客户端执行从AOF文件读取的一系列写命令,最终还原数据库的状态。

如果同时存在RDB文件和AOF文件,服务器将会从AOF文件加载数据库,否则有哪个文件就从哪个文件加载数据库。

RDB持久化

RDB的持久化是将键值对写入到磁盘的过程。通过执行SAVE或者BGSAVE命令,生成RDB文件。

SAVE命令

redis在处理客户端请求时采用了单线程的方式,即所有的客户端请求是通过单线程处理的。SAVE命令会阻塞服务器进程,在RDB文件持久化完成之前,redis服务器将无法处理客户端请求。RDB文件持久化完成之后,服务器可以正常处理客户端的请求。

BGSAVE命令

SAVE命令的缺点是很明显的,它会阻塞服务器,所有客户端请求都得不到处理,用户体验较差。BGSAVE命令通过fork一个子进程来处理RDB文件的持久化,服务器可以继续处理客户端的请求,子进程负责RDB文件的创建工作。

在BGSAVE命令执行期间,客户端发送的SAVE命令和BGSAVE命令会被服务器拒绝。

服务器启动时载入RDB文件期间,会处于阻塞状态,客户端请求将会被挂起。

redis提供了配制项,在条件满足的情况下,自动执行BGSAVE命令。下面3行是redis的默认配制项:

save 900 1save 300 10save 60 10000

这3行配制的意思分别是:在900秒之内对数据库进行了至少1次修改、在300秒之内对数据库进行了至少10次修改、在60秒之内进行了至少10000次修改。满足以上任一条件就会触发BGSAVE命令。

配制文件中的配制项最终会被保存在redisServer结构体的saveparams属性中:

//服务器状态的结构体struct redisServer {  //……  struct saveparam *saveparams;};//BGSAVE自动触发条件的结构体struct saveparam {  //秒数  time_t seconds;  //修改次数  int changes;};

RDB文件结构

下图是RDB的文件结构示意图,RDB文件的开头是5个字节,即”REDIS”。db_version是一个字符串表示的整数,这个值表示了RDB文件的版本号,这个字段长度为4字节。databases包含着0个或者任意多个数据库。EOF长度为1个字节,表示了RDB文件的结束。check_sum是通过REDIS、db_version、databases、EOF计算出来的校验和,长度为8个字节。redis服务器载入RDB文件后会重新计算校验和,与check_sum比较,若相同说明RDB文件没有损坏,否则说明RDB文件已经损坏。

这里写图片描述

图1 RDB文件结构

这里重点研究下databases部分,databases可以包括多个非空数据库,例如包括0和1号两个数据库,数据库的结构参考下图:

这里写图片描述

图2 数据库结构

其中SELECTED是长度为1字节的常量,服务器读到该值时,它将知道接下来读到的是数据库号码db_number。key_value_paris真正保存了数据库的键值对。key_value_paris分为不带过期时间的键值对和带有过期时间的键值对。参考图3和图4

这里写图片描述

图3 不带过期时间的键值对

这里写图片描述

图4 带过期时间的键值对

其中TYPE表示了该键值对value的类型,它是一个常量,取值范围为:

  • REDIS_RDB_TYPE_STRING
  • REDIS_RDB_TYPE_LIST
  • REDIS_RDB_TYPE_SET
  • REDIS_RDB_TYPE_ZSET
  • REDIS_RDB_TYPE_HASH
  • REDIS_RDB_TYPE_LIST_ZIPLIST
  • REDIS_RDB_TYPE_SET_INTSET
  • REDIS_RDB_TYPE_ZSET_ZIPLIST
  • REDIS_RDB_TYPE_HASH_ZIPLIST

服务器读到该值后,知道如何处理value部分。

带过期时间的键值对结构,其中EXPIRETIME_MS是一个长度为1字节的常量,服务器读到该值后知道这个键值对是带过期时间的,接下来读到的8字节长的ms字段即是过期时间,单位为毫秒。

AOF持久化

与RDB持久化不同的是,AOF是通过记录服务器执行的写命令来记录服务器状态的。AOF文件有自己的特定的文件格式来记录写命令。服务器启动时可以载入AOF文件,通过伪客户端执行AOF文件命令,还原数据库状态。

若服务器AOF持久化功能开启后,服务器每执行完一个写命令后,会将该命令以特有的协议格式追加到服务器结构体的aof_buf缓冲区末尾,看下aof_buf的定义:

struct redisServer {  //……  //AOF缓冲区  sds aof_buf;};

redis服务器进程其实是一个事件循环,这个事件负责接受客户端的请求。在每次事件循环的末尾,它都会去调用flushAppendOnlyFile函数,这个函数决定是否将aof_buf缓冲区的内容写入到AOF文件。这个函数的行为是根据配制文件的appendfsync选项的值来决定的,appendfsync有3个可选项:

appendfsync选项的值 flushAppendOnlyFile函数的行为 always 将aof_buf中的内容写入并同步到(写入磁盘)AOF文件 everysec 每隔一秒将aof_buf中的内容写入并且同步到AOF文件 no 将aof_buf缓冲区中的所有内容写入到AOF文件,但是不进行同步操作

这里解释下写入和同步操作:

将数据写入到文件的时候,操作系统通常会将写入数据暂存在一个内存缓冲区(注意和aof_buf区分)中,等到内存缓冲区的空间被填满或者超过一定的时间后才将缓冲区中的数据真正写入到磁盘里面。所以这里的同步操作指的是将内存缓冲区(不是aof_buf缓冲区)写入到磁盘文件。

AOF重写

AOF重写是通过BGREWRITEAOF命令实现的,AOF持久化有一个问题,随着服务器的运行,AOF文件会越来越大。redis提供了AOF文件的重写功能,这个功能可以对一个键的一系列写命令转化成一条命令。

例如如果对键list执行如下一系列命令:

127.0.0.1:6379> rpush list "hello" "world"(integer) 2127.0.0.1:6379> rpush list "redis"(integer) 3127.0.0.1:6379> lpop list"hello"

AOF文件将会记录关于list的这三条命令。AOF文件重写后list的命令只有一条:

rpush list "world" "redis"

这样原来关于list的三条命令就可以精简为一条命令。

AOF重写并不是重写原有的AOF文件,而是通过读取数据库的键值对来实现的。例如上面的键list,重写是读取数据库键list对应的键值对,并将键值对对应的写命令写到磁盘文件。

redis的AOF重写是通过子进程处理的,这样避免了服务器进程处理AOF重写造成客户端的阻塞。

但是这样带来了一个问题,子进程在处理AOF重写时,服务器进程还会继续处理来自客户端的请求,这就会改变数据库的状态,造成数据库状态和新的AOF文件状态不一致。为了解决这个问题,redis引入了AOF重写缓冲区,子进程处理AOF重写过程中,服务器进程处理客户端的写请求时,不仅会将写命令追加到AOF缓冲区(aof_buf),还会将该写命令追加到AOF**重写缓冲区**。这样可以保证:

  • AOF缓冲区(aof_buf)的内容会被定期写入和同步到AOF文件,即当前正常流程不受影响。
  • 从创建子进程开始,服务器进程处理的所有写命令都将会追加到AOF重写缓冲区。

子进程完成AOF文件重写后,会发送一个信号给服务器进程,服务器进程执行信号处理函数:

  • 将AOF重写缓冲区中的所有内容写入到新的AOF文件中,这样新的AOF文件和旧的AOF文件状态就一致了。
  • 将新的AOF文件改名,覆盖现有的AOF文件,完成新旧文件的替换。

服务器进程收到子进程的信号后,不再响应客户端的请求,直到完成信号处理函数,服务器进程才可以正常处理客户端请求。

原创粉丝点击