理解写时复制(Copy-on-Write)
| 技术Copy-on-Write(缩写为 COW)是一种优化策略,核心思想是:共享 → 复制,但等到真正需要修改时才复制。
一个形象的例子
想象你有一本书的复印件 A。现在有人想借这本书:
- 传统方式:立刻复印一本 B 给他,花费时间和纸张
- COW 方式:先告诉他"你和 A 共用同一本,只有当你要在上面写字时,我才给你复印一本"
只有对方尝试修改(写)时,才真正分配新内存、复制内容。
npm 生态系统中的应用
npm 在安装包时(Linux 环境下),会利用 COW 文件系统(Btrfs、XFS、overlayfs 等)的特性:
- npm 把每个包的 files 内容写到 content-addressable store(如
/root/.npm) - 然后通过 hard link 或 copy-on-write 的方式把文件"放进"
node_modules - 多个包如果有相同的文件(比如都有 LICENSE),物理上只存储一份内容,节省空间
- 只有当某个实例真正需要修改文件时,才会真正复制一份出来
这是 npm v7+ 优化安装速度和磁盘占用的重要手段。
Linux 内核中的 fork()
Unix 创建进程时,fork() 调用后子进程共享父进程的内存页,只有在任一方尝试写入时才会真正复制。这大大加快了进程创建速度——Linux 的 fork() 因此能够极快完成。
写时复制文件系统
Btrfs、ZFS、XFS 等文件系统默认启用 COW:
- 同一个 npm 包在不同项目中的相同文件,在磁盘上只有一份 data extent
- 修改时才复制出新的 extent
Docker 与容器层
容器镜像的每一层就是 COW 的概念:
- 镜像每一层都是只读的
- 启动容器时加一个 COW 层
- 所有写入操作都在这个层里,底层镜像文件不被修改
数据库的 MVCC
PostgreSQL 的多版本并发控制(MVCC)使用了类似 COW 的思路:
- 事务读取不会阻塞写入
- 老数据不覆盖,只标记新版本
- vacuum 才清理旧版本
SQLite 也是类似,写操作不覆盖原数据,而是创建新 page。
虚拟化与快照
虚拟机快照的原理:
- 创建快照瞬间只是记录"从现在起新改动写到新地方"
- 原来的 disk state 保持不变
- 可以随时回滚到快照点
总结
COW 的本质是:能共享受用,写了才复制。
这种"延迟分配"的优化策略,在所有"共享一份实体、按需复制"的场景都能看到——从进程创建、文件系统、包管理器到数据库,无一例外。
适用于读多写多、共享资源粒度细、复制成本高的场景。如果频繁写入,反而会触发大量复制,失去意义。