内存型数据库Redis,是如何实现持久化的?

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 内存型数据库Redis,是如何实现持久化的?

内存型数据库Redis,是如何实现持久化的?

一、导读


Redis是内存数据库,它将字节的数据库状态存储在内存里面,所以如果不想办法将存储在内存里的数据库状态保存到磁盘中,那么Redis服务器进程一旦退出,Redis中的数据库状态也会消失不见…


总所周知,Redis实现持久化主要有两种方式——RDB和AOF,本文主要介绍RDB。


RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中,如图1:

20200903172024964.png

RDB持久化所生成的RDB文件是一个经过压缩的二进制文件 ,通过该文件可以还原生成RDB文件的数据库状态,如图2:

20200903172230355.png

本文主要介绍:


Redis服务器保存和载入RDB文件的方法,分析save命令和bgsave命令的实现

Redis服务器自动保存功能的实现原理

分析RDB文件的组成部分以及其结构和含义


二、RDB文件的创建与载入

Redis的save和bgsave命令用于生成RDB文件。

1.SAVE命令

save命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。

redis> SAVE //等待直到RDB文件创建完毕
OK

2.BGSAVE命令

bgsave命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求。

redis> BGSAVE //派生子进程,并由子进程创建RDB文件
Background saving started

两个命令创建RDB文件的实际工作由rdb.c/rdbSave函数完成,以下是SAVE和BGSAVE的伪码

public static void save(){
  rdbSave();
}
public static void bgSave(){
  PID pid = fork();//创建子进程
  if(pid == 0){
    rdbSave();//子进程负责创建RDB文件
    signal_parent();//完成后向父进程发送信号
  }else if(pid > 0){
    handle_request_and_wait_signal();//父进程继续处理命令请求,并通过轮询等待子进程的信号
  }else{
    handle_fork_error();//处理出错情况
  }
}

RDB文件会在Redis服务器启动的时候载入,但要注意是否有AOF文件。载入RDB文件是由rdb.c/rdbLoad函数完成。

20200904093132957.png

3.服务器状态

接下来通过表格来说明save、bgsave和载入RDB文件时的Redis服务器状态


image.png


三、自动间隔性保存

由于bgsave命令可以在不阻塞服务器的情况下生成RDB文件,所以用户可以通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次bgsave命令。

例如:

save 900 1
save 300 10
save 60 10000

只要满足以下三个条件的任意一个,BGSAVE命令就会被执行:

  • 服务器在900秒之内,对数据库进行了至少1次修改
  • 服务器在300秒之内,对数据库进行了至少10次修改
  • 服务器在60 秒之内,对数据库进行了至少10000次修改
1.设置保存条件

当配置文件设置了以下值后:

save 900 1
save 300 10
save 60 10000

服务器状态中的保存条件的数据结构如下:

20200905114612413.png

从这个数据结构可以看出,saveparams是Redis服务器的一个数组,数组中的每个元素都是一个saveparam对象,saveparam对象又有两个属性分别为seconds(秒数)和changes(修改数)


2.dirty计数器和lastsave属性

dirty计数器记录距离上一次成功执行save或bgsave命令之后,服务器对数据库状态进行了多少次修改。

lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行save或bgsave的时间。

3.检查保存条件是否满足

Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,其负责检查save选项所设置的保存条件是否已经满足,满足则执行bgsave命令。


四、RDB文件结构

20200905135741778.png

这里主要讲下databases、EOF和check_sum的含义:


databases 部分包含零个或任意多个数据库,以及各个数据库中的键值对数据:


若服务器中所有数据库为空,则databases 也为空,长度为0字节

若服务器中至少有一个数据库非空,则databases 部分的长度会与数据库所保存的键值对的数量、类型和内容有关。

EOF 常量的长度为1字节,这个常量标志着RDB文件正文内容的结束,当读入程序遇到EOF时,则会感知到所有数据库的所有键值对都已经载入完毕了。


check_sum 是一个8字节的无符号整数,保存着一个校验和,其根据REDIS、db_version、databases、EOF四个部分的内容进行计算得出的。服务器在载入RDB文件时,会将载入数据所计算出的校验和与check_sum所记录的校验和进行对比,以此来检查RDB文件是否出错或损坏。


1.databases部分

一个RDB文件的databases部分可以保存任意多个非空数据库。


此RDB文件保存了0号和6号数据库中的所有键值对数据


REDIS db_version database 0 database 6 EOF check_sum

而每个非空数据库在RDB文件中都保存以下三个部分

SELECTDB db_number key_value_pairs

SELECTDB 常量的长度为1字节,当程序读到这个值时,它知道接下来要读入的将是一个数据库号码。

db_number 保存着一个数据库号码,长度为1、2或5字节,当程序读入db_number部分后,服务器会调用select命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。

key_value_pairs部分保存数据库的所有键值对。若键值对带有过期时间,则过期时间也会和键值对保存在一起。

例如:0号数据库中的结构:

SELECTDB 0 key_value_pairs

对应于本节开始给出的RDB文件,补充完整如下图:

REDIS db_version SELECTDB 0 pairs0 SELECTDB 6 paris6 EOF check_sum


2.key_value_pairs部分

key_value_pairs由以下三部分组成:

TYPE key value


YPE记录了value的类型,长度1字节,值可以是以下其中一个:


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

key是一个字符串对象,其编码方式和REDIS_RDB_TYPE_STRING类型的value一样。


根据TYPE类型的不同以及保存内容长度的不同,保存value的结构和长度也不同。

带有过期时间的key_value_pairs结构如下:

EXPIRETIME_MS ms TYPE key value

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

ms是一个8字节长的带符号整数,记录一个以毫秒为单位的UNIX时间戳即为k-v对的过期时间。


3.value编码

RDB文件中的每个value部分都保存了一个值对象,每个值对象的类型都由与之对应的TYPE记录,根据类型的不同,value部分的结构、长度也会不同。

接下来是各种不同类型的值对象在RDB文件中的保存结构。


(1)字符串对象


如果TYPE值为REDIS_RDB_TYPE_STRING ,那么value保存的值就是一个字符串对象,字符串对象的编码可以是REDIS_ENCODING_INT或REDIS_ENCODING_RAW。


若字符串对象编码为REDIS_ENCODING_INT,则对象中保存的是长度不超过32位的整数。


若字符串对象编码为REDIS_ENCODING_RAW,则有两种方法保存此字符串:


若字符串长度小于等于20字节,则这个字符串会被直接原样保存

若字符串长度大于20字节,则这个字符串会被压缩后保存


对于未被压缩的字符串,保存结构为:

len String

String保存字符串本身,len保存字符串的长度。

对于压缩后的字符串,保存结构为:

REDIS_RDB_ENC_LZF compressed_len origin_len compressed_string


REDIS_RDB_ENC_LZF常量表示字符串被LZF算法压缩。

当服务器读取到REDIS_RDB_ENC_LZF时,会根据之后的compressed_len 、origin_len、compressed_string对字符串进行解压缩。

其中compressed_string记录被压缩后的字符串,compressed_len 记录压缩后的长度,origin_len记录字符串原始长度。


(2)列表对象

如果TYPE值为REDIS_RDB_TYPE_LIST,那么value保存的值就是一个


REDIS_ENCODING_LINKEDLIST编码的列表对象,如:

list_length item1 item2 itemN

例如这是一个包含三个元素的列表:

3 5 “hello” 5 “world” 1 “!”

(3)集合对象

如果TYPE值为REDIS_RDB_TYPE_SET,那么value保存的值就是一个REDIS_ENCODING_HT编码的集合对象,如:

set_size item1 item2 itemN


例如这是一个包含四个元素的集合:

4 5 “apple” 6 “banana” 3 “cat” 3 “dog”


(4)哈希表对象

如果TYPE值为REDIS_RDB_TYPE_HASH,那么value保存的值就是一个REDIS_ENCODING_HT编码的集合对象,如:

hash_size k-v 1 k-v 2 k-v N

例如,这是一个包含两个键值对的哈希表

2 1 “a” 5 “apple” 1 “b” 6 “banana”


(5)有序集合对象

如果TYPE值为REDIS_RDB_TYPE_ZSET,那么value保存的值就是一个REDIS_ENCODING_SKIPLIST编码的集合对象,如:

sorted_set_size elem1 elem2 elemN


例如,这是一个带有两个元素的有序集合

2 2 “pi” 4 “3.14” 1 “e” 3 “2.7”


说明:


第一个元素的成员是长度为2的字符串“pi”,分值被转换成字符串之后变成了长度为4的字符串“3.14”

第二个元素的成员是长度为1的字符串“e”,分值被转换成字符串之后变成了长度为3的字符串“2.7”

(6)INTSET编码的集合

如果TYPE值为REDIS_RDB_TYPE_SET_INTSET,那么value保存的值就是一个整数集合对象,保存方法是:先将整数集合转换为字符串,然后将这个字符串保存到RDB文件里。


(7)ZIPLIST编码的列表、哈希表或有序集合

如果TYPE值为REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST、REDIS_RDB_TYPE_ZSET_ZIPLIST,那么value保存的就是一个压缩列表对象,RDB文件保存此对象的方法是:将压缩列表转成一个字符串对象,然后将这个字符串保存到RDB文件里。


五、参考文献与总结

参考文献:黄健宏——《Redis设计与实现》

Redis的RDB持久化功能无论是在工程应用上还是在后端开发工程师的面试上都是很重要的知识点,本文是我根据《Redis设计与实现》关于RDB的介绍时的学习总结,希望大家多多支持,如果觉得写得还OK的话,麻烦素质三连!!!

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3天前
|
存储 NoSQL Redis
Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
Redis 是一个内存数据库,意味着它主要将数据存储在内存中,从而能够提供极高的性能。然而,作为内存数据库,Redis 默认情况下的数据不会永久保存。为了确保数据在重启或故障后能够恢复,Redis 提供了几种 **持久化机制**。这些机制允许 Redis 将内存中的数据保存到硬盘上,从而实现数据持久化。
42 22
Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
|
17天前
|
NoSQL 算法 Redis
redis内存淘汰策略
Redis支持8种内存淘汰策略,包括noeviction、volatile-ttl、allkeys-random、volatile-random、allkeys-lru、volatile-lru、allkeys-lfu和volatile-lfu。这些策略分别针对所有键或仅设置TTL的键,采用随机、LRU(最近最久未使用)或LFU(最少频率使用)等算法进行淘汰。
33 5
|
17天前
|
NoSQL 安全 Redis
redis持久化策略
Redis 提供了两种主要的持久化策略:RDB(Redis DataBase)和AOF(Append Only File)。RDB通过定期快照将内存数据保存为二进制文件,适用于快速备份与恢复,但可能因定期保存导致数据丢失。AOF则通过记录所有写操作来确保数据安全性,适合频繁写入场景,但文件较大且恢复速度较慢。两者结合使用可增强数据持久性和恢复能力,同时Redis还支持复制功能提升数据可用性和容错性。
38 5
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
40 5
|
1月前
|
监控 NoSQL 测试技术
【赵渝强老师】Redis的AOF数据持久化
Redis 是内存数据库,提供数据持久化功能,支持 RDB 和 AOF 两种方式。AOF 以日志形式记录每个写操作,支持定期重写以压缩文件。默认情况下,AOF 功能关闭,需在 `redis.conf` 中启用。通过 `info` 命令可监控 AOF 状态。AOF 重写功能可有效控制文件大小,避免性能下降。
|
1月前
|
存储 监控 NoSQL
【赵渝强老师】Redis的RDB数据持久化
Redis 是内存数据库,提供数据持久化功能以防止服务器进程退出导致数据丢失。Redis 支持 RDB 和 AOF 两种持久化方式,其中 RDB 是默认的持久化方式。RDB 通过在指定时间间隔内将内存中的数据快照写入磁盘,确保数据的安全性和恢复能力。RDB 持久化机制包括创建子进程、将数据写入临时文件并替换旧文件等步骤。优点包括适合大规模数据恢复和低数据完整性要求的场景,但也有数据完整性和一致性较低及备份时占用内存的缺点。
|
2月前
|
存储 缓存 NoSQL
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
47 2
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
|
1月前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
599 2
|
2月前
|
存储 缓存 NoSQL
Redis Quicklist 竟让内存占用狂降50%?
【10月更文挑战第11天】
53 2
|
2月前
|
存储 缓存 API
LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存
LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存
48 6
下一篇
DataWorks