设计思路
- 原子性操作:使用
os
包的文件操作函数,在写文件时先写入临时文件,然后通过os.Rename
原子性地替换目标文件。对于读操作,由于Go的文件读取操作本身是原子的(针对单个读取调用),不需要额外处理。
- 一致性:通过文件锁保证同一时间只有一个协程可以对文件进行写操作。读操作在获取文件锁后进行,避免在写操作过程中读取到不一致的数据。
- 高性能:引入缓存机制,对于频繁读取的文件,将其内容缓存起来,减少文件读取次数。同时,合理地复用文件描述符,避免频繁打开和关闭文件。
- 文件锁:使用
syscall.Flock
函数实现文件锁,它在不同操作系统(Linux、Windows等)下都有较好的兼容性。
- 缓存:使用
map
来实现简单的缓存,键为文件名,值为文件内容。
- 操作系统兼容性:除了使用
syscall.Flock
保证文件锁的兼容性外,在文件路径处理上使用path/filepath
包,它可以根据不同操作系统自动处理路径分隔符。
关键代码示例
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"sync"
)
var (
fileCache = make(map[string][]byte)
cacheLock sync.RWMutex
fileLocks = make(map[string]*os.File)
lockMutex sync.Mutex
)
// 获取文件锁
func acquireLock(filePath string) (*os.File, error) {
lockMutex.Lock()
defer lockMutex.Unlock()
if f, ok := fileLocks[filePath]; ok {
return f, nil
}
lockFile, err := os.OpenFile(filepath.Join(filePath, ".lock"), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX)
if err != nil {
lockFile.Close()
return nil, err
}
fileLocks[filePath] = lockFile
return lockFile, nil
}
// 释放文件锁
func releaseLock(filePath string) {
lockMutex.Lock()
defer lockMutex.Unlock()
if f, ok := fileLocks[filePath]; ok {
f.Close()
os.Remove(filepath.Join(filePath, ".lock"))
delete(fileLocks, filePath)
}
}
// 读取文件内容
func readFile(filePath string) ([]byte, error) {
cacheLock.RLock()
if content, ok := fileCache[filePath]; ok {
cacheLock.RUnlock()
return content, nil
}
cacheLock.RUnlock()
lockFile, err := acquireLock(filePath)
if err != nil {
return nil, err
}
defer releaseLock(filePath)
content, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
cacheLock.Lock()
fileCache[filePath] = content
cacheLock.Unlock()
return content, nil
}
// 写入文件内容
func writeFile(filePath string, content []byte) error {
lockFile, err := acquireLock(filePath)
if err != nil {
return err
}
defer releaseLock(filePath)
tempFilePath := filePath + ".tmp"
err = ioutil.WriteFile(tempFilePath, content, 0644)
if err != nil {
return err
}
err = os.Rename(tempFilePath, filePath)
if err != nil {
os.Remove(tempFilePath)
return err
}
cacheLock.Lock()
fileCache[filePath] = content
cacheLock.Unlock()
return nil
}