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

.NET Core 5中IMemoryCache序列化对象引发内存不足问题求解

解决方案

针对高并发下反序列化大JSON导致的内存不足问题,提供以下几种可行方案,按优先级排序:

1. 标记对象为不可变,直接返回缓存实例(最优解)

如果业务场景中前端仅展示目录数据、不会修改实例并提交回后端,将Catalog及内部嵌套对象(如Product)定义为不可变类型,直接返回缓存的实例即可,完全避免拷贝/反序列化的内存开销。

代码示例

// 用record定义不可变类型(或手动实现get-only属性+构造函数)
public record Catalog(IReadOnlyList<Product> Products);
public record Product(int Id, string Name, string Category, decimal Price);

// 修改缓存方法
private readonly object _lockObj = new object();
public Catalog GetCatalog(IMemoryCache cache)
{
    var cachedCatalog = cache.Get<Catalog>("catalogKey");
    if (cachedCatalog == null)
    {
        lock (_lockObj)
        {
            // double-check锁确保线程安全
            cachedCatalog = cache.Get<Catalog>("catalogKey");
            if (cachedCatalog == null)
            {
                cachedCatalog = loadCatalogData(); // 返回不可变实例
                cache.Set("catalogKey", cachedCatalog, TimeSpan.FromHours(3));
            }
        }
    }
    return cachedCatalog; // 直接返回缓存实例,不可变无需担心修改
}

2. 缓存原始对象,每次请求返回深拷贝实例

放弃缓存JSON字符串,改为缓存加载好的Catalog对象,每次请求时对缓存对象做深拷贝,避免JSON序列化/反序列化的双重内存消耗(JSON字符串本身比强类型对象占用更多内存)。

代码示例

private readonly object _lockObj = new object();
public Catalog GetCatalog(IMemoryCache cache)
{
    var cachedCatalog = cache.Get<Catalog>("catalogKey");
    if (cachedCatalog == null)
    {
        lock (_lockObj)
        {
            cachedCatalog = cache.Get<Catalog>("catalogKey");
            if (cachedCatalog == null)
            {
                cachedCatalog = loadCatalogData();
                cache.Set("catalogKey", cachedCatalog, TimeSpan.FromHours(3));
            }
        }
    }
    // 返回深拷贝实例,确保每个用户拿到独立对象
    return DeepClone(cachedCatalog);
}

// 实现深拷贝(可选更高效的方式,如BinaryFormatter/手动拷贝)
private Catalog DeepClone(Catalog source)
{
    var json = JsonSerializer.Serialize(source);
    return JsonSerializer.Deserialize<Catalog>(json);
}

3. 使用对象池复用大对象实例

利用.NET的Microsoft.Extensions.ObjectPool预创建多个Catalog实例,每次请求时从池中取出实例,将缓存的原始数据复制到实例中,用完后返回池。这种方式能减少大对象频繁创建导致的内存碎片化和GC压力。

代码示例

private readonly ObjectPool<Catalog> _catalogPool;
private readonly object _lockObj = new object();

// 构造函数注入对象池
public YourService(ObjectPool<Catalog> catalogPool)
{
    _catalogPool = catalogPool;
}

public Catalog GetCatalog(IMemoryCache cache)
{
    var cachedCatalog = cache.Get<Catalog>("catalogKey");
    if (cachedCatalog == null)
    {
        lock (_lockObj)
        {
            cachedCatalog = cache.Get<Catalog>("catalogKey");
            if (cachedCatalog == null)
            {
                cachedCatalog = loadCatalogData();
                cache.Set("catalogKey", cachedCatalog, TimeSpan.FromHours(3));
            }
        }
    }

    // 从对象池获取实例
    var instance = _catalogPool.Get();
    // 将缓存数据复制到实例(需实现属性映射逻辑,如手动赋值/AutoMapper)
    MapCatalogData(cachedCatalog, instance);
    return instance;
}

// 用ActionFilter回收实例(需注册到管道)
public class CatalogPoolFilter : IAsyncActionFilter
{
    private readonly ObjectPool<Catalog> _catalogPool;

    public CatalogPoolFilter(ObjectPool<Catalog> catalogPool)
    {
        _catalogPool = catalogPool;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var resultContext = await next();
        if (resultContext.Result is ObjectResult objResult && objResult.Value is Catalog catalog)
        {
            // 重置实例状态后返回池
            ResetCatalog(catalog);
            _catalogPool.Return(catalog);
        }
    }
}

// 辅助方法:重置实例状态
private void ResetCatalog(Catalog catalog)
{
    catalog.Products.Clear(); // 假设Products是可清空集合,需根据实际结构调整
    // 重置其他属性...
}

// 辅助方法:映射数据到实例
private void MapCatalogData(Catalog source, Catalog target)
{
    // 手动复制属性,或用AutoMapper
    target.Id = source.Id;
    target.Products.AddRange(source.Products);
    // 复制其他属性...
}

4. 拆分缓存粒度,按需加载

不缓存整个大目录,按类别、品牌等维度拆分多个小缓存块,用户请求时仅加载所需部分,大幅降低单次请求的内存占用。

代码示例

public List<Product> GetProductsByCategory(IMemoryCache cache, string category)
{
    var cacheKey = $"catalog_category_{category}";
    var products = cache.Get<List<Product>>(cacheKey);
    
    if (products == null)
    {
        // 从全量目录中提取对应类别数据(或直接查询数据库)
        var fullCatalog = GetFullCatalog(cache);
        products = fullCatalog.Products.Where(p => p.Category.Equals(category, StringComparison.OrdinalIgnoreCase)).ToList();
        cache.Set(cacheKey, products, TimeSpan.FromHours(3));
    }
    
    // 返回浅拷贝(如果Product是只读或无需修改)
    return new List<Product>(products);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 01:25:05