美文网首页
Redis深度历险 - 持久化机制(一)

Redis深度历险 - 持久化机制(一)

作者: 突击手平头哥 | 来源:发表于2021-04-24 10:23 被阅读0次

Redis深度历险 - 持久化实现

Redis支持两种持久化的方式:快照和AOF;快照指的是会将当前数据库中所有的数据记录下来,是一次性全量备份;AOF日志记录的是内存数据修改的指令文本记录,是连续的增量备份。


Redis - 快照

快照是一次性的全量备份,执行bgsave命令就会开始全量备份,在此过程中并不会阻塞Redis的正常使用;从这一点上来看,很容易猜到其底层应该是fork了一个新的进程来实现的。

快照命令

docker run -d -p 6379:6379 --name=redis redis
docker exec -it redis /bin/bash
image-001.png

  最终会在目录下生成dump.rdb,dump.rdb中的内容并非是明文的

fork的原理

  `fork`是Linux中用来创建子进程的函数,在历史版本中`fork`创建子进程时会对父进程做一份复制操作,即子进程拥有父进程完全一样的数据,Redis正是利用此原理实现的,子进程拥有所有的数据进行备份操作,父进程依然执行业务。

  在高版本中的fork中实现了COW的特性,即写时复制,过去的版本中此特性由vfork实现;在Redis环境中,写时复制的含义是:子进程创建完毕后,父子进程共享一片内存空间,数据段在操作系统中由很多页面组成,当父进程对某个页面进行操作写入时会先复制一份分离出来进行修改。

快照原理

void bgsaveCommand(client *c)
................
    else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
        addReplyStatus(c,"Background saving started");
................
    
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),

  默认情况下备份文件名就是dump.rdb。

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi)
..................
    openChildInfoPipe();
    if ((childpid = redisFork()) == 0) {
        ....
        retval = rdbSave(filename,rsi);
        ....
.................
int redisFork() {
    int childpid;
    long long start = ustime();
    if ((childpid = fork()) == 0) {
        /* Child */
        closeListeningSockets(0);               //子进程关闭不需要的监听句柄
        setupChildSignalHandlers();             //创建信号将领
    } else {
        /* Parent */
        server.stat_fork_time = ustime()-start;
        server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
        latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
        if (childpid == -1) {
            return -1;
        }
        updateDictResizePolicy();       //避免大量的内存页面的复制,所以禁止hash表的rehash操作
    }
    return childpid;
}

  开始执行快照时先进行创建管道负责后续的通信,之后就执行fork操作。父进程在执行快照的时候会禁止dictrehash操作,但是这段代码好像有问题,实际上无法生效。

  Redis的快照后续的存储并没有涉及到非常复杂的算法技术等,但是也并非文本协议存储的;比如存储时会记录数据的类型,在Redis中以宏定义表示,存储时并没有说转成字符串表示,以文本方式打开的话可能会看到乱码之类的。

快照相关配置

  • rdbcompression yes:rdb文件是否压缩
  • Rdbchecksum yes:恢复数据时是否校验完整性,实质上时进行了一些crc64计算;每次进行写入文件时,将上一次crc64的值和此次写入的数据进行crc64计算
  • dbfilename dump.rdb:到处文件名
  • dir ./:存放路径

AOF存储

AOF存储的是Redis服务器的顺序指令记录,记录对内存有修改的记录;如果需要恢复Redis数据,只需要对所有的执行进行重放就可以恢复了

AOF配置

  • appendonly yes:是否开启AOF持久化
  • appendfilename appendonly.aof:文件名
  • appendfsync:磁盘写入策略,主要是因为文件IO有缓冲区可能数据没有真正写入到文件
    • always:每次写入磁盘,性能较差
    • everysec:每秒写入一次
    • no:系统处理缓存回写

AOF的效果

*2
$6
SELECT
$1
0
*3
$3
set
$4
name
$8

  在指定的文件中可以看到AOF是以文本的形式存储的操作记录,这部分基本与Redis的文本传输协议是类似的。

AOF的实现

createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
.........
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc);
    if (flags & PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}

  首先读取配置文件appendonly决定是否开启,服务器执行完写命令就会调用propagate继续追加记录

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    robj *tmpargv[3];
    
    //如果数据库进行了切换就会记录SELECT命令,Redis默认是有16个数据库的
    if (dictid != server.aof_selected_db) {
        char seldb[64];

        snprintf(seldb,sizeof(seldb),"%d",dictid);
        buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
            (unsigned long)strlen(seldb),seldb);
        server.aof_selected_db = dictid;
    }

.................
    if (server.aof_state == AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

    if (server.aof_child_pid != -1)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));

    sdsfree(buf);
}

  由于Redis有多个数据库db文件, 所有在数据库切换的情况下就需要记录SELECT命令;同时会对命令做一些处理,最终调用aofRewriteBufferAppend存储内容。

void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
    listNode *ln = listLast(server.aof_rewrite_buf_blocks);
    aofrwblock *block = ln ? ln->value : NULL;

    while(len) {
        /* If we already got at least an allocated block, try appending
         * at least some piece into it. */
        if (block) {
            unsigned long thislen = (block->free < len) ? block->free : len;
            if (thislen) {  /* The current block is not already full. */
                memcpy(block->buf+block->used, s, thislen);
                block->used += thislen;
                block->free -= thislen;
                s += thislen;
                len -= thislen;
            }
        }

        if (len) { /* First block to allocate, or need another block. */
            int numblocks;

            block = zmalloc(sizeof(*block));
            block->free = AOF_RW_BUF_BLOCK_SIZE;
            block->used = 0;
            listAddNodeTail(server.aof_rewrite_buf_blocks,block);

            /* Log every time we cross more 10 or 100 blocks, respectively
             * as a notice or warning. */
            numblocks = listLength(server.aof_rewrite_buf_blocks);
            if (((numblocks+1) % 10) == 0) {
                int level = ((numblocks+1) % 100) == 0 ? LL_WARNING :
                                                         LL_NOTICE;
                serverLog(level,"Background AOF buffer size: %lu MB",
                    aofRewriteBufferSize()/(1024*1024));
            }
        }
    }

    /* Install a file event to send data to the rewrite child if there is
     * not one already. */
    if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
        aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
            AE_WRITABLE, aofChildWriteDiffData, NULL);
    }
}

  可以看到数据并非立刻存储起来的,而是放在一个链表当中存储起来;同时记录注册写事件,等待可写。

AOF的优缺点

  • AOF随着进程的执行,整个文件会越来越庞大;需要进行重写瘦身
  • AOF在恢复时需要重新执行一边所有的命令,耗时较长

总结

  AOF和RDB各有各的长处,在实际部署时也非一成不变的;一种很常用的方法就是同时进行RDB和AOF记录,RDB记录镜像,AOF记录从记录镜像开始的日志,这样双方的优势都可以利用起来。

相关文章

  • Redis深度历险-AOF持久化

    Redis深度历险-AOF持久化 Redis提供两种持久化方式AOF和RDB,RDB是快照形式持久化全量数据、AO...

  • Redis深度历险 - 持久化机制(一)

    Redis深度历险 - 持久化实现 Redis支持两种持久化的方式:快照和AOF;快照指的是会将当前数据库中所有的...

  • Redis常见面试题

    Redis常见面试题 Redis持久化机制 Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同...

  • 10-Redis持久化

    1、Redis提供了哪些持久化机制: 1). RDB(Redis DataBase)持久化:该机制是指在指定的时间...

  • redis相关问题

    redis持久化的意义:在于故障恢复RDB和AOF两种持久化机制的介绍 RDB持久化机制:对redis中的数据执行...

  • redis笔记(四)redis持久化

    redis持久化 Redis持久化机制:redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中...

  • redis持久化机制

    redis持久化机制的原理 一、两种实现redis持久化的机制,RDB模式和AOF模式 ①RDB模式 ②AOF模式...

  • Redis

    Redis 持久化机制 Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数...

  • 读者让我总结一波 redis 面试题,现在肝出来了

    Redis 持久化机制 Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数...

  • 2020-09-05

    1、 Redis持久化机制 Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保...

网友评论

      本文标题:Redis深度历险 - 持久化机制(一)

      本文链接:https://www.haomeiwen.com/subject/vzmkrltx.html