Docker容器内Node.js应用不同fs写入方式致Block IO统计异常原因
为什么使用
fs.writeSync(持打开文件描述符)时Docker容器的Block IO统计显示为0? 这是个很有意思的问题,核心差异在于两种文件写入方式在内核层面的行为不同,以及Docker的Block IO统计的底层逻辑。咱们一步步拆解:
两种写入方式的内核行为差异
先看你的两段代码的本质区别:
fs.writeFileSync流程:
每次调用都会完成完整的「打开文件 → 写入数据 → 关闭文件」循环。当Node.js在writeFileSync内部自动执行close操作时,内核会做两件关键的事:- 标记该文件的脏页(内存中未刷到磁盘的数据)为待刷盘状态
- 强制更新文件的元数据(比如修改时间、文件大小)到磁盘
其中,元数据的更新是同步触发的块设备IO操作,这部分会被Docker的统计捕捉到。
fs.writeSync流程:
你提前用fs.openSync打开文件拿到文件描述符(fd),然后重复调用writeSync写入。此时文件始终处于打开状态:- 第一次写入时,内核会更新文件元数据,但后续如果是覆盖相同大小的内容,元数据不会再变化,也就没有元数据IO
- 所有数据写入都会被缓存到内核的**页缓存(page cache)**中,内核会根据自身的脏页刷写策略(比如
vm.dirty_ratio阈值)异步刷盘,不会在每次写入后立即触发块IO
Docker Block IO统计的逻辑
docker stats里的Block IO数据来源于Linux cgroup的blkio控制器,它只统计实际通过块设备完成的读写操作——内存缓存中的操作(比如页缓存的写入)不会被计入,只有当数据真正刷到物理磁盘时,才会被统计。
对于writeFileSync的场景,每次关闭文件触发的元数据IO是立即发生的块操作,所以docker stats能抓到数据;而writeSync的场景,数据一直停留在页缓存里,直到内核触发异步刷盘(可能需要积累一定量的脏数据,或者等待一段时间),在刷盘之前,docker stats采样到的Block IO就是0。
验证方法
如果想让writeSync的场景也能在docker stats里看到Block IO,可以在每次写入后强制刷盘:
const fs = require('fs') const fd = fs.openSync(`/data/file.txt`, 'w') setInterval(() => { fs.writeSync(fd, (new Array(1024)).join('.')) fs.fsyncSync(fd) // 强制将数据和元数据刷到磁盘 }, 100)
加上fsyncSync后,每次写入都会触发实际的块设备IO,docker stats就能显示对应的Block数据了。
内容的提问来源于stack exchange,提问作者Pavel




