C#怎么使用内存缓存?IMemoryCache实战教程详解

来源:MAC教程作者:宋琮安头衔:草根站长
导读:本期聚焦于宋琮安创作的《C#怎么使用内存缓存?IMemoryCache实战教程详解》,敬请观看详情。为什么同样的接口,加了缓存之后响应时间从几百毫秒降到几毫秒?答案往往就藏在IMemoryCache的正确使用上。本文围绕C#中的内存缓存组件IMemoryCache展开,从依赖注入注册开始,逐步演示基础读写、GetOrCreate快捷方法、过期时间的三种设置方式(绝对过期、滑动过期、组合过期),再深入讲解缓存穿透的应对手段、缓存雪崩的预防思路,以及基于ChangeToken和主动失效的进阶用法。文中所有代码均基于.NET 6及以上版本,可直接复制到项目中运行,适合正在做性能优化的开发者参考。

缓存是提升系统性能最直接的手段之一。在C#的ASP.NET Core生态里,微软提供了内置的内存缓存组件IMemoryCache,它把数据存储在当前进程的内存中,读写速度极快,不需要额外部署Redis这类分布式缓存服务,非常适合单实例部署的应用或者作为分布式缓存的前置缓存层。这篇文章会从零开始讲解IMemoryCache的注册、读写、过期策略,以及实际项目中容易踩到的坑。

C#怎么使用内存缓存?IMemoryCache实战教程详解

一、注册与基本使用

使用IMemoryCache的第一步是在依赖注入容器中注册内存缓存服务。在Program.cs中加入一行代码即可:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
var app = builder.Build();
app.Run();

注册完成后,就可以在任何通过DI创建的类中注入IMemoryCache接口。它最核心的几个方法是SetGetTryGetValueRemove。看一个基础的例子:

public class ProductService
{
    private readonly IMemoryCache _cache;

    public ProductService(IMemoryCache cache)
    {
        _cache = cache;
    }

    public Product? GetProduct(int id)
    {
        // 尝试从缓存获取
        if (_cache.TryGetValue($"product_{id}", out Product? product))
        {
            return product;
        }

        // 缓存未命中,从数据库查询
        product = QueryFromDb(id);

        // 写入缓存,60秒后过期
        _cache.Set($"product_{id}", product, TimeSpan.FromSeconds(60));
        return product;
    }

    public void RemoveProduct(int id)
    {
        _cache.Remove($"product_{id}");
    }

    private Product? QueryFromDb(int id) { /* 数据库查询逻辑 */ return new Product(); }
}

除了这种手动判断的写法,还有一个更简洁的API叫GetOrCreate,它把"查缓存、未命中则执行工厂方法、写入缓存"三步合并成了一次调用,代码可读性明显更好:

public Product GetProduct(int id)
{
    return _cache.GetOrCreate($"product_{id}", entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
        return QueryFromDb(id);
    })!;
}

需要注意一点:IMemoryCache存储的是对象的引用而不是副本(对于引用类型而言)。也就是说,你从缓存里取出来一个对象并修改了它的属性,缓存中的对象也会跟着变,因为它们是同一个实例。如果多个线程同时拿到这个对象并发修改,就可能产生数据错乱。稳妥的做法是取出来之后做一次深拷贝,或者把缓存的对象设计成不可变对象。

二、三种过期策略的区别与选择

IMemoryCache的过期机制是很多人容易混淆的地方,它支持绝对过期、滑动过期以及两者组合三种方式,行为差异很大,直接选错可能导致缓存失效或数据过期不更新。

绝对过期(AbsoluteExpiration)是指从设定时刻起,经过固定时间后缓存必定失效,无论期间有没有被访问。适合数据一致性要求较高的场景,比如商品库存、活动配置这类数据,即使访问频繁也必须定期回源刷新。

_cache.Set("key", value, new MemoryCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});

滑动过期(SlidingExpiration)则相反,只要缓存条目持续被访问,过期时间就会不断向后顺延;只有连续一段时间没人访问,它才会被清除。典型的应用是用户会话数据,活跃用户的会话一直保活,不活跃的用户数据自动清理,节省内存。

_cache.Set("key", value, new MemoryCacheEntryOptions
{
    SlidingExpiration = TimeSpan.FromMinutes(5)
});

两者组合使用时,滑动过期生效的前提是不超过绝对过期的上限。比如设置滑动5分钟、绝对30分钟,那么即使条目每分钟都被访问,30分钟后也一定会过期。另外还有Size属性可以限制单个条目占用的内存大小,配合注册时的SizeLimit做整体内存控制,避免缓存无限膨胀把进程撑爆。还有一个细节值得知道:过期条目并不是到点立即删除的,内部有个后台扫描机制定期清理,所以在Get一个刚过期的key时才会触发实际移除,这在高内存压力下表现会更明显。

三、缓存穿透与雪崩的应对

缓存穿透是指查询一个数据库里根本不存在的数据,由于缓存中永远不会有这条记录,每次请求都会打到数据库。如果有人恶意构造大量不存在的ID发起请求,数据库压力会瞬间飙升。应对方式是缓存空值:

public Product? GetProduct(int id)
{
    if (_cache.TryGetValue($"product_{id}", out Product? cached))
    {
        // 缓存了空对象,说明数据库中确实没有这条数据
        return cached;
    }

    var product = QueryFromDb(id);

    if (product is null)
    {
        // 数据库不存在也写入缓存,短时间过期,防止穿透
        _cache.Set($"product_{id}", (Product?)null, TimeSpan.FromSeconds(30));
        return null;
    }

    _cache.Set($"product_{id}", product, TimeSpan.FromMinutes(10));
    return product;
}

缓存雪崩是指大量缓存条目在同一时刻集中过期,请求全部涌向数据库。预防办法很简单:在基础过期时间上加一个随机偏移量,把过期时间打散。比如原本都是10分钟过期,改成10分钟加上0到3分钟的随机值,各条目的过期时间就错开了。

另外要提醒的是,内存缓存是进程级别的。如果应用部署了多个实例(比如容器编排扩容到多个Pod),每个实例的缓存相互独立,可能出现数据不一致的情况。这种场景下要么接受短暂不一致,要么改用Redis这类分布式缓存,要么在数据变更时通过消息广播通知所有实例清理各自缓存。单实例应用则完全不用担心这个问题,IMemoryCache就是最优解。

四、进阶用法:主动失效与缓存依赖

除了靠时间过期,IMemoryCache还支持主动让缓存失效。最直接的就是在数据发生变更时调用Remove方法删除对应条目,下次查询会自动回源重建。对于用户提交修改后台立即生效的场景,这种做法比等待过期可靠得多。

更进一步,可以利用RegisterPostEvictionCallback注册条目被移除后的回调,做一些日志记录或联动清理:

var options = new MemoryCacheEntryOptions()
    .SetAbsoluteExpiration(TimeSpan.FromMinutes(10))
    .RegisterPostEvictionCallback((key, value, reason, state) =>
    {
        Console.WriteLine($"缓存条目 {key} 被移除,原因:{reason}");
    });

_cache.Set("key", data, options);

还可以通过ExpirationTokens挂载CancellationToken实现缓存依赖,当Token被触发时缓存立即失效。这在配合文件监听、数据库变更通知等场景非常有用,比如配置文件一改动,相关缓存马上作废重建,业务代码完全无感知。

最后总结一下使用原则:读多写少的数据优先缓存,过期时间结合业务容忍度设置,热点数据加上随机过期防止雪崩,不存在的数据缓存空值防止穿透,多实例部署时评估是否需要分布式缓存。掌握这些,IMemoryCache基本就能覆盖日常开发中百分之九十的缓存需求了。

C#内存缓存IMemoryCache.NET缓存修改时间:2026-09-07 22:46:35

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260907/52493.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。