语言:golang
在抓取网站前,可能会多次读取文件中的cookie。频繁读取文件,性能不佳,故在go包中创建了一个map全局变量存储文件内容,只有键不存在时才去读取文件。
因为对go并不熟悉,才发现,不同请求读取到的是同一个变量(地址)。对于性能来说,这其实是好的。但出现一个问题:文件更新后,该变量的值依然是文件更新之前的内容,需要重新运行go包,而cookie随时可能失效而去更新,频繁重启这显然不现实。
解决方法
依旧采用map存储文件内容,但map的值为:1、文件的内容,2、文件的修改时间。
每次读取加读锁,并检查实际文件的修改时间,若与map中的值一致,则直接返回map中文件内容。
若不一致,则加写锁,再次检查修改时间,防止获取到写锁之前,文件已经修改。随后读取文件,获取文件中的内容,并更新内容和当前时间到map中。
type cacheEntry struct {
content []string
modTime time.Time
}
var (
fileCache = make(map[string]cacheEntry)
mu sync.RWMutex
group singleflight.Group
)
func getFileModTime(path string) (time.Time, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return time.Time{}, err
}
return fileInfo.ModTime(), nil
}
func GetFile(file string) string {
//合并请求
result, err, _ := group.Do(file, func() (interface{}, error) {
mu.RLock()
entry, ok := fileCache[file]
mu.RUnlock()
path := GetFullPath(file)
if ok {
currentModTime, err := getFileModTime(path)
if err != nil {
return nil, err
}
if currentModTime.Equal(entry.modTime) {
return entry.content, nil // 缓存有效
}
}
mu.Lock()
defer mu.Unlock()
//再次检查,避免在获取到写锁的前一刻已经更新
entry, ok = fileCache[file]
if ok {
currentModTime, err := getFileModTime(path)
if err != nil {
return nil, err
}
if currentModTime.Equal(entry.modTime) {
return entry.content, nil // 缓存有效
}
}
//读取文件并缓存
content, err := os.ReadFile(path)
if err != nil || len(content) == 0 {
return nil, errors.New("文件不存在或为空")
}
//可选,按行分割内容
lines := strings.Split(string(content), "\n")
var branchContent []string
for _, line := range lines {
if line != "" {
line = strings.TrimSpace(line)
branchContent = append(branchContent, line)
}
}
fileCache[file] = cacheEntry{
content: branchContent,
modTime: time.Now(),
}
return branchContent, nil
})
}