You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebAPI限流外部HttpClient调用 实现多请求复用单个外部请求结果

单Endpoint防缓存击穿调用方案

核心思路

你遇到的是典型的缓存击穿问题:热点缓存失效瞬间大量请求直接打到下游接口。我们可以通过在内存缓存中存储未完成的调用Task而非最终结果,实现同批次请求仅发起一次外部调用,其余请求复用同一结果的效果,不需要修改全局HttpClient配置,仅作用于目标endpoint。

实现代码示例

基于ASP.NET Core IMemoryCache的实现代码如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System.Net;

[ApiController]
[Route("api/[controller]")]
public class ExternalDataController : ControllerBase
{
    private readonly IMemoryCache _memoryCache;
    // 沿用你现有的静态HttpClient实例
    private static readonly HttpClient _httpClient = new HttpClient();
    
    // 缓存相关常量配置
    private const string _externalApiCacheKey = "External:TargetApi:Result";
    // 正常缓存有效期
    private const int _normalCacheExpirationMinutes = 10;
    // 待完成Task的兜底超时时间,避免外部接口完全不可用时缓存卡死
    private const int _pendingTaskTimeoutMinutes = 1;

    public ExternalDataController(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    [HttpGet("query")]
    public async Task<IActionResult> QueryExternalApi()
    {
        // 尝试从缓存获取调用Task
        var queryTask = _memoryCache.Get<Task<string>>(_externalApiCacheKey);
        if (queryTask == null)
        {
            // 无缓存时创建调用Task,先不await
            queryTask = _httpClient.GetStringAsync("https://你的外部接口地址");
            
            // 先将未完成的Task写入缓存,加短超时兜底
            _memoryCache.Set(_externalApiCacheKey, queryTask, TimeSpan.FromMinutes(_pendingTaskTimeoutMinutes));

            try
            {
                // 等待调用完成
                var result = await queryTask;
                // 调用成功后更新缓存为已完成的Task,设置正常有效期
                _memoryCache.Set(_externalApiCacheKey, Task.FromResult(result), TimeSpan.FromMinutes(_normalCacheExpirationMinutes));
                return Ok(result);
            }
            catch (HttpRequestException ex)
            {
                // 调用失败时移除缓存中的失败Task,允许后续请求重试
                _memoryCache.Remove(_externalApiCacheKey);
                // 可根据需要添加降级逻辑,比如返回历史缓存值
                return StatusCode((int)HttpStatusCode.ServiceUnavailable, "外部接口暂时不可用");
            }
        }

        // 已有缓存(不管是待完成还是已完成的Task),直接等待结果返回
        var data = await queryTask;
        return Ok(data);
    }
}

方案优势

  • 仅对当前Endpoint生效,不影响项目内其他接口的调用逻辑
  • 和现有静态HttpClient的用法完全兼容,不需要修改全局HttpClient配置
  • 同一时间最多只会发起一次外部接口调用,完全避免缓存击穿导致的下游接口压力放大问题
  • 异常兜底逻辑完善,单次调用失败不会导致后续请求持续报错

扩展说明

如果你的服务是多实例部署,需要跨实例限制调用次数,可以在上述逻辑基础上加上分布式锁,限制全局仅一个实例发起外部调用,其余实例等待缓存写入后读取即可。


内容的提问来源于stack exchange,提问作者citronas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 08:24:03