lolly/internal/cache/sharded_cache.go
xfy f145a8770e refactor: modernize code with Go 1.22+ features
Apply modern Go patterns across the codebase:
- Replace `interface{}` with `any` (Go 1.18+)
- Use `for range n` instead of `for i := 0; i < n; i++` (Go 1.22+)
- Replace `sort.Slice` with `slices.Sort` from slices package
- Simplify sync.WaitGroup patterns with errgroup where appropriate
- Add Makefile targets for modernize analyzer

Total: 84 files updated, net reduction of 79 lines

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 10:37:45 +08:00

266 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package cache 提供分片缓存实现,用于高并发场景。
//
// 该文件实现分片文件缓存,将缓存分散到多个独立分片,
// 每个分片有自己的锁和 LRU 链表,减少锁竞争。
//
// 主要用途:
//
// 用于高并发场景下的文件缓存,减少单一 RWMutex 的竞争压力。
//
// 注意事项:
// - 分片数固定为 16按 path hash 选择分片
// - 各分片独立 LRU 淘汰,无法跨分片协调
// - 适合读多写少场景,写入仍会阻塞单个分片
//
// 作者xfy
package cache
import (
"container/list"
"hash/fnv"
"sync"
"time"
)
const shardCount = 16
// FileCacheShard 单个缓存分片。
type FileCacheShard struct {
entries map[string]*FileEntry
lruList *list.List
maxEntries int64
maxSize int64
inactive time.Duration
currentSize int64
mu sync.RWMutex
entryPool sync.Pool
}
// ShardedFileCache 分片文件缓存。
type ShardedFileCache struct {
shards [shardCount]*FileCacheShard
maxEntries int64
maxSize int64
inactive time.Duration
}
// NewShardedFileCache 创建分片文件缓存实例。
func NewShardedFileCache(maxEntries, maxSize int64, inactive time.Duration) *ShardedFileCache {
s := &ShardedFileCache{
maxEntries: maxEntries,
maxSize: maxSize,
inactive: inactive,
}
perShardEntries := maxEntries / shardCount
if perShardEntries == 0 {
perShardEntries = maxEntries // 单分片容量
}
perShardSize := maxSize / shardCount
if perShardSize == 0 {
perShardSize = maxSize
}
for i := range shardCount {
shard := &FileCacheShard{
maxEntries: perShardEntries,
maxSize: perShardSize,
inactive: inactive,
entries: make(map[string]*FileEntry),
lruList: list.New(),
}
shard.entryPool = sync.Pool{
New: func() any {
return &FileEntry{}
},
}
s.shards[i] = shard
}
return s
}
// getShard 根据 path hash 选择分片。
func (s *ShardedFileCache) getShard(path string) *FileCacheShard {
h := fnv.New64a()
h.Write([]byte(path))
return s.shards[h.Sum64()%shardCount]
}
// Get 获取缓存的文件。
func (s *ShardedFileCache) Get(path string) (*FileEntry, bool) {
return s.getShard(path).Get(path)
}
// Set 设置缓存条目。
func (s *ShardedFileCache) Set(path string, data []byte, size int64, modTime time.Time) error {
return s.getShard(path).Set(path, data, size, modTime)
}
// Delete 删除缓存条目。
func (s *ShardedFileCache) Delete(path string) {
s.getShard(path).Delete(path)
}
// Clear 清空所有分片缓存。
func (s *ShardedFileCache) Clear() {
for _, shard := range s.shards {
shard.Clear()
}
}
// Stats 返回汇总统计信息。
func (s *ShardedFileCache) Stats() FileCacheStats {
totalEntries := int64(0)
totalSize := int64(0)
for _, shard := range s.shards {
stats := shard.Stats()
totalEntries += stats.Entries
totalSize += stats.Size
}
return FileCacheStats{
Entries: totalEntries,
MaxEntries: s.maxEntries,
Size: totalSize,
MaxSize: s.maxSize,
}
}
// --- FileCacheShard 方法(内联实现,避免依赖 FileCache---
// Get 获取缓存的文件。
func (sh *FileCacheShard) Get(path string) (*FileEntry, bool) {
sh.mu.RLock()
entry, ok := sh.entries[path]
if !ok {
sh.mu.RUnlock()
return nil, false
}
// 检查是否过期
if time.Since(entry.LastAccess) > sh.inactive {
sh.mu.RUnlock()
sh.mu.Lock()
if entry, ok = sh.entries[path]; ok && time.Since(entry.LastAccess) > sh.inactive {
sh.removeEntry(entry)
}
sh.mu.Unlock()
return nil, false
}
sh.mu.RUnlock()
// 更新访问时间(需写锁)
sh.mu.Lock()
if entry, ok = sh.entries[path]; ok && time.Since(entry.LastAccess) <= sh.inactive {
entry.LastAccess = time.Now()
sh.lruList.MoveToFront(entry.element)
}
sh.mu.Unlock()
return entry, true
}
// Set 设置缓存条目。
func (sh *FileCacheShard) Set(path string, data []byte, size int64, modTime time.Time) error {
sh.mu.Lock()
defer sh.mu.Unlock()
if entry, ok := sh.entries[path]; ok {
sh.currentSize -= entry.Size
entry.Data = data
entry.Size = size
entry.ModTime = modTime
entry.CachedAt = time.Now()
entry.LastAccess = time.Now()
sh.currentSize += size
sh.lruList.MoveToFront(entry.element)
sh.evictIfNeeded()
return nil
}
entry := sh.entryPool.Get().(*FileEntry)
entry.Path = path
entry.Data = data
entry.Size = size
entry.ModTime = modTime
entry.CachedAt = time.Now()
entry.LastAccess = time.Now()
entry.element = sh.lruList.PushFront(entry)
sh.entries[path] = entry
sh.currentSize += size
sh.evictIfNeeded()
return nil
}
// Delete 删除缓存条目。
func (sh *FileCacheShard) Delete(path string) {
sh.mu.Lock()
defer sh.mu.Unlock()
if entry, ok := sh.entries[path]; ok {
sh.removeEntry(entry)
}
}
// Clear 清空分片缓存。
func (sh *FileCacheShard) Clear() {
sh.mu.Lock()
defer sh.mu.Unlock()
sh.entries = make(map[string]*FileEntry)
sh.lruList = list.New()
sh.currentSize = 0
}
// Stats 返回分片统计信息。
func (sh *FileCacheShard) Stats() FileCacheStats {
sh.mu.RLock()
defer sh.mu.RUnlock()
return FileCacheStats{
Entries: int64(len(sh.entries)),
MaxEntries: sh.maxEntries,
Size: sh.currentSize,
MaxSize: sh.maxSize,
}
}
// removeEntry 内部删除条目(不加锁)。
func (sh *FileCacheShard) removeEntry(entry *FileEntry) {
sh.lruList.Remove(entry.element)
delete(sh.entries, entry.Path)
sh.currentSize -= entry.Size
// Reset entry 并放回池
entry.Path = ""
entry.Data = nil
entry.Size = 0
entry.ModTime = time.Time{}
entry.CachedAt = time.Time{}
entry.LastAccess = time.Time{}
entry.element = nil
sh.entryPool.Put(entry)
}
// evictIfNeeded 根据限制淘汰条目。
func (sh *FileCacheShard) evictIfNeeded() {
for sh.lruList.Len() > int(sh.maxEntries) && sh.maxEntries > 0 {
sh.evictLRU()
}
for sh.currentSize > sh.maxSize && sh.maxSize > 0 {
sh.evictLRU()
}
}
// evictLRU 淘汰最久未使用的条目。
func (sh *FileCacheShard) evictLRU() {
if sh.lruList.Len() == 0 {
return
}
element := sh.lruList.Back()
if element == nil {
return
}
entry, ok := element.Value.(*FileEntry)
if !ok {
return
}
sh.removeEntry(entry)
}