240 lines
5.4 KiB
Go
240 lines
5.4 KiB
Go
// cache/cache.go
|
||
package cache
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"strconv"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
type CacheEntry struct {
|
||
Data []byte
|
||
Headers map[string]string
|
||
CreatedAt time.Time
|
||
ExpiresAt time.Time
|
||
Size int64
|
||
}
|
||
|
||
type MemoryCache struct {
|
||
entries sync.Map
|
||
maxSize int64
|
||
currentSize int64
|
||
ttl time.Duration
|
||
mu sync.Mutex
|
||
}
|
||
|
||
func NewMemoryCache(maxSize int64, ttl time.Duration) *MemoryCache {
|
||
mc := &MemoryCache{
|
||
maxSize: maxSize,
|
||
ttl: ttl,
|
||
}
|
||
|
||
// 启动清理协程
|
||
if ttl > 0 {
|
||
go mc.cleanup()
|
||
}
|
||
|
||
return mc
|
||
}
|
||
|
||
// Age 返回缓存条目的年龄(秒)
|
||
func (e *CacheEntry) Age() string {
|
||
age := time.Since(e.CreatedAt)
|
||
return strconv.FormatInt(int64(age.Seconds()), 10)
|
||
}
|
||
|
||
// Get 获取缓存条目
|
||
func (mc *MemoryCache) Get(key string) *CacheEntry {
|
||
val, ok := mc.entries.Load(key)
|
||
if !ok {
|
||
return nil
|
||
}
|
||
|
||
entry := val.(*CacheEntry)
|
||
|
||
// 检查是否过期
|
||
if time.Now().After(entry.ExpiresAt) {
|
||
mc.Delete(key)
|
||
return nil
|
||
}
|
||
|
||
return entry
|
||
}
|
||
|
||
// Set 设置缓存条目
|
||
func (mc *MemoryCache) Set(key string, data []byte, headers map[string]string) bool {
|
||
// 如果禁用缓存(maxSize = 0),直接返回
|
||
if mc.maxSize == 0 {
|
||
return false
|
||
}
|
||
|
||
size := int64(len(data))
|
||
|
||
// 如果单个条目超过最大缓存大小,不缓存
|
||
if size > mc.maxSize {
|
||
return false
|
||
}
|
||
|
||
mc.mu.Lock()
|
||
defer mc.mu.Unlock()
|
||
|
||
// 检查是否超过最大缓存大小
|
||
if mc.currentSize+size > mc.maxSize {
|
||
// 尝试清理过期条目
|
||
mc.evictExpired()
|
||
|
||
// 如果还是不够,使用 LRU 清理
|
||
if mc.currentSize+size > mc.maxSize {
|
||
mc.evictOldest(size)
|
||
}
|
||
}
|
||
|
||
now := time.Now()
|
||
entry := &CacheEntry{
|
||
Data: data,
|
||
Headers: headers,
|
||
CreatedAt: now,
|
||
ExpiresAt: now.Add(mc.ttl),
|
||
Size: size,
|
||
}
|
||
|
||
// 如果 key 已存在,先删除旧的
|
||
if oldVal, exists := mc.entries.Load(key); exists {
|
||
oldEntry := oldVal.(*CacheEntry)
|
||
mc.currentSize -= oldEntry.Size
|
||
}
|
||
|
||
mc.entries.Store(key, entry)
|
||
mc.currentSize += size
|
||
|
||
return true
|
||
}
|
||
|
||
// Delete 删除缓存条目
|
||
func (mc *MemoryCache) Delete(key string) {
|
||
val, ok := mc.entries.LoadAndDelete(key)
|
||
if ok {
|
||
entry := val.(*CacheEntry)
|
||
mc.mu.Lock()
|
||
mc.currentSize -= entry.Size
|
||
mc.mu.Unlock()
|
||
}
|
||
}
|
||
|
||
// Clear 清空所有缓存
|
||
func (mc *MemoryCache) Clear() {
|
||
mc.mu.Lock()
|
||
defer mc.mu.Unlock()
|
||
|
||
mc.entries.Range(func(key, value interface{}) bool {
|
||
mc.entries.Delete(key)
|
||
return true
|
||
})
|
||
|
||
mc.currentSize = 0
|
||
}
|
||
|
||
// GenerateKey 生成缓存键
|
||
func (mc *MemoryCache) GenerateKey(url string) string {
|
||
hash := sha256.Sum256([]byte(url))
|
||
return hex.EncodeToString(hash[:])
|
||
}
|
||
|
||
// evictExpired 清理过期条目
|
||
func (mc *MemoryCache) evictExpired() {
|
||
now := time.Now()
|
||
mc.entries.Range(func(key, value interface{}) bool {
|
||
entry := value.(*CacheEntry)
|
||
if now.After(entry.ExpiresAt) {
|
||
mc.entries.Delete(key)
|
||
mc.currentSize -= entry.Size
|
||
}
|
||
return true
|
||
})
|
||
}
|
||
|
||
// evictOldest 清理最旧的条目
|
||
func (mc *MemoryCache) evictOldest(needed int64) {
|
||
type entryWithKey struct {
|
||
key string
|
||
entry *CacheEntry
|
||
}
|
||
|
||
var entries []entryWithKey
|
||
|
||
mc.entries.Range(func(key, value interface{}) bool {
|
||
entries = append(entries, entryWithKey{
|
||
key: key.(string),
|
||
entry: value.(*CacheEntry),
|
||
})
|
||
return true
|
||
})
|
||
|
||
// 按创建时间排序(最旧的在前)
|
||
for i := 0; i < len(entries)-1; i++ {
|
||
for j := i + 1; j < len(entries); j++ {
|
||
if entries[i].entry.CreatedAt.After(entries[j].entry.CreatedAt) {
|
||
entries[i], entries[j] = entries[j], entries[i]
|
||
}
|
||
}
|
||
}
|
||
|
||
// 删除最旧的条目直到有足够空间
|
||
freed := int64(0)
|
||
for _, e := range entries {
|
||
if freed >= needed {
|
||
break
|
||
}
|
||
mc.entries.Delete(e.key)
|
||
mc.currentSize -= e.entry.Size
|
||
freed += e.entry.Size
|
||
}
|
||
}
|
||
|
||
// cleanup 定期清理过期条目
|
||
func (mc *MemoryCache) cleanup() {
|
||
ticker := time.NewTicker(10 * time.Minute)
|
||
defer ticker.Stop()
|
||
|
||
for range ticker.C {
|
||
mc.mu.Lock()
|
||
mc.evictExpired()
|
||
mc.mu.Unlock()
|
||
}
|
||
}
|
||
|
||
// Stats 返回缓存统计信息
|
||
func (mc *MemoryCache) Stats() (entries int, size int64) {
|
||
count := 0
|
||
mc.entries.Range(func(key, value interface{}) bool {
|
||
count++
|
||
return true
|
||
})
|
||
return count, mc.currentSize
|
||
}
|
||
|
||
// GetStats 返回详细的缓存统计信息
|
||
func (mc *MemoryCache) GetStats() map[string]interface{} {
|
||
entries, size := mc.Stats()
|
||
|
||
return map[string]interface{}{
|
||
"entries": entries,
|
||
"size": size,
|
||
"max_size": mc.maxSize,
|
||
"utilization": float64(size) / float64(mc.maxSize) * 100,
|
||
"ttl_seconds": int64(mc.ttl.Seconds()),
|
||
}
|
||
}
|
||
|
||
|
||
func (mc *MemoryCache) Stats() (entries int, size int64) {
|
||
count := 0
|
||
mc.entries.Range(func(key, value interface{}) bool {
|
||
count++
|
||
return true
|
||
})
|
||
return count, mc.currentSize
|
||
}
|