C# --- 本地缓存失效形成缓存击穿触发限流
- 问题描述
- 解决方案
-
- [缓存Request Task](#缓存Request Task)
- 加入信号量限制请求数量
问题描述
- 某一接口前端会偶发返回400.
- 通过检查日志发现是后端服务调用外部Api时,因为缓存失效导致并发请求数量过多 (QPS接近300), 外部Api返回429导致的.
- 代码大致如下
csharp
private readonly MemoryCache _timeZoneCache = new(new MemoryCacheOptions())
private MemeoryCacheEntryOptions EntryOption => new MemeoryCacheEntryOption().SetAbsoluteExpiraton(DataTimeOffset.Utc.Now.AddMinutes(1))
public async Task Process()
{
var elements = await GetAllElementsAsync();
//如果elements数量过多,这里会并发发送大量请求
var tasks = elements.Select(element -> GetTimeZoneAsync(element)).ToList();
if (tasks.Any())
{
await Task.WhenAll(tasks)
}
}
private async Task<string> GetTimeZoneAsync(Element element)
{
if (_timeZoneCache.TryGetValue(element.QueryParameter, out string timeZone)
{
return timeZone
}
//send reuqets to external Api
var reponse = await SendTimeZoneRequestAsync(element)
//parse http response
var result = ParseHttpResponse(response)
//set cache
_timeZoneCache.Add(element.QueryParameter, result)
}
- 当缓存失效时,如果elements的数量过多(比如300个),那么上面的代码会并发发送300个请求. 导致触发外部服务的限流,返回429
解决方案
缓存Request Task
- 经过调查发现,被发送出去的大量请求其实很多是重复的请求
- 那么可以利用C#的Task机制,加入一个新的缓存,这个缓存用来缓存发送出去的Request Task,避免重复发送请求
csharp
private readonly MemoryCache _timeZoneCache = new(new MemoryCacheOptions())
private MemeoryCacheEntryOptions EntryOption => new MemeoryCacheEntryOption().SetAbsoluteExpiraton(DataTimeOffset.Utc.Now.AddMinutes(1))
public async Task Process()
{
var elements = await GetAllElementsAsync();
//如果elements数量过多,这里会并发发送大量请求
var tasks = elements.Select(element -> GetTimeZoneAsync(element)).ToList();
if (tasks.Any())
{
await Task.WhenAll(tasks)
}
}
private async Task<string> GetTimeZoneAsync(Element element)
{
if (_timeZoneCache.TryGetValue(element.QueryParameter, out string timeZone)
{
return timeZone
}
var response = await GetTimeZoneRequestTaskAsync(element);
//parse http response
var result = ParseHttpResponse(response)
//set cache
_timeZoneCache.Add(element.QueryParameter, result)
}
private async Task<HttpResponse> GetTimeZoneRequestTaskAsync(Element element)
{
if (_timeZoneRequestCache.TryGetValue(element.QueryParameter, out string timeZoneRequest)
{
return timeZoneRequest
}
//do not await here
var request = SendTimeZoneRequestAsync(element)
//cache the request
_timeZoneRequestCache(element.QueryParameter, request);
return request;
}
加入信号量限制请求数量
- 如果都是不同的请求,那么可以使用信号量控制并发发送请求的数量
参见 https://blog.csdn.net/weixin_38803409/article/details/135353000