乐趣区

关于redis:Redis-AOF

问大家一个问题,如果 Redis 宕机,内存中的数据全副失落,怎么复原数据?

Redis 别离提供了 RDB 和 AOF 两种长久化机制:
RDB 将数据库的快照 (snapshot) 以二进制的形式保留到磁盘中。
AOF 则以协定文本的形式,将所有对数据库进行过写入的命令 (及其参数) 记录到 AOF 文件,以此达到记录数据库状态的目标。

(1) AOF 日志是什么

AOF(Append Only File)日志是一种写后日志,在 Redis 先执行命令,把数据写入内存后,而后才记录日志,日志会追加到文件开端,所以叫 AOF 日志。

和咱们常见的 WAL 日志不同,WAL(Write Ahead Log)是写前日志,在理论写数据前,先把批改的数据记到日志文件中,再去执行命令,这个就要求数据库须要额定的查看命令是否正确。

(2) 为什么要用 AOF

AOF 日志的作用次要有 2 个:
1. 用来在 redis 宕机后复原数据;
2. 能够用来主从数据同步。

(3) AOF 原理

(3.1) AOF 命令同步原理

Redis 将所有对数据库进行过写入的命令 (及其参数) 记录到 AOF 文件,以此达到记录数据库状态的目标。

redis> RPUSH list 1 2 3 4
(integer) 4

redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"

redis> KEYS *
1) "list"

redis> RPOP list
"4"

redis> LPOP list
"1"

redis> LPUSH list 1
(integer) 3

redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"

那么其中四条对数据库有批改的写入命令就会被同步到 AOF 文件中:

RPUSH list 1 2 3 4

RPOP list

LPOP list

LPUSH list 1

(3.2) Reids AOF 数据存储形式

为了解决的不便,AOF 文件应用网络通讯协定的格局来保留这些命令。

*2      # 示意这条命令的音讯体共 2 行
$6      # 下一行的数据长度为 6
SELECT  # 音讯体
$1      # 下一行数据长度为 1
0       # 音讯体
*6      # 示意这条命令的音讯体共 6 行
$5      # 下一行的数据长度为 5
RPUSH   # 音讯体
$4      # 下一行的数据长度为 4
list
$1
1
$1
2
$1
3
$1
4
*2
$4
RPOP
$4
list
*2
$4
LPOP
$4
list
*3
$5
LPUSH
$4
list
$1
1

除了 SELECT 命令是 AOF 程序本人加上去的之外,其余命令都是之前咱们在终端里执行的命令。

(3.4) 同步命令到 AOF 文件的过程

同步命令到 AOF 文件的整个过程能够分为三个阶段:

  1. 命令流传:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。
  2. 缓存追加:AOF 程序依据接管到的命令数据,将命令转换为网络通讯协定的格局,而后将协定内容追加到服务器的 AOF 缓存中。
  3. 文件写入和保留:AOF 缓存中的内容被写入到 AOF 文件开端,如果设定的 AOF 保留条件被满足的话,fsync函数或者 fdatasync函数会被调用,将写入的内容真正地保留到磁盘中。

(4) AOF 怎么应用

(4.1) 保留模式

Redis 目前反对三种 AOF 保留模式,它们别离是:

  1. AOF_FSYNC_NO:不保留。
  2. AOF_FSYNC_EVERYSEC:每一秒钟保留一次。
  3. AOF_FSYNC_ALWAYS:每执行一个命令保留一次。

(4.1) AOF 保留模式对性能和安全性的影响

对于三种 AOF 保留模式,它们对服务器主过程的阻塞状况如下:

不保留(AOF_FSYNC_NO):写入和保留都由主过程执行,两个操作都会阻塞主过程。
每一秒钟保留一次(AOF_FSYNC_EVERYSEC):写入操作由主过程执行,阻塞主过程。保留操作由子线程执行,不间接阻塞主过程,但保留操作实现的快慢会影响写入操作的阻塞时长。
每执行一个命令保留一次(AOF_FSYNC_ALWAYS):和模式 1 一样。

(5) AOF 重写

AOF 文件通过同步 Redis 服务器所执行的命令,从而实现了数据库状态的记录,然而,这种同步形式会造成一个问题:随着运行工夫的流逝,AOF 文件会变得越来越大。

举个例子,如果服务器执行了以下命令,那么光是记录 list 键的状态,AOF 文件就须要保留四条命令。

RPUSH list 1 2 3 4      // [1, 2, 3, 4]

RPOP list               // [1, 2, 3]

LPOP list               // [2, 3]

LPUSH list 1            // [1, 2, 3]

“重写”其实是一个有歧义的词语,实际上,AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取,它针对的是数据库中键的以后值。
下面的例子,列表键 list 在数据库中的值就为 [1, 2, 3]。
如果要保留这个列表的以后状态,并且尽量减少所应用的命令数,那么最简略的形式不是去 AOF 文件上剖析后面执行的四条命令,而是间接读取 list 键在数据库的以后值,而后用一条 RPUSH 1 2 3 命令来代替后面的四条命令。

列表、汇合、字符串、有序集、哈希表等键能够用相似的办法来保留状态,并且保留这些状态所应用的命令数量,比起之前建设这些键的状态所应用命令的数量要大大减少。

(5.1) AOF 后盾重写

防止竞争 aof 文件

当子过程在执行 AOF 重写时,主过程须要执行以下三个工作:
解决命令申请。
将写命令追加到现有的 AOF 文件中。
将写命令追加到 AOF 重写缓存中。

(5.2) AOF 重写函数与触发机会

实现 AOF 重写的函数是 rewriteAppendOnlyFileBackground

触发 AOF 有 3 种办法:

  1. 执行 bgrewriteaof 命令,对应的函数是bgrewriteaofCommand
  2. 配置开始 AOF 重写,对应函数是startAppendOnly
  3. 周期性查看,对应函数是 serverCron 里的 rewriteAppendOnlyFileBackground

(5.2.1) 手动触发 AOF 重写 -bgrewriteaofCommand

void bgrewriteaofCommand(client *c) {if (server.aof_child_pid != -1) {  // 有 AOF 重写子过程 
        // 后盾曾经有重写过程
        addReplyError(c,"Background append only file rewriting already in progress");
    } else if (hasActiveChildProcess()) {  // 有沉闷子过程
        // 
        server.aof_rewrite_scheduled = 1;
        // 
        addReplyStatus(c,"Background append only file rewriting scheduled");
    } else if (rewriteAppendOnlyFileBackground() == C_OK) {  // 理论执行 AOF 重写
        addReplyStatus(c,"Background append only file rewriting started");
    } else {addReplyError(c,"Can't execute an AOF background rewriting. ""Please check the server logs for more information.");
    }
}

(5.2.2) 开始 AOF 从新 -startAppendOnly

/* Called when the user switches from "appendonly no" to "appendonly yes"
 * at runtime using the CONFIG command. */
int startAppendOnly(void) {char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
    int newfd;

    newfd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
    serverAssert(server.aof_state == AOF_OFF);
    if (newfd == -1) {char *cwdp = getcwd(cwd,MAXPATHLEN);

        serverLog(LL_WARNING,
            "Redis needs to enable the AOF but can't open the ""append only file %s (in server root dir %s): %s",
            server.aof_filename,
            cwdp ? cwdp : "unknown",
            strerror(errno));
        return C_ERR;
    }
    if (hasActiveChildProcess() && server.aof_child_pid == -1) {
        server.aof_rewrite_scheduled = 1;
        serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
    } else {
        /* If there is a pending AOF rewrite, we need to switch it off and
         * start a new one: the old one cannot be reused because it is not
         * accumulating the AOF buffer. */
        if (server.aof_child_pid != -1) {serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
            killAppendOnlyChild();}
        if (rewriteAppendOnlyFileBackground() == C_ERR) {close(newfd);
            serverLog(LL_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
            return C_ERR;
        }
    }
    /* We correctly switched on AOF, now wait for the rewrite to be complete
     * in order to append data on disk. */
    server.aof_state = AOF_WAIT_REWRITE;
    server.aof_last_fsync = server.unixtime;
    server.aof_fd = newfd;
    return C_OK;
}

(5.2.3) 周期性执行 -serverCron

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {

    // 省略局部代码

    /* Start a scheduled AOF rewrite if this was requested by the user while
     * a BGSAVE was in progress. */
    if (!hasActiveChildProcess() &&
        server.aof_rewrite_scheduled)
    {rewriteAppendOnlyFileBackground();
    }

}

(5.3) AOF 重写的根本过程

/* ----------------------------------------------------------------------------
 * AOF background rewrite
 * ------------------------------------------------------------------------- */

/* 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;

    if (hasActiveChildProcess()) return C_ERR;
    if (aofCreatePipes() != C_OK) return C_ERR;
    openChildInfoPipe();
    if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {char tmpfile[256];

        /* Child */
        redisSetProcTitle("redis-aof-rewrite");
        redisSetCpuAffinity(server.aof_rewrite_cpulist);
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {sendChildCOWInfo(CHILD_TYPE_AOF, "AOF rewrite");
            exitFromChild(0);
        } else {exitFromChild(1);
        }
    } else {
        /* Parent */
        if (childpid == -1) {closeChildInfoPipe();
            serverLog(LL_WARNING,
                "Can't rewrite append only file in background: fork: %s",
                strerror(errno));
            aofClosePipes();
            return C_ERR;
        }
        serverLog(LL_NOTICE,
            "Background append only file rewriting started by pid %d",childpid);
        server.aof_rewrite_scheduled = 0;
        server.aof_rewrite_time_start = time(NULL);
        server.aof_child_pid = childpid;
        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. */
        server.aof_selected_db = -1;
        replicationScriptCacheFlush();
        return C_OK;
    }
    return C_OK; /* unreached */
}

参考资料

https://weikeqin.com/tags/redis/

Redis 源码分析与实战 学习笔记 Day19 19 AOF 重写(上):触发机会与重写的影响
https://time.geekbang.org/col…

退出移动版