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

ASP.NET Core中按参数处理API请求:同ID等待缓存填充

最优实现方案

针对多Docker容器集群下的缓存填充并发控制需求,核心是通过分布式锁+共享状态标记的组合,避免相同ID的请求重复触发耗时的缓存填充,同时让后续请求等待填充完成后复用结果。具体实现步骤如下:

核心思路

  1. 优先从Blob缓存读取数据,命中直接返回。
  2. 缓存未命中时,通过分布式锁确保同一时间只有一个请求进入缓存填充流程。
  3. 第一个请求进入填充流程后,标记该ID的"填充中"状态,释放锁以避免后续请求阻塞在锁竞争上。
  4. 后续相同ID的请求发现"填充中"状态后,等待状态清除,再读取Blob缓存返回结果。
  5. 填充完成后写入Blob缓存,并清除"填充中"状态。

技术选型与实现细节

1. 分布式锁实现

因为是多容器集群,必须用跨实例的分布式锁:

  • 若使用Azure Blob存储,可利用Blob租约实现锁:为对应ID的Blob创建独占租约,有效期设为填充操作的最大预估时间(如5分钟),租约到期自动释放,避免异常导致死锁。
  • 通用场景可使用Redis的分布式锁(如IDistributedLock组件),锁粒度为单个ID(锁键格式如report-lock:{id}),确保仅针对当前ID的请求互斥。

2. 填充状态标记

用分布式存储维护填充状态,确保所有容器实例可见:

  • 推荐用Redis字符串键(格式如report-pending:{id}),设置合理过期时间(如5分钟),防止填充过程中服务崩溃导致状态永久留存。
  • 若依赖现有Blob存储,可创建临时文件(如{id}-pending),填充完成后删除该文件作为状态清除标记。

3. 请求等待机制

两种可选方式:

  • 轮询检查:后续请求每隔固定间隔(如500ms)检查状态,直到状态消失或超时(如30s),超时则返回降级响应。
  • Pub/Sub通知:第一个请求填充完成后,通过Redis发布订阅机制发送通知,后续请求订阅该ID的通知,收到后立即读取缓存,效率更高。

示例代码(Redis实现)

public async Task<ActionResult> GetReport(Guid id)
{
    // 1. 尝试从Blob缓存读取
    var report = await _blobCache.GetReportAsync(id);
    if (report != null)
        return Ok(report);

    var lockKey = $"report-lock:{id}";
    var pendingKey = $"report-pending:{id}";

    // 2. 获取分布式锁,尝试时间30s
    using var lockHandle = await _distributedLock.TryAcquireLockAsync(lockKey, TimeSpan.FromSeconds(30));
    if (lockHandle != null)
    {
        // 3. 二次检查缓存,避免等待锁期间已被其他请求填充
        report = await _blobCache.GetReportAsync(id);
        if (report != null)
            return Ok(report);

        // 4. 设置填充中状态,5分钟过期
        await _redisCache.StringSetAsync(pendingKey, "true", TimeSpan.FromMinutes(5));

        // 5. 提前释放锁,减少后续请求的锁竞争
        lockHandle.Dispose();

        try
        {
            // 6. 执行耗时的报告生成与缓存写入
            report = await GenerateReportAsync(id);
            await _blobCache.SaveReportAsync(id, report);
        }
        finally
        {
            // 7. 清除填充状态
            await _redisCache.KeyDeleteAsync(pendingKey);
            // 发布缓存就绪通知(若用Pub/Sub)
            await _redisPublisher.PublishAsync($"report-ready:{id}", string.Empty);
        }

        return Ok(report);
    }
    else
    {
        // 未抢到锁,进入等待逻辑
        var timeout = TimeSpan.FromSeconds(30);
        var startTime = DateTime.UtcNow;

        while (DateTime.UtcNow - startTime < timeout)
        {
            if (!await _redisCache.KeyExistsAsync(pendingKey))
            {
                report = await _blobCache.GetReportAsync(id);
                if (report != null)
                    return Ok(report);
            }
            await Task.Delay(500);
        }

        // 超时返回降级响应
        return StatusCode(StatusCodes.Status503ServiceUnavailable, "报告生成超时,请稍后重试");
    }
}

关键注意事项

  • 必须设置状态的过期时间,避免服务崩溃导致后续请求无限等待。
  • 填充操作的重试逻辑要谨慎,避免重复触发填充。
  • 超时时间需根据实际填充耗时调整,兼顾用户体验与资源占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:12:04