`
xpenxpen
  • 浏览: 703298 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

redis源码阅读笔记(9)——RDB,AOF持久化

阅读更多
1. 持久化
Redis提供了两种持久化数据到硬盘的方式。
RDB:数据库里所有记录的一个快照
AOF(append only file):原汁原味地记录了每次操作命令的历史记录,相当于一个log

如果还没了解过持久化功能的话,请先阅读Redis官网上的persistence手册中文翻译版

RDB以二进制方式存储,每条记录只记一次,文件大小更紧凑,这样恢复起来更快。但是写这个RDB需要时间,这样会造成宕机后数据丢失。
AOF以文本文件方式存储,把每条命令都记下来,会有冗余,但是写aof快,只要往文件末尾追加记录即可,可以使得宕机后可以恢复更多的数据。

接下来可以阅读Redis 设计与实现里的RDB和AOF部分。

2. RDB的save
    SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。
    BGSAVE 则 fork 出一个子进程,子进程调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。

2.1 redis代码核心点
我们看下BGSAVE的代码,主要关注如何fork子进程
//简化以后的流程 
int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;

    // 如果 BGSAVE 已经在执行,那么出错
    if (server.rdb_child_pid != -1) return REDIS_ERR;

    // ......

    if ((childpid = fork()) == 0) {
        int retval;
		
        /* Child */

        // 执行保存操作
        retval = rdbSave(filename);
		
	// ......
		
        // 向父进程发送信号
        exitFromChild((retval == REDIS_OK) ? 0 : 1);

    } else {

        /* Parent */

        // 如果 fork() 出错,那么报告错误
        if (childpid == -1) {
            return REDIS_ERR;
        }

        // 打印 BGSAVE 开始的日志
        redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);

        // 记录负责执行 BGSAVE 的子进程 ID
        server.rdb_child_pid = childpid;

        return REDIS_OK;
    }

    return REDIS_OK; /* unreached */
}

void exitFromChild(int retcode) {
    _exit(retcode); //_exit调用后,会发信号给父进程
}

解释一下,父进程fork出一个子进程。
子进程如果做完了,则调用_exit函数,即可通知到父进程。

如下代码则是父进程如何接收信号
/*  处理 BGSAVE 完成时发送的信号 */
void backgroundSaveDoneHandler(int exitcode, int bysignal) {

    // BGSAVE 成功
    if (!bysignal && exitcode == 0) {
        redisLog(REDIS_NOTICE,
            "Background saving terminated with success");
        //......
    }
    // 更新服务器状态
    server.rdb_child_pid = -1;
}

//简化以后的流程, 这是 Redis 的时间中断器,每秒调用 server.hz 次。
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
	pid_t pid;
	// 接收子进程发来的信号,非阻塞
	if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {

		// BGSAVE 执行完毕
		if (pid == server.rdb_child_pid) {
			backgroundSaveDoneHandler(exitcode,bysignal);

		// BGREWRITEAOF 执行完毕
		} else if (pid == server.aof_child_pid) {
			backgroundRewriteDoneHandler(exitcode,bysignal);
		}
	}

}


以上代码便是核心关键,serverCron可以暂且理解成一个定时要执行的函数,wait3函数意思可以理解成查看一下子进程有没有完成,有了则调用backgroundSaveDoneHandler,没有的话不阻塞,继续干自己的活,过一段时间再来查看子进程是否完成。

如果看不懂,需要补习一下c的进程交互的知识(fork,wait,_exit)。

3. C语言里的fork函数
c语言:fork函数详解

3.1 写个程序进一步理解fork,wait,_exit

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main (void)
{
    pid_t fpid; //fpid表示fork函数返回的值
    int count=0;
    fpid=fork();
    if (fpid < 0)
        printf("error in fork!");
    else if (fpid == 0)
    {
        printf("i am the child process, my process id is %d, parent process id is %d\n",getpid(), getppid());
        printf("我是儿子\n");
        fflush(stdout);
        count++;
        sleep(10);
    }
    else
    {
        printf("i am the parent process, my process id is %d, parent process id is %d\n",getpid(), getppid());
        printf("我是孩子他爹\n");
        fflush(stdout);
        count++;

        int statloc;
        pid_t pid;

        while (1) {
            if ((pid = wait3(&statloc,1,NULL)) != 0) {
            //if ((pid = wait(&statloc)) != 0) {
                printf("收到儿子 %d的信号\n",pid);
                fflush(stdout);
                break;
            } else {
                printf("还没收到儿子 的信号\n");
                fflush(stdout);
                sleep(2);
            }
        }
    }
    printf("统计结果是: %d\n",count);
    fflush(stdout);
    return 0;
}


运行结果如下
i am the child process, my process id is 4260, parent process id is 6660
我是儿子
i am the parent process, my process id is 6660, parent process id is 1
我是孩子他爹
还没收到儿子 的信号
还没收到儿子 的信号
还没收到儿子 的信号
还没收到儿子 的信号
还没收到儿子 的信号
统计结果是: 1
还没收到儿子 的信号
收到儿子 4260的信号
统计结果是: 1

可以看到用了wait3的话,父亲每隔2秒看一下孩子,如果没有音讯,可以做自己的事情(非阻塞的),过一会儿再来看看。
而如果把wait3改成wait的话(把代码里wait3那行注释掉,wait那行注释去掉)
运行结果如下
i am the child process, my process id is 10904, parent process id is 9056
我是儿子
i am the parent process, my process id is 9056, parent process id is 1
我是孩子他爹
统计结果是: 1
收到儿子 10904的信号
统计结果是: 1

可以看到父亲傻傻地等待孩子的音讯(阻塞的)等了10秒,这之间他不能做别的事情。

4. RDB和AOF的格式
这一部分自己跑个实例,然后打开dump.rdb和appendonly.aof,对照网上的资料或是协议分析一下就可以了,比较枯燥,不再赘述。

5. LZF压缩
5.1 Redis里的LZF
RDB因为存的是二进制,所以可以对长的字符串做压缩。如果字符串长度大于20,并且服务器开启了LZF压缩功能,那么保存压缩之后的数据。
用到的类库是liblzf
redis把以下4个源文件原封不动的拷过来了。
lzf_c.c
lzf_d.c
lzf.h
lzfP.h

5.2 Java里的LZF
java也有lzf的类库,可参考如下网址:
https://github.com/ning/compress
https://github.com/ning/jvm-compressor-benchmark/wiki

6. CRC校验
6.1 Redis里的CRC
RDB文件的末尾8个字节是CRC校验和(循环冗余校验).
算法都在下面几个文件里
crc16.c
crc64.c
crc64.h

redis采用了crc-64-jones算法,
rio.h中的定义
struct _rio {
    // 校验和计算函数,每次有写入/读取新数据时都要计算一次
    void (*update_cksum)(struct _rio *, const void *buf, size_t len);
    // 当前校验和
    uint64_t cksum;
};

typedef struct _rio rio;

//将 buf 中的 len 字节写入到 r 中。
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
    while (len) {
        size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
        // 如果crc函数指针被赋值过,则调用它更新crc值
        if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
        if (r->write(r,buf,bytes_to_write) == 0)
            return 0;
        buf = (char*)buf + bytes_to_write;
    }
    return 1;
}

如上定义了一个函数指针void (*update_cksum),
rioWrite可以理解成写rdb文件时会调用的函数,每调用一次,它就会去调用函数指针,以这样的方式来更新crc值:
r->update_cksum()

rio.c
/*
 * 通用校验和计算函数
 */
void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
    r->cksum = crc64(r->cksum,buf,len);
}

这个函数就是简单的调用一下crc64.c里写的crc算法

rdb.c中
int rdbSave(char *filename) {
    // 设置校验和函数,如果需要校验,则把函数指针赋值给rdb.update_cksum
    if (server.rdb_checksum)
        rdb.update_cksum = rioGenericUpdateChecksum;
    // ......
    cksum = rdb.cksum;
    rioWrite(&rdb,&cksum,8);
}

最后可以看到rdbSave的时候会去调用rioWrite,而最后则是写入crc校验和。

6.2 Java里的CRC
JDK里也有crc的算法实现,不过是32位的
java.util.zip.CRC32
java.util.zip.Adler32

Adler-32 校验和几乎与 CRC-32 一样可靠,但是能够更快地计算出来。
分享到:
评论

相关推荐

    尚硅谷——Redis之RDB和AOF持久化

    RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。 也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

    Redis 持久化之RDB和AOF.doc

    Redis 有两种持久化方案,RDB (Redis DataBase)和 AOF (Append Only File)。如果你想快速了解和使用RDB和AOF,可以直接跳到文章底部看总结。本章节通过配置文件,触发快照的方式,恢复数据的操作,命令操作演示...

    部署安装Redis及RDB、AOF持久化验证.md

    部署安装Redis及RDB、AOF持久化验证.md

    Redis持久化 - RDB和AOF

    Redis持久化 - RDB和AOF

    Redis的安装/连接/Redis中的五种数据累心的基本操作/Redis的持久化方案-Rdb+AOF

    java学习-Redis的安装/连接/Redis中的五种数据累心的基本操作/Redis的持久化方案-Rdb+AOF

    Redis两种持久化方案RDB和AOF详解

    本文主要针对Redis 有两种持久化方案RDB和AOF做了详细的分析,希望我们整理的内容能够帮助大家对这个两种方案有更加深入的理解。 Redis 有两种持久化方案,RDB (Redis DataBase)和 AOF (Append Only File)。如果...

    RDB持久化机制: - RDB默认开启 - RDB存储二进制数据

    - RDB持久化机制: ...- AOF持久化机制: - AOF默认关闭,需要将appendonly yes手动开启 - RDB默认持久化日志文件,将每次写操作的命令持久化到本地文件中,在持久化和读取持久化文件时,相对RDB较慢

    【大厂面试】Redis 持久化AOF、RDB概念总结

    AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以 redis 协议追加保存每次写的操作到文件末尾,redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积...

    redis主从复制 RDB/AOF持久化 数据类型-附件资源

    redis主从复制 RDB/AOF持久化 数据类型-附件资源

    Redis持久化RDB和AOF区别详解

    RDB是Redis内存到硬盘的快照,用于redis持久化,创建RDB二进制文件,将存储在内存中的数据,持久化的放到硬盘中,当我们需要这些数据的时候,启动载入RDB文件,数据将会被存入内存中,其实RDB就是一种快照的方式持久...

    吴天雄--Redis个人笔记.doc

    四、命令操作(redis的数据结构、高级命令),五、redis的安全性,六、redis主从复制的特点及实现过程(配置),七、redis哨兵,八、redis简单事务,九、持久化(redis的持久化机制RDB/AOF),十、redis发布与订阅...

    redis知识点.txt

    linux搭建redis单机 数据的持久化 rdb aof 优缺点 还原优先级 rdb切换aof介绍

    redis稳定版6.2.x with cygwin 发布包

    非阻塞,RDB-AOF混合持久化,PSYNC2.0 Redis 5 增加Stream即Redis MQ,主动碎片整理V2,集群管理器移植到C,RESP2 Redis 6 引入多线程IO,客户端缓存,权限控制,支持SSL,提升了RDB加载速度,Redis集群代理模块, ...

    redis稳定版 6.2.x with msys 发布包

    非阻塞,RDB-AOF混合持久化,PSYNC2.0 Redis 5 增加Stream即Redis MQ,主动碎片整理V2,集群管理器移植到C,RESP2 Redis 6 引入多线程IO,客户端缓存,权限控制,支持SSL,提升了RDB加载速度,Redis集群代理模块, ...

    09.图解分析redis的RDB和AOF两种持久化机制的工作原理.zip

    很全面的大数据公开课,课件+视频+代码

    Spring Boot中配置Redis的讲义最全讲义

    目录: 简介 1.1 Redis概述 1.2 Redis在Spring Boot中的应用 配置Redis依赖 2.1 添加Maven依赖 2.2 配置application.properties 编写Redis配置类 ...7.2 AOF持久化 7.3 持久化配置示例 最佳实践与注意事项

    Redis开发实战视频.zip

    0304 AOF持久化机制. 0305 AOF数据恢复案例 0306 Redis线程模型 0307 Redis过期数据淘汰 0308 listpack 0309 碎片整理 0310 SLOWLOG 0311 Latency Monitoring 0312 SSL 0313 ACL简介 0314 ACL用户管理 0315 ACL配置...

Global site tag (gtag.js) - Google Analytics