REDIS fdatasync技术问题和BIO技术的引入

http://oldblog.antirez.com/post/fsync-different-thread-useless.html

这是原文作者的博客 把他翻译下 带上自己的一些理解 看看作者引入fdatasync和bio技术上的一些探讨:【非常有用的磁盘持久化技术

对于从kernel buffers写入到磁盘中这点是非常重要的,fdatasync就是做这个工作,当然本身磁盘本省也有cache层 这层对于我们来讲可以暂时不用考虑,

对于fsync() 把文件数据和文件元信息写入强刷到磁盘中,速度是比较慢的。

REDIS提供一个持久化的模式叫做Append Only File.数据集的每个改变都会被写入到磁盘中,就是一个操作日志型的操作。所以fsync()操作是必须得用的【如果想要安全的话 这是必须的操作】

因为fsync是慢的 REDIS就允许有3种不同的策略:

fsync never: 让kernel后台线程去做  这个线程可能是30秒去做一次哦

fsync everysec: 一秒调用fsync

fsync always:每次有write操作到AOF里 就会调用fsync

fsync erverysec 是一个很好的这种折中对于性能和安全这2个点。我们仅仅涉及一个能够每秒做一次sync但是不会阻塞我们反馈给客户端信息的操作。所以作者觉得这样做必须把fsync call调用就移到了另外一个线程里。用这种方式来处理,也许fsync会引起DISK非常繁忙 但是没有用户会注意到这种延迟。因为客户端所需要的数据还是在内存里存在,redis还是照样能够很好的工作。

貌似正确 但是我开始感觉这是无用的  因为write调用会被阻塞如果一个fsync()去处理同一个文件的时候。

现在我们尝试的来分析下【非翻译部分】

目前而言 前面探讨了这个fdatasync技术 现在我们来看看BIO技术和怎么解决和提高AOF的技术。

BIO: backgroud IO .首先fsync是必须要调用的 不过他多慢  其次fsync必须要放在其他thread来做 也就是backgroud thread.

background thread 目前redis主要做2个事情:

1 fdatasync(fd)

2 close(fd)

线程1 来做fdatasync(fd)  线程2做close(fd)

#define REDIS_BIO_CLOSE_FILE    0 /* Deferred close(2) syscall. */
#define REDIS_BIO_AOF_FSYNC     1 /* Deferred AOF fsync. */

在bio.c:

static pthread_mutex_t bio_mutex[REDIS_BIO_NUM_OPS];
static pthread_cond_t bio_condvar[REDIS_BIO_NUM_OPS];
static list *bio_jobs[REDIS_BIO_NUM_OPS];

在aof.c文件里 会调用bioCreateBackgroundJob(),来创建2个JOB 放在thread1和thread2之后 线程就完成了在后台处理这2个动作。

可以看下面的图: 就能了解大概的做的一些动作:

QQ截图20140830214442

这里我就假设你较为熟悉AOF机制,下期我会写下AOF持久化。 这样就暴露出了一个问题:

AOF创建了一个进程之后 flushAppendOnlyFile 要把AOF文件写入到磁盘里,可以试想下:主线程一方面要调用write()把aofbuf写入到内核缓冲区,而另外一个线程调用fdatasync这就比较麻烦了这样会阻止了主线程的一个阻塞动作,影响了客户端的延迟,你懂的,这不是我们所想要的,看看redis怎么做的:

if (server.aof_fsync == AOF_FSYNC_EVERYSEC) sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;

首先判断aof_FSYNC那个线程里面有没有任务,如果有就要标记下sync_in_progress

if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
        /* With this append fsync policy we do background fsyncing.
         * If the fsync is still in progress we can try to delay
         * the write for a couple of seconds. */
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                /* No previous write postponinig, remember that we are
                 * postponing the flush and return. */
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                /* We were already waiting for fsync to finish, but for less
                 * than two seconds this is still ok. Postpone again. */
                return;
            }
            /* Otherwise fall trough, and go write since we can't wait
             * over two seconds. */
            server.aof_delayed_fsync++;
            redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
        }

sync_in_progress不等于0 是有fdatasync做这个动作,这里有2个条件让他不会真正的做 而是延迟fdatasync。如果本身start还是0 设置下sart时间 然后退出,或者Unixtime-start时间小于2 也返回。如果fdatasync执行有2秒钟了 如果都不满足 那么就要做真正的fdatasync 注意了 这里就会引起write阻塞导致主服务阻塞。

nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));

这里write可能会导致了主线程阻塞并不是write 因为wirte的aof_buf并不大 基本上不会导致阻塞,而是后台的fdatasync导致write等待datasync完成了之后才调用write导致阻塞 当然这个2延迟其实是可以修改的。看你自己的应用来看。当然改多了也不行 权衡需要自己把握好。

接下来:

server.aof_current_size += nwritten;

    /* Re-use AOF buffer when it is small enough. The maximum comes from the
     * arena size of 4k minus some overhead (but is otherwise arbitrary). */
    if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
        sdsclear(server.aof_buf);
    } else {
        sdsfree(server.aof_buf);
        server.aof_buf = sdsempty();
    }

    /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
     * children doing I/O in the background. */
    if (server.aof_no_fsync_on_rewrite &&
        (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
            return;

然后处理了aof缓冲区部分其是说如果小于4k 就clear 如果不是 就free掉 这里保证了一个内存的重复利用性

如果有子进程做aof或者rdb存储工作 就拒绝调用fsync。

这里又做了相关优化: 因为前面调用的是fdatasync 没有修改文件大小元信息到磁盘里 所以呢 如果write成功之后 就调用了ftruncate(server.aof_fd, server.aof_current_size)来修改aof_fd的大小。

if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
        if (!sync_in_progress) aof_background_fsync(server.aof_fd);
        server.aof_last_fsync = server.unixtime;
    }

如果没有后台fsync 并且unixtime>aof_last_fsync(这个unixtime更新是一秒更新一次的 )则要进行fdatasync操作:aof_last_fsync改成当前时间 然后aof_backgroud_fsync把fsync加入到后台操作里面。由此可以看出来 要写出高性能的文件刷新程序 是要对linux核的文件系统要非常了解 以后有时间要把linux文件系统部分的内核操作要好好研究下。

原文地址:https://www.cnblogs.com/sfwtoms/p/3947238.html