redis源码之rdb(九)

简介

rdb是redis的数据持久化方式之一,类似数据库的全备,通过将kv数据全量保存到磁盘来进行数据持久化。特点是数据格式紧凑,加载速度快,一般数据丢失是分钟级别。

配置rdb

# rdb文件存放路径
dir /usr/local/redis/data
# rdb文件名称,结合上面rdb配置,rdb文件就是/usr/local/redis/dump.rdb
dbfilename dump.rdb
# 开启rdb, 设置1小时内有1个key修改或插入,就创建新的rdb文件
# 可以配置多条save, 满足任意一个save都会触发创建新的rdb文件
# 如果想关闭rdb, 就配置 save ""
save 3600 1
save 300 100
save 60 10000

源码分析

原理简介

redis主进程通过fork系统调用创建子进程,子进程就有了fork调用前一刻父进程的完整内存快照,然后子进程将kv数据写入到文件。操作系统的fork调用考虑到子进程可能只有很少的数据修改,所以是子进程和父进程共享了物理页的,但是在发生写操作的时候,先将物理内存页先拷贝一份,然后将子进程页表指向新的物理内存页,也就是copy-on-write技术。fork调用会阻塞主进程,当redis进程的页表越大,fork阻塞的时间也就越长

# 查看内存使用情况, 这里rss为20G
info memory
used_memory_rss:22341062656
# 查看上次fork调用耗时, 发现消耗了623ms
info stats
latest_fork_usec:458556

触发时机

触发创建rdb的方式有:

  • 执行命令bgsave或者save
  • 从节点连接到主节点,需要进行全量同步
  • 满足配置文件中的save条件(多少秒内发生了多少次修改操作)

源码

// 定时检查是否满足save条件
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData){
     ...
           // 检查是否满足save配置条件
        for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

            /* Save if we reached the given amount of changes,
             * the given amount of seconds, and if the latest bgsave was
             * successful or if, in case of an error, at least
             * CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed. */
            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);
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

     ...
}


// bgsave命令
/* BGSAVE [SCHEDULE] */
void bgsaveCommand(client *c) {
    int schedule = 0;

    /* The SCHEDULE option changes the behavior of BGSAVE when an AOF rewrite
     * is in progress. Instead of returning an error a BGSAVE gets scheduled. */
    if (c->argc > 1) {
        if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
            schedule = 1;
        } else {
            addReplyErrorObject(c,shared.syntaxerr);
            return;
        }
    }

    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);

    if (server.child_type == CHILD_TYPE_RDB) {
        addReplyError(c,"Background save already in progress");
    } else if (hasActiveChildProcess()) {
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
        } else {
            addReplyError(c,
            "Another child process is active (AOF?): can't BGSAVE right now. "
            "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
            "possible.");
        }
    } else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
        addReplyStatus(c,"Background saving started");
    } else {
        addReplyErrorObject(c,shared.err);
    }
}

个人经验

  • dump.rdb文件的体积比redis进程使用的内存小很多,我见到的12G RSS 内存的redis的rdb文件就2G
  • 保存rdb文件是顺序写,所以速度较快,2G的rdb文件的保存时间差不多要持续2分钟,这两分钟内并不会一直持续写磁盘;因为开始数据都是写到了内存缓冲区,操作系统会刷内存缓冲区数据到磁盘;rdb子进程在写完rdb文件的时候会调用flush,主动触发文件完全刷写到磁盘。
    038b2eda462d760492536b6b0495bb43.png
  • 采用了默认的save配置的时候,可能每5分钟触发一次写盘,每次写盘持续2分钟,每次写2GB的文件到磁盘。根据copy-on-write日志知道实际上发送的数据写也就88MB,这种情况下如果是aof,写磁盘的数据量可以减少很多。
17955:M 23 Jul 14:58:06.481 * Background saving started by pid 14710
14710:C 23 Jul 15:00:00.137 * DB saved on disk
14710:C 23 Jul 15:00:00.532 * RDB: 91 MB of memory used by copy-on-write
17955:M 23 Jul 15:00:01.063 * Background saving terminated with success
17955:M 23 Jul 15:05:02.002 * 10 changes in 300 seconds. Saving...
17955:M 23 Jul 15:05:02.439 * Background saving started by pid 16112
16112:C 23 Jul 15:06:52.268 * DB saved on disk
16112:C 23 Jul 15:06:52.627 * RDB: 88 MB of memory used by copy-on-write
17955:M 23 Jul 15:06:53.213 * Background saving terminated with success
17955:M 23 Jul 15:11:54.069 * 10 changes in 300 seconds. Saving...
17955:M 23 Jul 15:11:54.501 * Background saving started by pid 17650
17650:C 23 Jul 15:13:44.802 * DB saved on disk
17650:C 23 Jul 15:13:45.185 * RDB: 88 MB of memory used by copy-on-write
17955:M 23 Jul 15:13:45.775 * Background saving terminated with success
  • 根据redis官方建议是只开启rdb就行,如果希望只丢失1秒的数据就可以开启aof, rdb和aof可以同时开启,如果存在aof文件就优先从aof加载,不过从aof加载比从rdb慢,因为aof是指令的格式记录,相当于重新应用指令,而且aof文件会大一些。我认为可以配置很久才进行一次rdb生成,然后主要是依靠aof,从而减少写磁盘和减少数据丢失风险。
  • aof文件的格式是rdb+command,这样子可以减少aof文件的大小,当aof文件增长到初始aof文件大小2倍(可配置)然后就重新创建aof文件,这样子可以加快重新启动的时候aof文件加载速度

rdb文件分析

生产上的redis内存越来越大,因为没有设置maxmemory,最后触发了oom,分析发现发现avg_ttl的值很大,于是使用rdbtool分析rdb文件,了解存在哪些key没有设置ttl,哪些key比较大。

安装

# 安装rdbtools工具, 安装后命令会在/usr/local/bin下面,需要配置一下环境变量PATH
pip3 install rdbtools python-lzf


rdb --help 
   -c command  command可以为
     json 使用json格式输出kv
	    memory 分析内存使用情况
		  justkeys 只输出keys
		  protocol 输出成内部指令格式
   -l n 只输出大小(size)最大的n个key 
   -b bytes 只输出超过bytes的key
   -t type 输出指定类型的数据
   -k xxx 输出指定格式(正则匹配)的keys

分析rdb文件

# 建议将rdb文件拷贝出来了进行分析,或者在备机上分析

# 分析内存相关信息
rdb -c memory dump.rdb  > mem.log

# 查看设置了expire的键
awk -F ',' '{if ($8!="") print $0}'  mem.log  > exp.log

# 查看大于1kb的键
rdb -c memory -b 1024 dump.rdb > size_1024.log

posted @ 2024-09-27 17:10  董少奇  阅读(61)  评论(0)    收藏  举报