最近在读微信开源的 paxos 实现 phxpaxos ,读到 localstorage 部分学习到 fdatasync 系统调用。这一部分是非常核心的存储模块,参与者的状态信息、变更日志等都要写入磁盘并且可能要求强制刷入存储磁盘避免系统崩溃等情况下数据丢失,性能的很大一部分因素取决于这一块的实现。
phxpaxos 使用了 LevelDB 做存储,但是做了几个优化:
1.f
、 2.f
、 3.f
……以此类推(在日志目录的 vfile 目录下)。并且每个文件都是在创建的时候分配固定大小,默认 100M(还有所谓 large buffer 模式分配的是 500M)。值的写入都是 append 操作,写入完成后,将偏移量、校验和、当前文件 ID 再写入 LevelDB。读取的时候就先从 LevelDB 得到这三个信息,然后去对应的 vfile 读取实际的值。因为文件是固定大小分配,每次强制刷盘就不需要调用 fsync
,而是采用 fdatasync
,这样就无需做两次刷盘,因为 fdatasync
不会去刷入文件的元信息(比如大小、最后访问时间、最后修改时间等),而 fsync 是会的。 一张图来展示大概是这样:
注意到图中还有个 metafile,它存储了 log store 的当前的文件编号这个元信息,写满一个 vfile 就递增下这个计数,并更新到 metafile,为了保证不丢,对它的每次修改都需要强制 fsync。
回到文本的主题 fsync 和 fdatasync,按照 manual 的描述, fsync 同时刷入数据和元信息
fsync() transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file descriptor fd to the disk device ...... It also flushes metadata information associated with the file (see stat(2)).
而 fdatasync 如无必要,只刷入数据:
fdatasync() is similar to fsync(), but does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be correctly handled.
log_store 的实现因为提前分配了固定大小的文件,因此无需在刷盘的时候再去写入元信息,就可以使用 fdatasync 来优化刷盘性能,而访问时间和修改时间之类的元信息丢失对业务没有影响。