You've already forked VptPassiveAdapter
- 添加 CacheConfig 结构体定义文件列表缓存的TTL和最大条目数 - 在RecordConfig中集成Cache配置项 - 为AliOSS和S3适配器实现统一的文件列表缓存机制 - 移除原有的sync.Map缓存实现和定时清理逻辑 - 引入go-cache依赖库实现专业的缓存管理功能 - 使用LRU算法控制缓存大小避免内存泄漏 - 通过singleflight实现缓存穿透保护和并发控制 - 更新配置文件添加缓存相关配置项 - 在.gitignore中添加.exe文件忽略规则
183 lines
3.9 KiB
Go
183 lines
3.9 KiB
Go
package fs
|
|
|
|
import (
|
|
"ZhenTuLocalPassiveAdapter/config"
|
|
"ZhenTuLocalPassiveAdapter/dto"
|
|
"container/list"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/patrickmn/go-cache"
|
|
"golang.org/x/sync/singleflight"
|
|
)
|
|
|
|
const (
|
|
defaultFileListCacheTTLSeconds = 30
|
|
defaultFileListCacheMaxEntries = 256
|
|
fileListCacheCleanupInterval = 1 * time.Minute
|
|
)
|
|
|
|
var (
|
|
s3FileListCacheOnce sync.Once
|
|
s3FileListCacheInstance *fileListCache
|
|
|
|
aliOssFileListCacheOnce sync.Once
|
|
aliOssFileListCacheInstance *fileListCache
|
|
)
|
|
|
|
func getS3FileListCache() *fileListCache {
|
|
s3FileListCacheOnce.Do(func() {
|
|
s3FileListCacheInstance = newFileListCache(getFileListCacheTTL(), getFileListCacheMaxEntries())
|
|
})
|
|
return s3FileListCacheInstance
|
|
}
|
|
|
|
func getAliOssFileListCache() *fileListCache {
|
|
aliOssFileListCacheOnce.Do(func() {
|
|
aliOssFileListCacheInstance = newFileListCache(getFileListCacheTTL(), getFileListCacheMaxEntries())
|
|
})
|
|
return aliOssFileListCacheInstance
|
|
}
|
|
|
|
func getFileListCacheTTL() time.Duration {
|
|
ttlSeconds := config.Config.Record.Cache.FileListTTLSeconds
|
|
if ttlSeconds <= 0 {
|
|
ttlSeconds = defaultFileListCacheTTLSeconds
|
|
}
|
|
return time.Duration(ttlSeconds) * time.Second
|
|
}
|
|
|
|
func getFileListCacheMaxEntries() int {
|
|
maxEntries := config.Config.Record.Cache.FileListMaxEntries
|
|
if maxEntries <= 0 {
|
|
maxEntries = defaultFileListCacheMaxEntries
|
|
}
|
|
return maxEntries
|
|
}
|
|
|
|
type fileListCache struct {
|
|
cache *cache.Cache
|
|
maxEntries int
|
|
|
|
mu sync.Mutex
|
|
lru *list.List
|
|
elements map[string]*list.Element
|
|
|
|
group singleflight.Group
|
|
}
|
|
|
|
func newFileListCache(ttl time.Duration, maxEntries int) *fileListCache {
|
|
if ttl <= 0 {
|
|
ttl = defaultFileListCacheTTLSeconds * time.Second
|
|
}
|
|
if maxEntries <= 0 {
|
|
maxEntries = defaultFileListCacheMaxEntries
|
|
}
|
|
|
|
c := cache.New(ttl, fileListCacheCleanupInterval)
|
|
result := &fileListCache{
|
|
cache: c,
|
|
maxEntries: maxEntries,
|
|
lru: list.New(),
|
|
elements: make(map[string]*list.Element, maxEntries),
|
|
}
|
|
c.OnEvicted(func(key string, value any) {
|
|
result.removeFromLru(key)
|
|
})
|
|
return result
|
|
}
|
|
|
|
func (c *fileListCache) GetOrLoad(key string, loader func() ([]dto.File, error)) ([]dto.File, bool, bool, error) {
|
|
if files, ok := c.Get(key); ok {
|
|
return files, true, false, nil
|
|
}
|
|
|
|
value, err, shared := c.group.Do(key, func() (any, error) {
|
|
if files, ok := c.Get(key); ok {
|
|
return files, nil
|
|
}
|
|
files, err := loader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Set(key, files)
|
|
return files, nil
|
|
})
|
|
if err != nil {
|
|
return nil, false, shared, err
|
|
}
|
|
files, ok := value.([]dto.File)
|
|
if !ok {
|
|
return nil, false, shared, fmt.Errorf("缓存返回类型错误: key=%s", key)
|
|
}
|
|
return files, false, shared, nil
|
|
}
|
|
|
|
func (c *fileListCache) Get(key string) ([]dto.File, bool) {
|
|
value, ok := c.cache.Get(key)
|
|
if !ok {
|
|
c.removeFromLru(key)
|
|
return nil, false
|
|
}
|
|
files, ok := value.([]dto.File)
|
|
if !ok {
|
|
c.cache.Delete(key)
|
|
c.removeFromLru(key)
|
|
return nil, false
|
|
}
|
|
c.touch(key)
|
|
return files, true
|
|
}
|
|
|
|
func (c *fileListCache) Set(key string, files []dto.File) {
|
|
c.cache.Set(key, files, cache.DefaultExpiration)
|
|
c.touch(key)
|
|
c.evictIfNeeded()
|
|
}
|
|
|
|
func (c *fileListCache) touch(key string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if el, ok := c.elements[key]; ok {
|
|
c.lru.MoveToFront(el)
|
|
return
|
|
}
|
|
c.elements[key] = c.lru.PushFront(key)
|
|
}
|
|
|
|
func (c *fileListCache) removeFromLru(key string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
el, ok := c.elements[key]
|
|
if !ok {
|
|
return
|
|
}
|
|
c.lru.Remove(el)
|
|
delete(c.elements, key)
|
|
}
|
|
|
|
func (c *fileListCache) evictIfNeeded() {
|
|
for {
|
|
c.mu.Lock()
|
|
if len(c.elements) <= c.maxEntries {
|
|
c.mu.Unlock()
|
|
return
|
|
}
|
|
el := c.lru.Back()
|
|
if el == nil {
|
|
c.mu.Unlock()
|
|
return
|
|
}
|
|
key := el.Value.(string)
|
|
c.lru.Remove(el)
|
|
delete(c.elements, key)
|
|
c.mu.Unlock()
|
|
|
|
// 注意:不要在持有 c.mu 时调用 c.cache.Delete,否则会和 OnEvicted 回调产生锁顺序死锁风险
|
|
c.cache.Delete(key)
|
|
}
|
|
}
|