feat(metrics): 优化指标数据清理和持久化策略

- 调整指标清理机制,智能保留高频路径和引用来源统计数据
- 修改清理任务频率为15分钟,并立即执行首次清理
- 优化指标存储服务保存间隔为30分钟,减少IO操作
- 在清理和保存过程中添加内存使用情况日志
- 强制执行垃圾回收,减少内存占用
- 移除部分冗余的性能指标统计项目
This commit is contained in:
wood chen 2025-03-10 03:30:39 +08:00
parent 22c0d2e301
commit 10aef5e73e
3 changed files with 123 additions and 22 deletions

View File

@ -468,8 +468,10 @@ func (c *Collector) validateLoadedData() error {
return true
})
if totalPathRequests != statusCodeTotal {
return fmt.Errorf("path stats total (%d) does not match status code total (%d)",
// 由于我们限制了路径统计的收集数量,路径统计总数可能小于状态码统计总数
// 因此,我们只需要确保路径统计总数不超过状态码统计总数即可
if float64(totalPathRequests) > float64(statusCodeTotal)*1.1 { // 允许10%的误差
return fmt.Errorf("path stats total (%d) significantly exceeds status code total (%d)",
totalPathRequests, statusCodeTotal)
}
@ -583,7 +585,10 @@ func simplifyReferer(referer string) string {
// startCleanupTask 启动定期清理任务
func (c *Collector) startCleanupTask() {
go func() {
ticker := time.NewTicker(1 * time.Hour) // 每小时清理一次
// 先立即执行一次清理
c.cleanupOldData()
ticker := time.NewTicker(15 * time.Minute) // 每15分钟清理一次
defer ticker.Stop()
for range ticker.C {
@ -596,32 +601,102 @@ func (c *Collector) startCleanupTask() {
func (c *Collector) cleanupOldData() {
log.Printf("[Metrics] 开始清理旧数据...")
// 清理路径统计 - 只保留有请求的路径
// 清理路径统计 - 只保留有请求且请求数较多的路径
var pathsToRemove []string
var pathsCount int
var totalRequests int64
// 先收集所有路径及其请求数
type pathInfo struct {
path string
count int64
}
var paths []pathInfo
c.pathStats.Range(func(key, value interface{}) bool {
path := key.(string)
stats := value.(*models.PathMetrics)
if stats.GetRequestCount() == 0 {
pathsToRemove = append(pathsToRemove, path)
}
count := stats.GetRequestCount()
pathsCount++
totalRequests += count
paths = append(paths, pathInfo{path, count})
return true
})
// 按请求数排序
sort.Slice(paths, func(i, j int) bool {
return paths[i].count > paths[j].count
})
// 只保留前100个请求数最多的路径或者请求数占总请求数1%以上的路径
threshold := totalRequests / 100 // 1%的阈值
if threshold < 10 {
threshold = 10 // 至少保留请求数>=10的路径
}
// 标记要删除的路径
for _, pi := range paths {
if len(paths)-len(pathsToRemove) <= 100 {
// 已经只剩下100个路径了不再删除
break
}
if pi.count < threshold {
pathsToRemove = append(pathsToRemove, pi.path)
}
}
// 删除标记的路径
for _, path := range pathsToRemove {
c.pathStats.Delete(path)
}
// 清理引用来源统计 - 只保留有请求的引用来源
// 清理引用来源统计 - 类似地处理
var referersToRemove []string
var referersCount int
var totalRefererRequests int64
// 先收集所有引用来源及其请求数
type refererInfo struct {
referer string
count int64
}
var referers []refererInfo
c.refererStats.Range(func(key, value interface{}) bool {
referer := key.(string)
stats := value.(*models.PathMetrics)
if stats.GetRequestCount() == 0 {
referersToRemove = append(referersToRemove, referer)
}
count := stats.GetRequestCount()
referersCount++
totalRefererRequests += count
referers = append(referers, refererInfo{referer, count})
return true
})
// 按请求数排序
sort.Slice(referers, func(i, j int) bool {
return referers[i].count > referers[j].count
})
// 只保留前50个请求数最多的引用来源或者请求数占总请求数2%以上的引用来源
refThreshold := totalRefererRequests / 50 // 2%的阈值
if refThreshold < 5 {
refThreshold = 5 // 至少保留请求数>=5的引用来源
}
// 标记要删除的引用来源
for _, ri := range referers {
if len(referers)-len(referersToRemove) <= 50 {
// 已经只剩下50个引用来源了不再删除
break
}
if ri.count < refThreshold {
referersToRemove = append(referersToRemove, ri.referer)
}
}
// 删除标记的引用来源
for _, referer := range referersToRemove {
c.refererStats.Delete(referer)
}
@ -654,5 +729,15 @@ func (c *Collector) cleanupOldData() {
}
c.bandwidthStats.Unlock()
log.Printf("[Metrics] 清理完成: 删除了 %d 个路径, %d 个引用来源", len(pathsToRemove), len(referersToRemove))
// 强制进行一次GC
runtime.GC()
// 打印内存使用情况
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
log.Printf("[Metrics] 清理完成: 删除了 %d/%d 个路径, %d/%d 个引用来源, 当前内存使用: %s",
len(pathsToRemove), pathsCount,
len(referersToRemove), referersCount,
utils.FormatBytes(int64(mem.Alloc)))
}

View File

@ -20,7 +20,7 @@ func InitMetricsStorage(cfg *config.Config) error {
// 创建指标存储服务
dataDir := filepath.Join("data", "metrics")
saveInterval := 5 * time.Minute // 默认5分钟保存一次
saveInterval := 30 * time.Minute // 默认30分钟保存一次减少IO操作
metricsStorage = NewMetricsStorage(GetCollector(), dataDir, saveInterval)
@ -30,7 +30,7 @@ func InitMetricsStorage(cfg *config.Config) error {
return err
}
log.Printf("[Metrics] 指标存储服务已初始化")
log.Printf("[Metrics] 指标存储服务已初始化,保存间隔: %v", saveInterval)
return nil
}

View File

@ -7,6 +7,8 @@ import (
"os"
"path/filepath"
"proxy-go/internal/models"
"proxy-go/internal/utils"
"runtime"
"sync"
"sync/atomic"
"time"
@ -106,12 +108,10 @@ func (ms *MetricsStorage) SaveMetrics() error {
// 保存基本指标 - 只保存必要的字段
basicMetrics := map[string]interface{}{
"uptime": stats["uptime"],
"total_bytes": stats["total_bytes"],
"avg_response_time": stats["avg_response_time"],
"requests_per_second": stats["requests_per_second"],
"bytes_per_second": stats["bytes_per_second"],
"save_time": time.Now().Format(time.RFC3339),
"uptime": stats["uptime"],
"total_bytes": stats["total_bytes"],
"avg_response_time": stats["avg_response_time"],
"save_time": time.Now().Format(time.RFC3339),
}
// 单独保存延迟统计,避免嵌套结构导致的内存占用
@ -154,7 +154,15 @@ func (ms *MetricsStorage) SaveMetrics() error {
ms.lastSaveTime = time.Now()
ms.mutex.Unlock()
log.Printf("[MetricsStorage] 指标数据保存完成,耗时: %v", time.Since(start))
// 强制进行一次GC
runtime.GC()
// 打印内存使用情况
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
log.Printf("[MetricsStorage] 指标数据保存完成,耗时: %v, 内存使用: %s",
time.Since(start), utils.FormatBytes(int64(mem.Alloc)))
return nil
}
@ -314,7 +322,15 @@ func (ms *MetricsStorage) LoadMetrics() error {
}
ms.mutex.Unlock()
log.Printf("[MetricsStorage] 指标数据加载完成,耗时: %v", time.Since(start))
// 强制进行一次GC
runtime.GC()
// 打印内存使用情况
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
log.Printf("[MetricsStorage] 指标数据加载完成,耗时: %v, 内存使用: %s",
time.Since(start), utils.FormatBytes(int64(mem.Alloc)))
return nil
}