Files
pansou/util/cache/sharded_memory_cache.go
2025-09-10 20:05:35 +08:00

390 lines
9.3 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
import (
"hash/fnv"
"runtime"
"sync"
"sync/atomic"
"time"
)
// 全局清理任务相关变量(单例模式)
var (
globalCleanupTicker *time.Ticker
globalCleanupOnce sync.Once
registeredCaches []cleanupTarget
cacheRegistryMutex sync.RWMutex
)
// 清理目标接口
type cleanupTarget interface {
CleanExpired()
}
// 分片内存缓存项
type shardedMemoryCacheItem struct {
data []byte
expiry time.Time
lastUsed int64 // 使用原子操作的时间戳
lastModified time.Time
size int
}
// 单个分片
type memoryCacheShard struct {
items map[string]*shardedMemoryCacheItem
mutex sync.RWMutex
currSize int64
}
// 分片内存缓存
type ShardedMemoryCache struct {
shards []*memoryCacheShard
shardMask uint32 // 用于快速取模的掩码
maxItems int
maxSize int64
itemsPerShard int
sizePerShard int64
diskCache *ShardedDiskCache // 磁盘缓存引用
diskCacheMutex sync.RWMutex // 磁盘缓存引用的保护锁
}
// 创建新的分片内存缓存
func NewShardedMemoryCache(maxItems int, maxSizeMB int) *ShardedMemoryCache {
// 动态确定分片数量基于CPU核心数但至少4个最多64个
shardCount := runtime.NumCPU() * 2
if shardCount < 4 {
shardCount = 4
}
if shardCount > 64 {
shardCount = 64
}
// 确保分片数是2的幂便于使用掩码进行快速取模
shardCount = nextPowerOfTwo(shardCount)
totalSize := int64(maxSizeMB) * 1024 * 1024
itemsPerShard := maxItems / shardCount
sizePerShard := totalSize / int64(shardCount)
shards := make([]*memoryCacheShard, shardCount)
for i := 0; i < shardCount; i++ {
shards[i] = &memoryCacheShard{
items: make(map[string]*shardedMemoryCacheItem),
}
}
return &ShardedMemoryCache{
shards: shards,
shardMask: uint32(shardCount - 1), // 用于快速取模
maxItems: maxItems,
maxSize: totalSize,
itemsPerShard: itemsPerShard,
sizePerShard: sizePerShard,
}
}
// 获取下一个2的幂
func nextPowerOfTwo(n int) int {
if n <= 1 {
return 1
}
n--
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
return n + 1
}
// 获取分片
func (c *ShardedMemoryCache) getShard(key string) *memoryCacheShard {
h := fnv.New32a()
h.Write([]byte(key))
shardIndex := h.Sum32() & c.shardMask // 使用掩码进行快速取模
return c.shards[shardIndex]
}
// 设置缓存
func (c *ShardedMemoryCache) Set(key string, data []byte, ttl time.Duration) {
c.SetWithTimestamp(key, data, ttl, time.Now())
}
// SetWithTimestamp 设置缓存,并指定最后修改时间
func (c *ShardedMemoryCache) SetWithTimestamp(key string, data []byte, ttl time.Duration, lastModified time.Time) {
shard := c.getShard(key)
shard.mutex.Lock()
defer shard.mutex.Unlock()
// 如果已存在,先减去旧项的大小
if item, exists := shard.items[key]; exists {
atomic.AddInt64(&shard.currSize, -int64(item.size))
}
// 创建新的缓存项
now := time.Now()
item := &shardedMemoryCacheItem{
data: data,
expiry: now.Add(ttl),
lastUsed: now.UnixNano(),
lastModified: lastModified,
size: len(data),
}
// 检查是否需要清理空间
if len(shard.items) >= c.itemsPerShard || shard.currSize+int64(len(data)) > c.sizePerShard {
c.evictFromShard(shard)
}
// 存储新项
shard.items[key] = item
atomic.AddInt64(&shard.currSize, int64(len(data)))
}
// 获取缓存
func (c *ShardedMemoryCache) Get(key string) ([]byte, bool) {
shard := c.getShard(key)
shard.mutex.RLock()
item, exists := shard.items[key]
shard.mutex.RUnlock()
if !exists {
return nil, false
}
// 检查是否过期
if time.Now().After(item.expiry) {
shard.mutex.Lock()
delete(shard.items, key)
atomic.AddInt64(&shard.currSize, -int64(item.size))
shard.mutex.Unlock()
return nil, false
}
// 原子操作更新最后使用时间,避免额外的锁
atomic.StoreInt64(&item.lastUsed, time.Now().UnixNano())
return item.data, true
}
// GetWithTimestamp 获取缓存及其最后修改时间
func (c *ShardedMemoryCache) GetWithTimestamp(key string) ([]byte, time.Time, bool) {
shard := c.getShard(key)
shard.mutex.RLock()
item, exists := shard.items[key]
shard.mutex.RUnlock()
if !exists {
return nil, time.Time{}, false
}
// 检查是否过期
if time.Now().After(item.expiry) {
shard.mutex.Lock()
delete(shard.items, key)
atomic.AddInt64(&shard.currSize, -int64(item.size))
shard.mutex.Unlock()
return nil, time.Time{}, false
}
// 原子操作更新最后使用时间
atomic.StoreInt64(&item.lastUsed, time.Now().UnixNano())
return item.data, item.lastModified, true
}
// GetLastModified 获取缓存项的最后修改时间
func (c *ShardedMemoryCache) GetLastModified(key string) (time.Time, bool) {
shard := c.getShard(key)
shard.mutex.RLock()
defer shard.mutex.RUnlock()
item, exists := shard.items[key]
if !exists {
return time.Time{}, false
}
// 检查是否过期
if time.Now().After(item.expiry) {
return time.Time{}, false
}
return item.lastModified, true
}
// 从指定分片中驱逐最久未使用的项(带磁盘备份)
func (c *ShardedMemoryCache) evictFromShard(shard *memoryCacheShard) {
var oldestKey string
var oldestItem *shardedMemoryCacheItem
var oldestTime int64 = 9223372036854775807 // int64最大值
for k, v := range shard.items {
lastUsed := atomic.LoadInt64(&v.lastUsed)
if lastUsed < oldestTime {
oldestKey = k
oldestItem = v
oldestTime = lastUsed
}
}
// 如果找到了最久未使用的项,删除它
if oldestKey != "" && oldestItem != nil {
// 🔥 关键优化:淘汰前检查是否需要刷盘保护
diskCache := c.getDiskCacheReference()
if time.Now().Before(oldestItem.expiry) && diskCache != nil {
// 数据还没过期,异步刷新到磁盘保存
go func(key string, data []byte, expiry time.Time) {
ttl := time.Until(expiry)
if ttl > 0 {
diskCache.Set(key, data, ttl) // 保持相同TTL
}
}(oldestKey, oldestItem.data, oldestItem.expiry)
}
// 从内存中删除
atomic.AddInt64(&shard.currSize, -int64(oldestItem.size))
delete(shard.items, oldestKey)
}
}
// 清理过期项
func (c *ShardedMemoryCache) CleanExpired() {
now := time.Now()
// 并行清理所有分片
var wg sync.WaitGroup
for _, shard := range c.shards {
wg.Add(1)
go func(s *memoryCacheShard) {
defer wg.Done()
s.mutex.Lock()
defer s.mutex.Unlock()
for k, v := range s.items {
if now.After(v.expiry) {
atomic.AddInt64(&s.currSize, -int64(v.size))
delete(s.items, k)
}
}
}(shard)
}
wg.Wait()
}
// Delete 删除指定键的缓存项
func (c *ShardedMemoryCache) Delete(key string) {
shard := c.getShard(key)
shard.mutex.Lock()
defer shard.mutex.Unlock()
if item, exists := shard.items[key]; exists {
atomic.AddInt64(&shard.currSize, -int64(item.size))
delete(shard.items, key)
}
}
// Clear 清空所有缓存项
func (c *ShardedMemoryCache) Clear() {
// 并行清理所有分片
var wg sync.WaitGroup
for _, shard := range c.shards {
wg.Add(1)
go func(s *memoryCacheShard) {
defer wg.Done()
s.mutex.Lock()
defer s.mutex.Unlock()
s.items = make(map[string]*shardedMemoryCacheItem)
atomic.StoreInt64(&s.currSize, 0)
}(shard)
}
wg.Wait()
}
// 启动全局清理任务(单例模式)
func startGlobalCleanupTask() {
globalCleanupOnce.Do(func() {
globalCleanupTicker = time.NewTicker(5 * time.Minute)
go func() {
for range globalCleanupTicker.C {
cacheRegistryMutex.RLock()
caches := make([]cleanupTarget, len(registeredCaches))
copy(caches, registeredCaches)
cacheRegistryMutex.RUnlock()
// 并行清理所有注册的缓存
for _, cache := range caches {
go cache.CleanExpired()
}
}
}()
})
}
// 注册缓存到全局清理任务
func registerForCleanup(cache cleanupTarget) {
cacheRegistryMutex.Lock()
defer cacheRegistryMutex.Unlock()
registeredCaches = append(registeredCaches, cache)
}
// 启动定期清理(修改为使用单例模式)
func (c *ShardedMemoryCache) StartCleanupTask() {
registerForCleanup(c)
startGlobalCleanupTask()
}
// SetDiskCacheReference 设置磁盘缓存引用
func (c *ShardedMemoryCache) SetDiskCacheReference(diskCache *ShardedDiskCache) {
c.diskCacheMutex.Lock()
defer c.diskCacheMutex.Unlock()
c.diskCache = diskCache
}
// getDiskCacheReference 获取磁盘缓存引用
func (c *ShardedMemoryCache) getDiskCacheReference() *ShardedDiskCache {
c.diskCacheMutex.RLock()
defer c.diskCacheMutex.RUnlock()
return c.diskCache
}
// MemoryCacheItem 内存缓存项结构(用于导出)
type MemoryCacheItem struct {
Data []byte
TTL time.Duration
}
// GetAllItems 获取内存缓存中的所有项
func (c *ShardedMemoryCache) GetAllItems() map[string]*MemoryCacheItem {
result := make(map[string]*MemoryCacheItem)
now := time.Now()
// 遍历所有分片
for _, shard := range c.shards {
shard.mutex.RLock()
for key, item := range shard.items {
// 检查是否过期
if !item.expiry.IsZero() && now.After(item.expiry) {
continue // 跳过过期项
}
// 计算剩余TTL
var ttl time.Duration
if !item.expiry.IsZero() {
ttl = item.expiry.Sub(now)
if ttl <= 0 {
continue // 跳过即将过期的项
}
}
result[key] = &MemoryCacheItem{
Data: item.data,
TTL: ttl,
}
}
shard.mutex.RUnlock()
}
return result
}