redis持计划_rdb_aof_持久化

原创:redis09/24/2019发布pv:0uv:0ip:0twitter #redis

原文地址:https://www.douyacun.com/article/b1833461eadfd05dbb32cfbe6f33e0ce

redis 持久化策略

  • master关闭持久化
  • slave 开启rdb,必要时开启aof和rdb

aof和rdb配置

  • save rdb出发记录条件

    • save 900 1 # 服务器在900秒内,修改了1次
    • save 300 10 # 服务器在300秒内,修改了10次
    • save 60 10000 # 服务器在60秒内,修改了10000次
    • save "" 关闭rdb持久化
  • rdbchecksum yes 压缩rdb文件

  • rdbcompression yes 压缩rdb文件

  • dbfilename dump.rdb rdb文件

  • appendonly yes 启用aof增量持久化

  • appendfilename "appendonly.aof" aof文件

  • appendfsync fsync脏页同步策略

    • always 每次修改
    • everysec 每秒记录一次
    • no 取决于系统
  • no-appendfsync-on-rewrite yes # 如果是开启状态的,并且有bgsave或bgrewriteaof正在进行的话不调用系统函数fsync()(同步脏页到磁盘中)

/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
    * children doing I/O in the background. 
    *
    * 如果 no-appendfsync-on-rewrite 选项为开启状态,
    * 并且有 BGSAVE 或者 BGREWRITEAOF 正在进行的话,
    * 那么不执行 fsync 
    */
if (server.aof_no_fsync_on_rewrite &&
    (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
        return;

aof 实现原理

aof重写并不读取分析现有aof文件,是通过读取当前数据库状态来实现,然后用一条命令代替之前记录这个键值对的多条命令,aof重写函数,因为这个函数会进行大量的写入操作,这个线程会被长时间阻塞,no-appendfsync-on-rewrite 是为了避免在此期间与aof重写进程占用文件描述符

  • fork一个子进程处理
  • 父进程将新输入的写命令追加到server.aof_rewrite_buffer

aof.c/rewriteAppendOnlyFileBackground

/* This is how rewriting of the append only file in background works:
 * 1) The user calls BGREWRITEAOF
 * 2) Redis calls this function, that forks():
 *    2a) the child rewrite the append only file in a temp file.
 *    2b) the parent accumulates differences in server.aof_rewrite_buf.
 * 3) When the child finished '2a' exists.
 * 4) The parent will trap the exit code, if it's OK, will append the
 *    data accumulated into server.aof_rewrite_buf into the temp file, and
 *    finally will rename(2) the temp file in the actual file name.
 *    The the new file is reopened as the new append only file. Profit!
 */
int rewriteAppendOnlyFileBackground(void) {
    pid_t childpid;
    long long start;

    // 已经有进程在进行 AOF 重写了
    if (server.aof_child_pid != -1) return REDIS_ERR;

    // 记录 fork 开始前的时间,计算 fork 耗时用
    start = ustime();

    if ((childpid = fork()) == 0) {
        char tmpfile[256];

        /* Child */

        // 关闭网络连接 fd
        closeListeningSockets(0);

        // 为进程设置名字,方便记认
        redisSetProcTitle("redis-aof-rewrite");

        // 创建临时文件,并进行 AOF 重写
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {
            size_t private_dirty = zmalloc_get_private_dirty();

            if (private_dirty) {
                redisLog(REDIS_NOTICE,
                    "AOF rewrite: %zu MB of memory used by copy-on-write",
                    private_dirty/(1024*1024));
            }
            // 发送重写成功信号
            exitFromChild(0);
        } else {
            // 发送重写失败信号
            exitFromChild(1);
        }
    } else {
        /* Parent */
        // 记录执行 fork 所消耗的时间
        server.stat_fork_time = ustime()-start;

        if (childpid == -1) {
            redisLog(REDIS_WARNING,
                "Can't rewrite append only file in background: fork: %s",
                strerror(errno));
            return REDIS_ERR;
        }

        redisLog(REDIS_NOTICE,
            "Background append only file rewriting started by pid %d",childpid);

        // 记录 AOF 重写的信息
        server.aof_rewrite_scheduled = 0;
        server.aof_rewrite_time_start = time(NULL);
        server.aof_child_pid = childpid;

        // 关闭字典自动 rehash
        updateDictResizePolicy();

        /* We set appendseldb to -1 in order to force the next call to the
         * feedAppendOnlyFile() to issue a SELECT command, so the differences
         * accumulated by the parent into server.aof_rewrite_buf will start
         * with a SELECT statement and it will be safe to merge. 
         *
         * 将 aof_selected_db 设为 -1 ,
         * 强制让 feedAppendOnlyFile() 下次执行时引发一个 SELECT 命令,
         * 从而确保之后新添加的命令会设置到正确的数据库中
         */
        server.aof_selected_db = -1;
        replicationScriptCacheFlush();
        return REDIS_OK;
    }
    return REDIS_OK; /* unreached */
}

残酷的事实

  1. redis 是单线程的数据库,导致单核cpu的最大处理能力就是redis单实例处理能力的天花板了, 举个例子:

新功能上线又有点不放心,于是做了个开关放在 Redis,所有应用可以很方便的共享。通过读取 Redis 中的开关 key 来判断是否启用某个功能,对每个请求做判断。这里的问题是什么?

这个key只能放在一个实例上,而所有的流量都要去这个实例去get,导致该分片实例亚历山大,他的极限在我们的实例上不过4w QPS

  • 根据数据性质把 Redis 集群分类;我的经验是分三类:cache、buffer 和 db
    • cache:临时缓存数据,加分片扩容容易,一般无持久化需要。
    • buffer:用作缓冲区,平滑后端数据库的写操作,根据数据重要性可能有持久化需求。
    • db:替代数据库的用法,有持久化需求。

redis实践