first commit
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
This commit is contained in:
380
internal/cache/manager.go
vendored
Normal file
380
internal/cache/manager.go
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/subconverter-go/internal/conversion"
|
||||
"github.com/subconverter-go/internal/logging"
|
||||
)
|
||||
|
||||
// CacheEntry 缓存条目
|
||||
type CacheEntry struct {
|
||||
Response *conversion.ConversionResponse `json:"response"`
|
||||
Expiration time.Time `json:"expiration"`
|
||||
AccessCount int `json:"accessCount"`
|
||||
LastAccess time.Time `json:"lastAccess"`
|
||||
}
|
||||
|
||||
// CacheManager 缓存管理器
|
||||
// 提供转换结果的缓存功能,提高性能和减少重复计算
|
||||
type CacheManager struct {
|
||||
logger *logging.Logger
|
||||
entries map[string]*CacheEntry
|
||||
mutex sync.RWMutex
|
||||
ttl time.Duration
|
||||
maxSize int
|
||||
enabled bool
|
||||
stats *CacheStats
|
||||
cleanupChan chan struct{}
|
||||
}
|
||||
|
||||
// CacheStats 缓存统计信息
|
||||
type CacheStats struct {
|
||||
Hits int64 `json:"hits"`
|
||||
Misses int64 `json:"misses"`
|
||||
Evictions int64 `json:"evictions"`
|
||||
Size int64 `json:"size"`
|
||||
TotalSize int64 `json:"totalSize"`
|
||||
HitRate float64 `json:"hitRate"`
|
||||
}
|
||||
|
||||
// CacheConfig 缓存配置
|
||||
type CacheConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
TTL time.Duration `json:"ttl" yaml:"ttl"`
|
||||
MaxSize int `json:"maxSize" yaml:"maxSize"`
|
||||
Cleanup time.Duration `json:"cleanup" yaml:"cleanup"`
|
||||
}
|
||||
|
||||
// NewCacheManager 创建新的缓存管理器
|
||||
func NewCacheManager(config *CacheConfig, logger *logging.Logger) *CacheManager {
|
||||
if config == nil {
|
||||
// 默认配置
|
||||
config = &CacheConfig{
|
||||
Enabled: true,
|
||||
TTL: 5 * time.Minute,
|
||||
MaxSize: 1000,
|
||||
Cleanup: 1 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
cm := &CacheManager{
|
||||
logger: logger,
|
||||
entries: make(map[string]*CacheEntry),
|
||||
ttl: config.TTL,
|
||||
maxSize: config.MaxSize,
|
||||
enabled: config.Enabled,
|
||||
stats: &CacheStats{},
|
||||
cleanupChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
if cm.enabled {
|
||||
go cm.cleanupTask(config.Cleanup)
|
||||
logger.Infof("Cache manager initialized with TTL=%v, maxSize=%d", cm.ttl, cm.maxSize)
|
||||
} else {
|
||||
logger.Info("Cache manager disabled")
|
||||
}
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
// Get 获取缓存的转换结果
|
||||
func (cm *CacheManager) Get(key string) (*conversion.ConversionResponse, bool) {
|
||||
if !cm.enabled {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cm.mutex.RLock()
|
||||
defer cm.mutex.RUnlock()
|
||||
|
||||
entry, exists := cm.entries[key]
|
||||
if !exists {
|
||||
cm.stats.Misses++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if time.Now().After(entry.Expiration) {
|
||||
cm.stats.Misses++
|
||||
go cm.removeExpiredEntry(key)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 更新访问统计
|
||||
entry.AccessCount++
|
||||
entry.LastAccess = time.Now()
|
||||
cm.stats.Hits++
|
||||
|
||||
cm.logger.Debugf("Cache hit for key: %s", key)
|
||||
return entry.Response, true
|
||||
}
|
||||
|
||||
// Set 设置缓存条目
|
||||
func (cm *CacheManager) Set(key string, response *conversion.ConversionResponse) error {
|
||||
if !cm.enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
// 检查缓存大小限制
|
||||
if len(cm.entries) >= cm.maxSize {
|
||||
cm.evictEntries()
|
||||
}
|
||||
|
||||
entry := &CacheEntry{
|
||||
Response: response,
|
||||
Expiration: time.Now().Add(cm.ttl),
|
||||
AccessCount: 1,
|
||||
LastAccess: time.Now(),
|
||||
}
|
||||
|
||||
cm.entries[key] = entry
|
||||
cm.stats.Size++
|
||||
cm.stats.TotalSize++
|
||||
|
||||
cm.logger.Debugf("Cache entry set for key: %s, expires at: %v", key, entry.Expiration)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateKey 生成缓存键
|
||||
func (cm *CacheManager) GenerateKey(request *conversion.ConversionRequest) string {
|
||||
if request == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
signature := request.CacheSignature()
|
||||
|
||||
// 使用MD5生成固定长度的键
|
||||
hash := md5.Sum([]byte(signature))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// Remove 移除指定的缓存条目
|
||||
func (cm *CacheManager) Remove(key string) {
|
||||
if !cm.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
if _, exists := cm.entries[key]; exists {
|
||||
delete(cm.entries, key)
|
||||
cm.stats.Size--
|
||||
cm.logger.Debugf("Cache entry removed for key: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear 清空所有缓存
|
||||
func (cm *CacheManager) Clear() {
|
||||
if !cm.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
cm.entries = make(map[string]*CacheEntry)
|
||||
cm.stats.Size = 0
|
||||
cm.logger.Info("Cache cleared")
|
||||
}
|
||||
|
||||
// GetStats 获取缓存统计信息
|
||||
func (cm *CacheManager) GetStats() *CacheStats {
|
||||
if !cm.enabled {
|
||||
return &CacheStats{}
|
||||
}
|
||||
|
||||
cm.mutex.RLock()
|
||||
defer cm.mutex.RUnlock()
|
||||
|
||||
// 计算命中率
|
||||
total := cm.stats.Hits + cm.stats.Misses
|
||||
if total > 0 {
|
||||
cm.stats.HitRate = float64(cm.stats.Hits) / float64(total)
|
||||
}
|
||||
|
||||
// 返回统计信息的副本
|
||||
statsCopy := *cm.stats
|
||||
return &statsCopy
|
||||
}
|
||||
|
||||
// Close 关闭缓存管理器
|
||||
func (cm *CacheManager) Close() {
|
||||
if !cm.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
// 停止清理任务
|
||||
close(cm.cleanupChan)
|
||||
|
||||
// 清空缓存
|
||||
cm.Clear()
|
||||
|
||||
cm.logger.Info("Cache manager closed")
|
||||
}
|
||||
|
||||
// removeExpiredEntry 移除过期条目(异步)
|
||||
func (cm *CacheManager) removeExpiredEntry(key string) {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
if entry, exists := cm.entries[key]; exists && time.Now().After(entry.Expiration) {
|
||||
delete(cm.entries, key)
|
||||
cm.stats.Size++
|
||||
cm.stats.Evictions++
|
||||
cm.logger.Debugf("Expired cache entry removed for key: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// evictEntries 执行缓存淘汰策略
|
||||
func (cm *CacheManager) evictEntries() {
|
||||
// LRU (Least Recently Used) 淘汰策略
|
||||
var oldestKey string
|
||||
var oldestTime time.Time
|
||||
|
||||
for key, entry := range cm.entries {
|
||||
if oldestKey == "" || entry.LastAccess.Before(oldestTime) {
|
||||
oldestKey = key
|
||||
oldestTime = entry.LastAccess
|
||||
}
|
||||
}
|
||||
|
||||
if oldestKey != "" {
|
||||
delete(cm.entries, oldestKey)
|
||||
cm.stats.Size--
|
||||
cm.stats.Evictions++
|
||||
cm.logger.Debugf("Cache entry evicted (LRU) for key: %s", oldestKey)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupTask 定期清理过期条目
|
||||
func (cm *CacheManager) cleanupTask(interval time.Duration) {
|
||||
// 如果间隔为0,不启动清理任务
|
||||
if interval <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
cm.cleanupExpired()
|
||||
case <-cm.cleanupChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupExpired 清理所有过期条目
|
||||
func (cm *CacheManager) cleanupExpired() {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
expiredKeys := make([]string, 0)
|
||||
|
||||
for key, entry := range cm.entries {
|
||||
if now.After(entry.Expiration) {
|
||||
expiredKeys = append(expiredKeys, key)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range expiredKeys {
|
||||
delete(cm.entries, key)
|
||||
cm.stats.Evictions++
|
||||
}
|
||||
|
||||
cm.stats.Size = int64(len(cm.entries))
|
||||
|
||||
if len(expiredKeys) > 0 {
|
||||
cm.logger.Debugf("Cleaned up %d expired cache entries", len(expiredKeys))
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled 检查缓存是否启用
|
||||
func (cm *CacheManager) IsEnabled() bool {
|
||||
return cm.enabled
|
||||
}
|
||||
|
||||
// GetSize 获取当前缓存大小
|
||||
func (cm *CacheManager) GetSize() int {
|
||||
if !cm.enabled {
|
||||
return 0
|
||||
}
|
||||
|
||||
cm.mutex.RLock()
|
||||
defer cm.mutex.RUnlock()
|
||||
return len(cm.entries)
|
||||
}
|
||||
|
||||
// GetTTTL 获取缓存TTL
|
||||
func (cm *CacheManager) GetTTL() time.Duration {
|
||||
return cm.ttl
|
||||
}
|
||||
|
||||
// SetTTTL 设置缓存TTL
|
||||
func (cm *CacheManager) SetTTL(ttl time.Duration) {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
cm.ttl = ttl
|
||||
cm.logger.Infof("Cache TTL updated to: %v", ttl)
|
||||
}
|
||||
|
||||
// GetMaxSize 获取最大缓存大小
|
||||
func (cm *CacheManager) GetMaxSize() int {
|
||||
return cm.maxSize
|
||||
}
|
||||
|
||||
// SetMaxSize 设置最大缓存大小
|
||||
func (cm *CacheManager) SetMaxSize(maxSize int) {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
cm.maxSize = maxSize
|
||||
cm.logger.Infof("Cache max size updated to: %d", maxSize)
|
||||
}
|
||||
|
||||
// GetEntry 获取缓存条目详细信息(用于调试)
|
||||
func (cm *CacheManager) GetEntry(key string) (*CacheEntry, bool) {
|
||||
if !cm.enabled {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cm.mutex.RLock()
|
||||
defer cm.mutex.RUnlock()
|
||||
|
||||
entry, exists := cm.entries[key]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 返回副本
|
||||
entryCopy := *entry
|
||||
return &entryCopy, true
|
||||
}
|
||||
|
||||
// GetAllKeys 获取所有缓存键
|
||||
func (cm *CacheManager) GetAllKeys() []string {
|
||||
if !cm.enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
cm.mutex.RLock()
|
||||
defer cm.mutex.RUnlock()
|
||||
|
||||
keys := make([]string, 0, len(cm.entries))
|
||||
for key := range cm.entries {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user