<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Caching on Coding with Denglei</title>
    <link>https://blog.denglei.me/tags/caching/</link>
    <description>Recent content in Caching on Coding with Denglei</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 25 Jun 2026 23:06:22 +0800</lastBuildDate>
    <atom:link href="https://blog.denglei.me/tags/caching/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>ASP.NET Core 内存缓存实战：一篇搞懂该怎么配、怎么避坑</title>
      <link>https://blog.denglei.me/posts/aspnet-core-memory-cache-guide/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/aspnet-core-memory-cache-guide/</guid>
      <description>&lt;h2 id=&#34;引言&#34;&gt;引言&lt;/h2&gt;&#xA;&lt;p&gt;这篇文章我们来聊一聊 asp.net core 的内存缓存。asp.net core 内存缓存（IMemoryCache）是一个轻量级的缓存方案，适用于单实例应用或者分布式环境中的本地缓存。它提供了简单的 API 来存储和检索数据，同时支持过期策略、优先级设置等功能。&lt;/p&gt;&#xA;&lt;h2 id=&#34;什么是缓存&#34;&gt;什么是缓存&lt;/h2&gt;&#xA;&lt;p&gt;从用户请求到数据库返回数据，这是一个漫长的过程（夸张了点，通常也就是几十毫秒到几百毫秒）。可是又不止一个用户在访问，甚至同一个用户在短时间内发起多个相似请求，这时候每次都走完整个流程就显得很浪费了。缓存的作用就是把之前请求的结果存储起来，下次有相同请求时直接返回缓存结果，省去重复计算和数据库访问的开销。所以，缓存是一个存储机制，使用它的目的是为了提高性能和响应速度。&lt;/p&gt;&#xA;&lt;h2 id=&#34;aspnet-core-中的缓存类型&#34;&gt;asp.net core 中的缓存类型&lt;/h2&gt;&#xA;&lt;p&gt;在 asp.net core 中，提供了三种常用的缓存方案：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;内存缓存（IMemoryCache）：适用于单实例应用或者分布式环境中的本地缓存。&lt;/li&gt;&#xA;&lt;li&gt;分布式缓存（IDistributedCache）：适用于分布式环境中的共享缓存，常见实现有 Redis、SQL Server 等。&lt;/li&gt;&#xA;&lt;li&gt;Hybrid 缓存：结合内存缓存和分布式缓存，先查内存缓存，未命中再查分布式缓存。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;每一类的缓存都有自己的使用场景和适用边界，选择合适的缓存方案是非常重要的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;aspnet-core-的内存缓存-imemorycache&#34;&gt;asp.net core 的内存缓存 IMemoryCache&lt;/h2&gt;&#xA;&lt;p&gt;asp.net core 的内存缓存使用本地内存来临时存储数据，所以它的访问速度非常快，通常远快于网络和数据库访问（具体耗时取决于数据大小与序列化开销）。但是内存缓存也有一些限制，不能在多实例中共享数据。此外，内存缓存的数据会随着应用重启而丢失，所以它更适合存储一些临时数据或者不需要持久化的数据。存放在本机内存中，会占用服务器的内存资源，如果缓存的数据量过大或者过期策略设置不当，可能会导致内存压力增大、频繁回收或者性能问题。因此，在使用内存缓存时需要注意以下几点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;不要将外部输入作为缓存键，因为这类输入可能会消耗不可预测的内存资源，导致缓存被恶意攻击或者误用。&lt;/li&gt;&#xA;&lt;li&gt;设置合理的过期时间限制缓存的增长。&lt;/li&gt;&#xA;&lt;li&gt;限制缓存的大小，避免占用过多内存资源。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;在-aspnet-core-中使用-imemorycache&#34;&gt;在 asp.net core 中使用 IMemoryCache&lt;/h2&gt;&#xA;&lt;p&gt;在 asp.net core 中使用 IMemoryCache 非常简单，首先需要在 Program.cs 中注册服务：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WebApplication&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;builder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddMemoryCache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后在需要使用缓存的地方注入 IMemoryCache：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MyService&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IMemoryCache&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_cache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MyService&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IMemoryCache&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;_cache&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetDataAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_cache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TryGetValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 从缓存中获取数据&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FetchDataFromDatabaseAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 从数据库获取数据&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;_cache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TimeSpan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FromMinutes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 将数据存入缓存，设置过期时间为5分钟&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上面的示例中，我们首先尝试从缓存中获取数据，如果缓存命中就直接返回，否则就从数据库获取数据，并将结果存入缓存中，设置过期时间为5分钟。这样下次有相同请求时，就可以直接从缓存中获取数据，提高性能和响应速度。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Redis：延迟双删的适用边界与落地细节</title>
      <link>https://blog.denglei.me/posts/redis-cache-pattern-delayed-double-delete/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/redis-cache-pattern-delayed-double-delete/</guid>
      <description>&lt;p&gt;延迟双删不是新概念，但线上一出缓存脏读，我曾经在项目中把它当成标准答案直接套进去。结果通常是代码写了两次删除，问题却没真正收住。&lt;/p&gt;&#xA;&lt;p&gt;这篇就聚焦一个知识点：延迟双删到底解决什么问题，为什么它只能改善最终一致概率，以及在 .NET 服务里怎么把第二次删除做得更稳一点。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1-问题背景数据库已经更新为什么缓存里还是旧值&#34;&gt;1. 问题背景：数据库已经更新，为什么缓存里还是旧值&lt;/h2&gt;&#xA;&lt;p&gt;聊一个高频场景：商品详情页读 Redis，后台商品编辑写数据库。读流量远大于写流量，最常见的缓存策略是 Cache Aside。&lt;/p&gt;&#xA;&lt;p&gt;我的更新代码长这样：先更新数据库，再删除缓存。平时看起来没什么问题，但高并发下还是会偶发脏数据。业务侧看到的现象一般是：管理后台已经改价成功，前台用户短时间内还能查到旧价格。&lt;/p&gt;&#xA;&lt;p&gt;关键不在“删没删缓存”，而在并发时序。&lt;/p&gt;&#xA;&lt;p&gt;一个典型过程是这样的：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;线程 A 更新数据库中的商品价格&lt;/li&gt;&#xA;&lt;li&gt;线程 A 删除 Redis 中的商品缓存&lt;/li&gt;&#xA;&lt;li&gt;线程 B 正好在这个空档读缓存未命中，开始查数据库&lt;/li&gt;&#xA;&lt;li&gt;线程 B 读到的仍然是旧值，或者读到了事务提交前的旧快照&lt;/li&gt;&#xA;&lt;li&gt;线程 B 把旧值重新写回 Redis&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这时候数据库是新值，缓存却又变回旧值了。问题根因不是删除动作本身，而是删除之后，旧数据又被别的请求回填进缓存。&lt;/p&gt;&#xA;&lt;p&gt;如果只看文字，这个并发窗口不算直观。把它画成时序图会更清楚：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram&#xA;    autonumber&#xA;    participant A as 写线程A&#xA;    participant R as Redis&#xA;    participant D as Database&#xA;    participant B as 读线程B&#xA;&#xA;    A-&amp;gt;&amp;gt;D: 更新商品价格为新值&#xA;    A-&amp;gt;&amp;gt;R: 删除商品缓存&#xA;    B-&amp;gt;&amp;gt;R: 读取商品缓存&#xA;    R--&amp;gt;&amp;gt;B: 未命中&#xA;    B-&amp;gt;&amp;gt;D: 查询商品数据&#xA;    D--&amp;gt;&amp;gt;B: 返回旧值或旧快照&#xA;    B-&amp;gt;&amp;gt;R: 回填旧值到缓存&#xA;    B--&amp;gt;&amp;gt;B: 后续请求命中旧缓存&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这张图里最关键的不是“删缓存”这一步，而是删完之后到下一次稳定回填新值之前，中间存在一个旧值重新进入 Redis 的窗口。延迟双删补的就是这个窗口。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
