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

本文涉及的产品
云数据库 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
相关文章
|
5天前
|
存储 监控 负载均衡
保证Redis的高可用性是一个涉及多个层面的任务,主要包括数据持久化、复制与故障转移、集群化部署等方面
【5月更文挑战第15天】保证Redis高可用性涉及数据持久化、复制与故障转移、集群化及优化策略。RDB和AOF是数据持久化方法,哨兵模式确保故障自动恢复。Redis Cluster实现分布式部署,提高负载均衡和容错性。其他措施包括身份认证、多线程、数据压缩和监控报警,以增强安全性和稳定性。通过综合配置与监控,可确保Redis服务的高效、可靠运行。
27 2
|
5天前
|
存储 监控 NoSQL
Redis处理大量数据主要依赖于其内存存储结构、高效的数据结构和算法,以及一系列的优化策略
【5月更文挑战第15天】Redis处理大量数据依赖内存存储、高效数据结构和优化策略。选择合适的数据结构、利用批量操作减少网络开销、控制批量大小、使用Redis Cluster进行分布式存储、优化内存使用及监控调优是关键。通过这些方法,Redis能有效处理大量数据并保持高性能。
25 0
|
5天前
|
NoSQL 安全 Unix
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(中)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
16 0
|
4天前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
5天前
|
存储 NoSQL 关系型数据库
【Redis】Redis的特性和应用场景 · 数据类型 · 持久化 · 数据淘汰 · 事务 · 多机部署
【Redis】Redis的特性和应用场景 · 数据类型 · 持久化 · 数据淘汰 · 事务 · 多机部署
15 0
|
5天前
|
缓存 监控 NoSQL
Redis的主要内存淘汰策略
【5月更文挑战第15天】Redis内存淘汰策略在内存满时删除旧数据以容纳新数据。策略包括:volatile-lru/LFU/random(针对有过期时间的键),volatile-ttl(淘汰TTL最短的键),allkeys-lru/LFU(淘汰所有键),和allkeys-random。还有noeviction策略,不淘汰任何键,新写入会报错。选择策略应基于应用访问模式、数据重要性和性能需求。可以通过info命令监控缓存命中率调整策略。
15 3
|
5天前
|
存储 NoSQL 调度
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(下)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
10 0
|
5天前
|
存储 NoSQL API
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(上)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
18 1
|
5天前
|
存储 NoSQL 算法
Redis源码、面试指南(2)内存编码数据结构(下)
Redis源码、面试指南(2)内存编码数据结构
20 4
|
5天前
|
存储 NoSQL API
Redis源码、面试指南(2)内存编码数据结构(上)
Redis源码、面试指南(2)内存编码数据结构
17 0