using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.Metrics; namespace TakeoutSaaS.Infrastructure.Dictionary.Caching; /// /// 缓存命中/耗时指标采集器。 /// public sealed class CacheMetricsCollector { private const string MeterName = "TakeoutSaaS.DictionaryCache"; private static readonly Meter Meter = new(MeterName, "1.0.0"); private readonly Counter _hitCounter; private readonly Counter _missCounter; private readonly Counter _invalidationCounter; private readonly Histogram _durationHistogram; private readonly ConcurrentQueue _queries = new(); private readonly TimeSpan _retention = TimeSpan.FromDays(7); private long _hitTotal; private long _missTotal; /// /// 初始化指标采集器。 /// public CacheMetricsCollector() { _hitCounter = Meter.CreateCounter("cache_hit_count"); _missCounter = Meter.CreateCounter("cache_miss_count"); _invalidationCounter = Meter.CreateCounter("cache_invalidation_count"); _durationHistogram = Meter.CreateHistogram("cache_query_duration_ms"); Meter.CreateObservableGauge( "cache_hit_ratio", () => new Measurement(CalculateHitRatio())); } /// /// 记录缓存命中。 /// public void RecordHit(string cacheLevel, string dictionaryCode) { Interlocked.Increment(ref _hitTotal); _hitCounter.Add(1, new TagList { { "cache_level", cacheLevel }, { "dictionary_code", NormalizeCode(dictionaryCode) } }); } /// /// 记录缓存未命中。 /// public void RecordMiss(string cacheLevel, string dictionaryCode) { Interlocked.Increment(ref _missTotal); _missCounter.Add(1, new TagList { { "cache_level", cacheLevel }, { "dictionary_code", NormalizeCode(dictionaryCode) } }); } /// /// 记录缓存查询耗时。 /// public void RecordDuration(string dictionaryCode, double durationMs) { _durationHistogram.Record(durationMs, new TagList { { "dictionary_code", NormalizeCode(dictionaryCode) } }); } /// /// 记录查询详情,用于统计窗口分析。 /// public void RecordQuery(string dictionaryCode, bool l1Hit, bool l2Hit, double durationMs) { var record = new CacheQueryRecord(DateTime.UtcNow, NormalizeCode(dictionaryCode), l1Hit, l2Hit, durationMs); _queries.Enqueue(record); PruneOldRecords(); } /// /// 记录缓存失效事件。 /// public void RecordInvalidation(string dictionaryCode) { _invalidationCounter.Add(1, new TagList { { "dictionary_code", NormalizeCode(dictionaryCode) } }); } /// /// 获取指定时间范围内的统计快照。 /// public CacheStatsSnapshot GetSnapshot(TimeSpan window) { var since = DateTime.UtcNow.Subtract(window); var records = _queries.Where(record => record.Timestamp >= since).ToList(); var l1Hits = records.Count(record => record.L1Hit); var l1Misses = records.Count(record => !record.L1Hit); var l2Hits = records.Count(record => record.L2Hit); var l2Misses = records.Count(record => !record.L1Hit && !record.L2Hit); var totalHits = l1Hits + l2Hits; var totalMisses = l1Misses + l2Misses; var hitRatio = totalHits + totalMisses == 0 ? 0 : totalHits / (double)(totalHits + totalMisses); var averageDuration = records.Count == 0 ? 0 : records.Average(record => record.DurationMs); var topQueried = records .GroupBy(record => record.DictionaryCode) .Select(group => new DictionaryQueryCount(group.Key, group.Count())) .OrderByDescending(item => item.QueryCount) .Take(5) .ToList(); return new CacheStatsSnapshot( totalHits, totalMisses, hitRatio, new CacheLevelStats(l1Hits, l2Hits), new CacheLevelStats(l1Misses, l2Misses), averageDuration, topQueried); } /// /// 从缓存键解析字典编码。 /// public static string ExtractDictionaryCode(string cacheKey) { if (string.IsNullOrWhiteSpace(cacheKey)) { return "unknown"; } if (cacheKey.StartsWith("dict:groups:", StringComparison.Ordinal)) { return "groups"; } if (cacheKey.StartsWith("dict:items:", StringComparison.Ordinal)) { return "items"; } if (cacheKey.StartsWith("dict:", StringComparison.Ordinal)) { var parts = cacheKey.Split(':', StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 3) { return parts[2]; } } return "unknown"; } private static string NormalizeCode(string? code) => string.IsNullOrWhiteSpace(code) ? "unknown" : code.Trim().ToLowerInvariant(); private double CalculateHitRatio() { var hits = Interlocked.Read(ref _hitTotal); var misses = Interlocked.Read(ref _missTotal); return hits + misses == 0 ? 0 : hits / (double)(hits + misses); } private void PruneOldRecords() { var cutoff = DateTime.UtcNow.Subtract(_retention); while (_queries.TryPeek(out var record) && record.Timestamp < cutoff) { _queries.TryDequeue(out _); } } private sealed record CacheQueryRecord( DateTime Timestamp, string DictionaryCode, bool L1Hit, bool L2Hit, double DurationMs); } /// /// 缓存统计快照。 /// public sealed record CacheStatsSnapshot( long TotalHits, long TotalMisses, double HitRatio, CacheLevelStats HitsByLevel, CacheLevelStats MissesByLevel, double AverageQueryDurationMs, IReadOnlyList TopQueriedDictionaries); /// /// 命中统计。 /// public sealed record CacheLevelStats(long L1, long L2); /// /// 字典查询次数统计。 /// public sealed record DictionaryQueryCount(string Code, int QueryCount);