庄周梦蝶

生活、程序、未来

声明:本博客所有文章,未经允许,禁止转载。谢谢。

微信 phxpaxos 源码解读:fsync 和 fdatasync

| Comments

最近在读微信开源的 paxos 实现 phxpaxos,读到 localstorage 部分学习到 fdatasync 系统调用。这一部分是非常核心的存储模块,参与者的状态信息、变更日志等都要写入磁盘并且可能要求强制刷入存储磁盘避免系统崩溃等情况下数据丢失,性能的很大一部分因素取决于这一块的实现。

phxpaxos 使用了 LevelDB 做存储,但是做了几个优化:

  • LevelDB 存储的 value 只是一个 24 个字节的 fileid 索引(有一个例外 minchosen 值),真正的状态数据存储在他们自己实现的 log_store 里, fileid 存储了在 log_store 里的文件编号、写入 vfile 的offset 和 checksum 这三个字段信息。这样做的原因是由于 leveldb 对于比较大的 value 的存取效率不是最优,通过间接一层存储,利用了 LevelDB 的 Key 的有序性和小 value 存储的性能优势,加上定制的 log_store 的写入优化,达到最优组合。
  • log_store 按照文件编号固定增长,比如 1.f2.f3.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 同时刷入数据和元信息

1
2
3
4
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 如无必要,只刷入数据:

1
2
3
 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 来优化刷盘性能,而访问时间和修改时间之类的元信息丢失对业务没有影响。

声明:本博客所有文章,未经允许,禁止转载。谢谢。

linux, 性能, 文件系统

« Clojure Under a Microscope(1): Clojure 如何理解代码(上) Clojure Under a Microscope(1):Clojure 如何理解代码(下) »

Comments