Redis版本:Redis 4.0.1
Redis是一个键值对数据库服务器,存储在内存中,如果重启没进行持久化,数据会丢失。所以需要持久化策略RDB和AOF。
涉及Redis源码解析:
https://gitee.com/lidishan/redis-source-code-analysis/blob/master/src/rdb.c
前提知识归纳
RDB用于保存和恢复数据库所有键值对
使用save或bgsave报错,其中bgsave是fork子进程不会造成阻塞
rdb文件是一个二进制文件
不同类型键,会有不同数据结构存储
RDB会采用save、bgsave进行rdb保存
save:生成RDB,会导致Redis阻塞
bgsave:生成RDB,fork子进程处理
备注:RDB是在服务器启动的时候自动载入的(如果有rdb文件)。但如果开启了AOF(更新·频率更高),服务器就会
优先使用AOF文件加载数据。
其中是否执行bgsave这些是根据
RedisServer#saveparams
来判断,其数据结构如下:
struct
redisServer
{
....
//
保存触发
rdb
文件存储的参数数组
struct
saveparam
*
saveparams
;
/* Save points array for RDB */
}
// 在seconds秒内数据库修改changes次就触发RDB文件生成
struct
saveparam
{
time_t
seconds
;// 秒数
int
changes
;// 数据库修改次数
};
符合上面的参数生成RDB,举个例子:
-
seconds=1, changes=5。即代表1秒内数据库有5次修改就可以重新生成RDB
bgsave是fork子进程进行处理,其触发方式大概有如下两种:
手动触发:执行bgsave命令手动触发
自动触发:根据上面的saveparams命令定时触发 或者 主从节点同步
sync指令或psync(这个做主从分析的时候再解析)
下面举个定时触发的例子源码(serverCron定时触发,然后扫描saveparams参数)
/**
*
作为时间事件运行
*/
int
serverCron
(
struct
aeEventLoop
*eventLoop
, long long
id
, void
*clientData) {
....
//
单位时间内修改次数达到上限
if
(server.
dirty
>= sp->
changes
&&
server.unixtime-server.lastsave > sp->seconds &&
(server.unixtime-server.lastbgsave_try >
CONFIG_BGSAVE_RETRY_DELAY ||
server.
lastbgsave_status
== C_OK))
{
//
打印保存进行中的日志
serverLog(LL_NOTICE
,
"%d changes in %d seconds. Saving..."
,
sp->
changes
,
(
int
)sp->seconds)
;
rdbSaveInfo
rsi
,
*rsiptr
;
rsiptr = rdbPopulateSaveInfo(&rsi)
;
//
进行
bgsave
rdbSaveBackground(server.
rdb_filename
,
rsiptr)
;
break;
}
....
}
/**
*
rdbbgsave
,会
fork
一个子进程处理
**/
int
rdbSaveBackground
(
char
*filename
,
rdbSaveInfo
*rsi) {
pid_t childpid
;
//
子进程
//
判断是否有活跃的子进程在拷贝,如果有则终止
server.child_pid != -1
if
(hasActiveChildProcess())
return
C_ERR
;
//
记录当前
bgsave
前的数据库被修改次数 和 最后修改的时间
server.
dirty_before_bgsave
= server.
dirty
;
server.lastbgsave_try = time(NULL)
;
// fork
子进程
if
((childpid = redisFork(CHILD_TYPE_RDB)) ==
0
) {
int
retval
;
/* Child */
redisSetProcTitle(
"redis-rdb-bgsave"
)
;
//
设置标题
redisSetCpuAffinity(server.
bgsave_cpulist
)
;
//
设置
CPU
亲缘属性
retval = rdbSave(filename
,
rsi)
;
//
开始保存
if
(retval == C_OK) {
//
发送保存数据
sendChildCowInfo(
CHILD_INFO_TYPE_RDB_COW_SIZE
,
"RDB"
)
;
}
exitFromChild((retval == C_OK) ?
0
:
1
)
;
//
结束子进程保存
}
else
{
/* Parent */
// fork
子进程失败
if
(childpid == -
1
) {
server.
lastbgsave_status
= C_ERR
;
serverLog(LL_WARNING
,
"Can't save in background: fork: %s"
,
strerror(errno))
;
return
C_ERR
;
}
//
保存状态
serverLog(LL_NOTICE
,
"Background saving started by pid %ld"
,
(
long
) childpid)
;
server.rdb_save_time_start = time(NULL)
;
server.
rdb_child_type
= RDB_CHILD_TYPE_DISK
;
return
C_OK
;
}
return
C_OK
;
/* unreached */
}
上面解释了RDB是如何装载进来的,那下面来讲解一下RDB的文件结构。如何存储?如何压缩?
RDB文件大致组成结构
- REDIS:标识RDB文件开头的字符'REDIS'
- db_version:4字节。记录RDB文件版本号
- database:包含0-N个数据库
- EOF:标记RDB结束
- check_sum:保存前面四个的校验和,用于做签名校验
其中database存储结构如下
- SELECTDB:database存储的开始标识
- db_number:1/2/5字节。数据库号码
-
key_value_pairs:存储当前数据库中所有的键值对数据,
根据类型不同而结构不同
key_value_pairs结构为
不带过期时间的键值对
- TYPE:记录value类型,主要含string、list、zset、hash、list_zipskiplist等
- key:redis key,总是string类型
- value:redis对应的值
带过期时间的键值对
- EXPIRETIME_MS:1字节。开始标识
- ms:8字节。UNIX时间戳
- TYPE:记录value类型,主要含string、list、zset、hash、list_zipskiplist等
- key:redis key,总是string类型
- value:redis对应的值
接下来讲解一下
value是怎么根据TYPE编码来生成RDB的对应值数据(大致讲解几个即可,知道什么回事,没必要纠结)
String字符串
字符串<=20字节:len记录原来长度
字符串>20字节:origin_len记录原来长度,compresesed_len记录压缩后长度
List列表
记录长度,然后item一个接一个拼起来(每个元素记录长度)
set集合
跟list一样
hash哈希
记录有多少个键值对,比如下面:2个键值对,"a"->"apple","b"->"banana"
zset有序集合
记录分数和item
标签:fork,Redis,RDB,bgsave,server,键值,rdb,解析
来源: https://blog.csdn.net/qq_28666081/article/details/117192030