You've already forked VptPassiveAdapter
feat(config): 添加文件列表缓存配置并优化阿里云和S3适配器缓存实现
- 添加 CacheConfig 结构体定义文件列表缓存的TTL和最大条目数 - 在RecordConfig中集成Cache配置项 - 为AliOSS和S3适配器实现统一的文件列表缓存机制 - 移除原有的sync.Map缓存实现和定时清理逻辑 - 引入go-cache依赖库实现专业的缓存管理功能 - 使用LRU算法控制缓存大小避免内存泄漏 - 通过singleflight实现缓存穿透保护和并发控制 - 更新配置文件添加缓存相关配置项 - 在.gitignore中添加.exe文件忽略规则
This commit is contained in:
182
fs/file_list_cache.go
Normal file
182
fs/file_list_cache.go
Normal file
@@ -0,0 +1,182 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user