实现写文件的原子性
最近在阅读docker源码时发现保存container configuration的时候用到atomicFileWriter
把写文件变成原子操作,源码。
实现步骤
- 使用
ioutil.TempFile
新建一个临时文件 - 跟平时一样使用
Write
写入文件 - 需要 close 文件时先调用
sync
函数保证数据落盘,然后才真正 close 文件,修改权限等,最后利用系统调用os.Rename
为什么可以这样做
第一步首先要知道TempFile是怎么实现的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21func TempFile(dir, prefix string) (f *os.File, err error) {
if dir == "" {
dir = os.TempDir()
}
nconflict := 0
for i := 0; i < 10000; i++ {
name := filepath.Join(dir, prefix+nextSuffix())
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) {
if nconflict++; nconflict > 10 {
randmu.Lock()
rand = reseed()
randmu.Unlock()
}
continue
}
break
}
return
}
主要是os.O_EXCL
参数,作用是保证os.O_CREATE
create 文件之前,保证文件不存在才创建。
for 循环是使用前缀加随机数创建临时文件保证没有冲突,重试10000次都冲突返回错误。
对于后面几步,首先要知道 inode
维基百科的定义
The inode is a data structure in a Unix-style file system that describes a filesystem object such as a file or a directory “Directory (computing)”). Each inode stores the attributes and disk block location(s) of the object’s data.[1] Filesystem object attributes may include metadata (times of last change,[2] access, modification), as well as owner and permission data.[3]
inode 是unix类文件系统中的数据结构,用于描述文件系统对象,例如文件或者目录。每一个inode都存储着对象的属性和数据存储在磁盘块的位置。文件系统对象属性可能包含了元信息,拥有者和权限。
rm
当我们使用rm
删除文件时,首先会删除文件名对这个 inode 的链接,如果 inode 链接计数器为零并且没有进程占用,那么就会删除这个文件(旧数据会存在磁盘,只是没有inode记录)。rm
命令底层使用unlink
实现的。unlink 删除的是符号链接本身,而不是其指向的文件。
rename
当我们使用 rename
时,实际上是删除旧的 inode 节点,然后新建一个一样的,底层数据没有被删除。
总结
docker源码中实现的atomicFileWriter
,如果文件已经被读进程打开然后被写进程调用rename
,那么读进程还是可以通过文件描述符读取到“旧”的文件,但是新的文件路径已经指向新的 inode,并且可以读到新的数据。