mirror of
https://github.com/fish2018/pansou.git
synced 2025-11-25 03:14:59 +08:00
390 lines
9.3 KiB
Go
390 lines
9.3 KiB
Go
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
|
||
} |