这是原文作者的博客 把他翻译下 带上自己的一些理解 看看作者引入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个动作。
可以看下面的图: 就能了解大概的做的一些动作:
这里我就假设你较为熟悉AOF机制,下期我会写下AOF持久化。 这样就暴露出了一个问题:
AOF创建了一个进程之后 flushAppendOnlyFile 要把AOF文件写入到磁盘里,可以试想下:主线程一方面要调用write()把aofbuf写入到内核缓冲区,而另外一个线程调用fdatasync这就比较麻烦了这样会阻止了主线程的一个阻塞动作,影响了客户端的延迟,你懂的,这不是我们所想要的,看看redis怎么做的:
首先判断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文件系统部分的内核操作要好好研究下。