ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NeDB凭什么扛住崩溃?崩溃安全写入源码剖析:临时文件+fsync+原子rename

NeDB凭什么扛住崩溃?崩溃安全写入源码剖析:临时文件+fsync+原子rename NeDB凭什么扛住崩溃崩溃安全写入源码剖析临时文件fsync原子rename【免费下载链接】nedbThe JavaScript Database, for Node.js, nw.js, electron and the browser项目地址: https://gitcode.com/gh_mirrors/ne/nedbNeDB 是一款纯 JavaScript 写的嵌入式数据库用于 Node.js、Electron 和浏览器场景没有任何二进制依赖。它的崩溃安全写入机制——临时文件 fsync 原子 rename——保证即使断电或进程被强杀数据一条不丢、不会出现半截损坏的文件整套方案藏在不到 150 行代码里非常值得精读。一、为什么普通写文件会在崩溃时丢数据调用 Node.js 的fs.writeFile时数据其实先进操作系统页缓存由内核决定何时真正落盘。如果此时断电或进程被杀页缓存丢失 → 文件内容残缺更糟的是直接覆盖原文件写了一半 → 旧版本也被毁掉所以崩溃安全写入必须回答三个问题写新版本时如何保证原文件不被弄坏→临时文件如何保证数据真的在盘上而不是只躺在缓存→fsync如何一次性干净地切换新旧版本→原子 renameNeDB 在 lib/storage.js 里把这三个问题全部回答了。二、先看全景NeDB 的持久化模型日常增删改走的是append-only模型数据文件从中间不动新内容一律追加到文件末尾。lib/persistence.js 的persistNewState里更新和删除也是以新状态的形式追加一行记录追加是单次写入快且原子性天然。只有整文件重写压缩 / compaction时才走崩溃安全写入通道触发时机有三种每次loadDatabase时由 lib/persistence.js 的persistCachedDatabase全量重写手动调用persistence.compactDatafile()通过setAutocompactionInterval设置自动压缩间隔源码强制最小 5 秒 为什么这么设计因为压缩是低频操作为它付出先写临时文件 两次 fsync的代价是划算的高频操作走追加性能拉满。三、核心剖析crashSafeWriteFile 的 6 步流水线lib/storage.js 的crashSafeWriteFile是崩溃安全的灵魂用async.waterfall串联 6 步步骤动作目的1fsync 数据文件所在目录保证临时文件的目录项真正落盘2若原文件存在先 fsync 原文件让盘上的旧版本做实再动手3writeFile写入filename~临时文件新内容完整写一份不碰原文件4fsync 临时文件把新内容刷到物理磁盘5rename(临时文件, 原文件名)原子替换一步到位6再次 fsync 目录让 rename 后的目录项持久化3.1 第一块拼图~临时文件临时文件名固定为filename ~见 lib/storage.js。由于从不直接覆写原文件磁盘上任何时刻只可能是两种状态之一原文件 完整的旧版本临时文件 可能不完整的新版本NeDB 甚至在构造时就禁止用户数据库文件名以~结尾防止与崩溃备份文件冲突见 lib/persistence.js 的显式报错。3.2 第二块拼图fsync 把页缓存真正刷到盘lib/storage.js 的flushToStorage先fs.open拿到文件句柄再调fs.fsync(fd)——这是数据离开可能丢失区的唯一时刻。两个细节值得注意目录也要 fsyncLinux 上目录项文件名到 inode 的映射同样被缓存不刷新的话重启后可能找回的是旧文件名Windows 豁免Windows 无法对目录 fsynclib/storage.js 对 win32/win64 直接跳过目录刷新源码注释坦承这是权衡——除了首次加载时恰好崩溃这种极小概率场景不会造成 100% 数据丢失3.3 第三块拼图原子 renamePOSIX 系统上rename是原子操作读者眼里原文件要么是完整旧版、要么是完整新版绝不存在半新半旧。NeDB 的storage.rename就是 lib/storage.js 里对fs.rename的直通封装。✅ 三步合起来就是写到一边 → 刷盘做实 → 原子切换 → 刷新目录项。断电切在任何一步你拿到的要么是可用的旧版要么是完整的新版。四、崩溃之后ensureDatafileIntegrity 自动善后如果进程恰好死在临时文件写了一半下次启动时磁盘上会残留完好的原文件 半个临时文件。lib/storage.js 的ensureDatafileIntegrity负责三分法善后原文件存在→ 写入成功过直接放行原文件与临时文件都不存在→ 全新数据库创建空数据文件原文件丢失但临时文件在→ 说明这次写入差临门一脚把临时文件 rename 回正式文件名救回数据⚠️ 注意这一步被放在loadDatabase流水线的最前端执行见 lib/persistence.js意味着用户永远看不到残留的~文件——数据库自己会打扫战场。五、真实验证一个故意杀死进程的测试作者没有停留在纸上谈兵。test/persistence.test.js 里有一个端到端的崩溃演习写入一个约 150KB、500 条记录的数据文件在子进程中加载数据库而 test_lac/loadAndCrash.test.js 提前猴补丁改写了fs.writeFile临时文件刚写满 5000 字节就process.exit(1)模拟硬崩溃崩溃后断言原文件长度分毫未动临时文件恰好停在 5000 字节重新加载数据库500 条记录一条不少临时文件被自动清理文件系统恢复干净这套测试证明了一句话整文件重写过程中的任何时刻崩溃旧数据零丢失。六、几个值得记住的工程细节追加写并不每次 fsync为性能最坏情况只是最后一行被截断。而 lib/persistence.js 的treatRawData重放时会跳过损坏行初始计数corruptItems -1正是为文件末尾正常空行留的容错lib/persistence.js——追加式设计与容错读取互为兜底损坏率熔断损坏行占比超过corruptAlertThreshold默认 10%时NeDB 拒绝启动lib/persistence.js防止用错反序列化钩子导致静默吞数据序列化钩子必须成对只配afterSerialization或beforeDeserialization其中一个NeDB 直接拒绝启动lib/persistence.js还会用随机字符串验证两个钩子互为逆操作纯内存模式全跳过inMemoryOnly会在每个持久化方法开头直接短路返回七、一句话总结崩溃风险点NeDB 的解法源码位置原文件被写坏新内容先完整写入~临时文件lib/storage.js数据停留在页缓存文件 目录双 fsynclib/storage.js新旧版本切换瞬间崩溃POSIX 原子 renamelib/storage.js崩溃后残留临时文件ensureDatafileIntegrity三分法善后lib/storage.js方案是否真有效写 5000 字节即杀进程的端到端测试test/persistence.test.js这套设计的精妙之处在于它没有发明任何新东西——临时文件、fsync、rename 全是操作系统最基础的积木拼图的顺序和完整性才是关键。对于任何想写文件型存储的工程师lib/storage.js 里这条 6 步流水线都可以直接当作参考实现来抄作业。【免费下载链接】nedbThe JavaScript Database, for Node.js, nw.js, electron and the browser项目地址: https://gitcode.com/gh_mirrors/ne/nedb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表