Files
go-pixelnebula/cache/monitor.go
2025-04-18 16:47:08 +08:00

256 lines
6.7 KiB
Go
Raw Permalink 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 (
"sync"
"time"
)
// MonitorOptions 缓存监控选项
type MonitorOptions struct {
Enabled bool // 是否启用监控
SampleInterval time.Duration // 采样间隔时间
AdjustInterval time.Duration // 调整间隔时间
MinSize int // 最小缓存大小
MaxSize int // 最大缓存大小
TargetHitRate float64 // 目标命中率
SizeGrowthFactor float64 // 缓存大小增长因子
SizeShrinkFactor float64 // 缓存大小收缩因子
ExpirationFactor float64 // 过期时间调整因子
}
// DefaultMonitorOptions 默认监控选项
var DefaultMonitorOptions = MonitorOptions{
Enabled: true,
SampleInterval: time.Minute, // 每分钟采样一次
AdjustInterval: time.Minute * 10, // 每10分钟调整一次
MinSize: 50, // 最小缓存大小
MaxSize: 1000, // 最大缓存大小
TargetHitRate: 0.8, // 目标命中率80%
SizeGrowthFactor: 1.2, // 增长20%
SizeShrinkFactor: 0.8, // 收缩20%
ExpirationFactor: 1.5, // 过期时间调整因子
}
// CacheStats 缓存统计信息
type CacheStats struct {
Size int // 当前缓存大小
Hits int // 命中次数
Misses int // 未命中次数
HitRate float64 // 命中率
MemoryUsage int64 // 内存使用量(字节)
LastAdjusted time.Time // 最后调整时间
SamplesCount int // 样本数量
AvgAccessTime float64 // 平均访问时间(纳秒)
}
// Monitor 缓存监控器
type Monitor struct {
options MonitorOptions
cache *PNCache
stats CacheStats
sampleHistory []CacheStats
mutex sync.RWMutex
stopChan chan struct{}
isRunning bool
}
// NewMonitor 创建一个新的缓存监控器
func NewMonitor(cache *PNCache, options MonitorOptions) *Monitor {
return &Monitor{
options: options,
cache: cache,
sampleHistory: make([]CacheStats, 0, 100), // 预分配100个样本的容量
stopChan: make(chan struct{}),
isRunning: false,
}
}
// Start 启动监控器
func (m *Monitor) Start() {
if !m.options.Enabled || m.isRunning {
return
}
m.mutex.Lock()
m.isRunning = true
m.mutex.Unlock()
go m.monitorRoutine()
}
// Stop 停止监控器
func (m *Monitor) Stop() {
if !m.isRunning {
return
}
m.mutex.Lock()
m.isRunning = false
m.mutex.Unlock()
m.stopChan <- struct{}{}
}
// GetStats 获取当前缓存统计信息
func (m *Monitor) GetStats() CacheStats {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.stats
}
// GetSampleHistory 获取采样历史
func (m *Monitor) GetSampleHistory() []CacheStats {
m.mutex.RLock()
defer m.mutex.RUnlock()
// 创建一个副本
result := make([]CacheStats, len(m.sampleHistory))
copy(result, m.sampleHistory)
return result
}
// monitorRoutine 监控例程
func (m *Monitor) monitorRoutine() {
sampleTicker := time.NewTicker(m.options.SampleInterval)
adjustTicker := time.NewTicker(m.options.AdjustInterval)
defer sampleTicker.Stop()
defer adjustTicker.Stop()
for {
select {
case <-m.stopChan:
return
case <-sampleTicker.C:
m.collectSample()
case <-adjustTicker.C:
m.adjustCache()
}
}
}
// collectSample 收集缓存样本
func (m *Monitor) collectSample() {
m.mutex.Lock()
defer m.mutex.Unlock()
// 获取缓存统计信息
hits, misses, hitRate := m.cache.Stats()
size := m.cache.Size()
// 估算内存使用量
var memoryUsage int64
// 获取所有缓存项
items := m.cache.GetAllItems()
for _, item := range items {
itemSize := int64(0)
// 计算SVG字符串占用的内存
if !item.IsCompressed {
itemSize += int64(len(item.SVG) * 2) // Go中字符串以UTF-16编码存储每个字符占2字节
}
// 计算压缩数据占用的内存
if item.IsCompressed {
itemSize += int64(len(item.Compressed))
}
// 添加对象结构体本身的大小估计
// CacheItem结构体的基本大小约为40字节2个时间戳、1个布尔值、2个引用
itemSize += 40
memoryUsage += itemSize
}
// 添加缓存管理结构的开销估计 (映射表、双向链表等)
// 每个map项约24字节开销每个链表节点约16字节
mapOverhead := int64(size * 24)
listOverhead := int64(size * 16)
memoryUsage += mapOverhead + listOverhead
// 创建新的统计样本
newStat := CacheStats{
Size: size,
Hits: hits,
Misses: misses,
HitRate: hitRate,
MemoryUsage: memoryUsage,
LastAdjusted: time.Now(),
}
// 添加到历史样本
m.sampleHistory = append(m.sampleHistory, newStat)
// 限制历史样本数量保留最近的100个样本
if len(m.sampleHistory) > 100 {
m.sampleHistory = m.sampleHistory[len(m.sampleHistory)-100:]
}
// 更新当前统计信息
m.stats = newStat
m.stats.SamplesCount = len(m.sampleHistory)
}
// adjustCache 根据统计信息调整缓存
func (m *Monitor) adjustCache() {
m.mutex.Lock()
defer m.mutex.Unlock()
// 如果样本数量不足,不进行调整
if len(m.sampleHistory) < 5 {
return
}
// 计算平均命中率
totalHitRate := 0.0
for _, stat := range m.sampleHistory {
totalHitRate += stat.HitRate
}
avgHitRate := totalHitRate / float64(len(m.sampleHistory))
// 获取当前缓存选项
cacheOptions := m.cache.GetOptions()
// 根据命中率调整缓存大小
newSize := cacheOptions.Size
if avgHitRate < m.options.TargetHitRate {
// 命中率低于目标,增加缓存大小
newSize = int(float64(newSize) * m.options.SizeGrowthFactor)
// 确保不超过最大大小
if newSize > m.options.MaxSize {
newSize = m.options.MaxSize
}
} else if avgHitRate > m.options.TargetHitRate+0.1 && m.stats.Size > m.options.MinSize {
// 命中率远高于目标且缓存大小大于最小值,可以适当减小缓存
newSize = int(float64(newSize) * m.options.SizeShrinkFactor)
// 确保不小于最小大小
if newSize < m.options.MinSize {
newSize = m.options.MinSize
}
}
// 根据访问模式调整过期时间
newExpiration := cacheOptions.Expiration
if avgHitRate < m.options.TargetHitRate {
// 命中率低,增加过期时间
newExpiration = time.Duration(float64(newExpiration) * m.options.ExpirationFactor)
} else if avgHitRate > m.options.TargetHitRate+0.1 {
// 命中率高,可以适当减少过期时间
newExpiration = time.Duration(float64(newExpiration) / m.options.ExpirationFactor)
}
// 应用新的缓存选项
if newSize != cacheOptions.Size || newExpiration != cacheOptions.Expiration {
cacheOptions.Size = newSize
cacheOptions.Expiration = newExpiration
m.cache.UpdateOptions(cacheOptions)
// 更新最后调整时间
m.stats.LastAdjusted = time.Now()
}
}