fix: 缓存改为 static 解决跨请求并发问题

根因:
- MarketDataService 是 Scoped,每个 HTTP 请求有独立实例
- 两个请求同时查询同一股票,各自有自己的 _memoryCache 和 _pendingPriceRequests
- 导致重复查询数据库,连接冲突

修复:
1. _memoryCache 改为 static,跨请求共享
2. _pendingPriceRequests 改为 static,跨请求共享
3. GetOrAdd 正确模式:先创建 Lazy,再 GetOrAdd

流程:
请求1 → static _memoryCache 未命中 → static _pendingPriceRequests.GetOrAdd
请求2 → static _memoryCache 未命中 → static _pendingPriceRequests.GetOrAdd(复用请求1的 Lazy)
                                              ↓
                                     只查一次数据库
This commit is contained in:
OpenClaw Agent 2026-03-24 10:30:55 +00:00
parent 5b546061c0
commit ad7761810d

View File

@ -22,11 +22,11 @@ public class MarketDataService : IMarketDataService
private readonly IOkxMarketService _okxService;
private readonly IMarketDataRepository _marketDataRepo;
// 内存缓存层(避免并发数据库查询导致连接池冲突)
private readonly ConcurrentDictionary<string, (MarketPriceCache cache, DateTime expireAt)> _memoryCache = new();
// 静态内存缓存层(跨请求共享,避免并发数据库查询导致连接池冲突)
private static readonly ConcurrentDictionary<string, (MarketPriceCache cache, DateTime expireAt)> _memoryCache = new();
// 防止并发请求同一股票(使用 Lazy 确保只创建一次
private readonly ConcurrentDictionary<string, Lazy<Task<MarketPriceResponse>>> _pendingPriceRequests = new();
// 静态 pending 请求字典(跨请求共享,防止并发请求同一股票)
private static readonly ConcurrentDictionary<string, Lazy<Task<MarketPriceResponse>>> _pendingPriceRequests = new();
public MarketDataService(
ILogger<MarketDataService> logger,
@ -46,7 +46,7 @@ public class MarketDataService : IMarketDataService
/// <summary>
/// 获取实时价格(自动根据资产类型路由到对应数据源)
/// 使用 Lazy 确保同一时间只有一个请求在处理
/// 使用静态缓存 + Lazy 确保跨请求并发安全
/// </summary>
public async Task<MarketPriceResponse> GetPriceAsync(string symbol, string assetType)
{
@ -55,7 +55,7 @@ public class MarketDataService : IMarketDataService
try
{
// 第一步:查内存缓存(快速返回)
// 第一步:查静态内存缓存(快速返回)
if (_memoryCache.TryGetValue(cacheKey, out var memoryCached) && memoryCached.expireAt > DateTime.Now)
{
_logger.LogInformation("[内存缓存命中] Symbol={Symbol}, Price={Price}", symbol, memoryCached.cache.Price);
@ -69,13 +69,14 @@ public class MarketDataService : IMarketDataService
};
}
// 第二步:使用 Lazy 确保同一时间只有一个请求在处理(包括查数据库和获取价格)
var lazyTask = _pendingPriceRequests.GetOrAdd(cacheKey,
_ => new Lazy<Task<MarketPriceResponse>>(() => GetPriceInternalAsync(symbol, assetType, cacheKey)));
// 第二步:创建 Lazy 并尝试添加到字典
// 先创建 Lazy再 GetOrAdd确保所有线程使用同一个 Lazy
var lazy = new Lazy<Task<MarketPriceResponse>>(() => GetPriceInternalAsync(symbol, assetType, cacheKey));
var actualLazy = _pendingPriceRequests.GetOrAdd(cacheKey, lazy);
try
{
var result = await lazyTask.Value;
var result = await actualLazy.Value;
_logger.LogInformation("[价格获取成功] Symbol={Symbol}, Price={Price}", symbol, result.Price);
return result;
}