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

如何搭配使用分布式内存缓存与分布式SQL Server缓存?

如何搭配使用分布式内存缓存与分布式SQL Server缓存?

嘿,你这个场景太接地气了——想用内存缓存当快速访问的L1,SQL Server缓存当持久化的L2,但因为俩都实现了IDistributedCache,直接注册会互相覆盖对吧?别慌,我给你捋个清晰的路子,不用改太多现有代码就能搞定!

核心思路

问题的关键在于:默认情况下DI容器里IDistributedCache只能有一个默认实现,所以咱得给两个缓存实例“起名字”,让DI能区分它们,然后再封装一个自定义服务,把“先查L1、再查L2,写入同步双端”的分层逻辑给包起来,消费端只用和这个自定义服务打交道就行。

具体步骤

1. 给两个缓存做命名注册

在你的Program.cs里,先分别注册分布式内存缓存和SQL Server缓存,并且给它们指定唯一的键名,让DI能精准找到对应的实例:

// 注册分布式内存缓存作为L1,命名为"L1Cache"
services.AddDistributedMemoryCache();
services.AddKeyedSingleton<IDistributedCache, DistributedMemoryCache>("L1Cache");

// 注册SQL Server缓存作为L2,命名为"L2Cache"
services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = Configuration.GetConnectionString("App");
    options.SchemaName = "dbo";
    options.TableName = "Cache";
});
services.AddKeyedSingleton<IDistributedCache, SqlServerCache>("L2Cache");

2. 封装分层缓存服务

咱自己写一个接口和实现类,把分层缓存的逻辑封装好,这样消费端不用关心底层是两个缓存:

// 先定义一个分层缓存的接口,按需加你需要的方法
public interface ILayeredCache
{
    Task<byte[]?> GetAsync(string key, CancellationToken token = default);
    Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default);
    Task RemoveAsync(string key, CancellationToken token = default);
}

// 实现类,把L1和L2的逻辑串起来
public class LayeredCache : ILayeredCache
{
    private readonly IDistributedCache _l1MemoryCache;
    private readonly IDistributedCache _l2SqlCache;

    // 通过[FromKeyedServices]特性,从DI里取出对应名字的缓存实例
    public LayeredCache(
        [FromKeyedServices("L1Cache")] IDistributedCache l1Cache,
        [FromKeyedServices("L2Cache")] IDistributedCache l2Cache)
    {
        _l1MemoryCache = l1Cache;
        _l2SqlCache = l2Cache;
    }

    public async Task<byte[]?> GetAsync(string key, CancellationToken token = default)
    {
        // 第一步:先查L1内存缓存,速度最快
        var value = await _l1MemoryCache.GetAsync(key, token);
        if (value != null)
        {
            return value;
        }

        // 第二步:L1没有,再查L2 SQL Server缓存
        value = await _l2SqlCache.GetAsync(key, token);
        if (value != null)
        {
            // 把L2的结果同步到L1,下次访问就直接走L1了
            await _l1MemoryCache.SetAsync(key, value, token);
        }

        return value;
    }

    public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
    {
        // 写入的时候同时更新L1和L2,保证数据一致
        await Task.WhenAll(
            _l1MemoryCache.SetAsync(key, value, options, token),
            _l2SqlCache.SetAsync(key, value, options, token)
        );
    }

    public async Task RemoveAsync(string key, CancellationToken token = default)
    {
        // 删除的时候也要同时删两个缓存的键
        await Task.WhenAll(
            _l1MemoryCache.RemoveAsync(key, token),
            _l2SqlCache.RemoveAsync(key, token)
        );
    }
}

3. 注册自定义服务并在消费端使用

最后把咱的分层缓存服务注册到DI里:

services.AddScoped<ILayeredCache, LayeredCache>();

然后在你需要用缓存的地方,直接注入ILayeredCache就行,完全不用管底层的两个IDistributedCache实例:

public class ProductService
{
    private readonly ILayeredCache _layeredCache;

    public ProductService(ILayeredCache layeredCache)
    {
        _layeredCache = layeredCache;
    }

    public async Task<Product?> GetProductByIdAsync(int id)
    {
        var cacheKey = $"Product_{id}";
        var cachedValue = await _layeredCache.GetAsync(cacheKey);
        if (cachedValue != null)
        {
            return JsonSerializer.Deserialize<Product>(cachedValue);
        }

        // 从数据库查数据的逻辑...
        var product = await _dbContext.Products.FindAsync(id);
        if (product != null)
        {
            var serializedValue = JsonSerializer.SerializeToUtf8Bytes(product);
            await _layeredCache.SetAsync(cacheKey, serializedValue, new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
            });
        }

        return product;
    }
}

几个要注意的小细节

  • 内存缓存是进程内的,如果你的应用是多实例部署(比如集群、容器化),每个实例的L1缓存是独立的,可能会出现短暂的数据不一致。如果对一致性要求高,可以给L1缓存设置较短的过期时间,缩小不一致的窗口。
  • 写入的时候用Task.WhenAll并行操作,能提升性能,但如果其中一个缓存写入失败,可能会出现数据不一致。如果你的业务对一致性要求极高,可以改成串行写入(比如先写L2成功,再写L1),或者加简单的重试逻辑。
  • 要是你不想写自定义服务,也可以在消费端直接注入IKeyedServiceProvider,然后手动解析对应的IDistributedCache实例,但这样消费端会和底层缓存实现耦合,不如自定义服务优雅。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:39:35