惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
博客园 - Franky
J
Java Code Geeks
V
Visual Studio Blog
G
Google Developers Blog
罗磊的独立博客
MongoDB | Blog
MongoDB | Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Recent Announcements
Recent Announcements
Last Week in AI
Last Week in AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Stack Overflow Blog
Stack Overflow Blog
博客园 - 司徒正美
The GitHub Blog
The GitHub Blog
腾讯CDC
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
博客园 - 【当耐特】
IT之家
IT之家
I
InfoQ
U
Unit 42
C
Check Point Blog
Martin Fowler
Martin Fowler

博客园 - Rick Carter

官方APP太难用了,所以我写了个 OpenNas App 死锁是怎么发生的,举个简单的例子 hangfire内部执行器是同步的,会导致死锁 dotnet未捕获异常导致系统崩溃问题 dotnet-dump安装、收集dump和崩溃自动收集dump EFCore中巧妙利用ToQueryString()实现批插(不借助第三方包) 修复达梦EFCore驱动布尔类型兼容问题 dotnet使用redis时需要注意的问题 巧用 using 作用域(IDisposable)的生命周期包装特性 实现前后置处理 非托管内存怎么计算? dotnet集合类型性能优化的两个小儿科的知识点 一个前端树形控件联动勾选框卡顿问题及解决 .net c# Func<Task>及变体做为多播委托异步执行会另开线程的问题 关于EFCore插件API使用中踩过的坑 利用C#9.0中的record提高性能 dotnet CultureInfo遇到欧洲如俄文小数点是逗号想转点的解决办法 hangfire.entityframeworkcore这个库因为System.Threading.Timer未停止也未释放而导致的性能问题 达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTime to DateTimeOffset 达梦DOTNET驱动DM.Provider8.3.1.30495存在空字符串插入变DBNull的问题 是否可以考虑做一个dotnet应用的性能诊断工具
缓存读写代码逻辑的正确姿势
Rick Carter · 2026-01-30 · via 博客园 - Rick Carter

缓存通常用于提高数据访问的效率。一般来说,缓存读取和写入的逻辑遵循“先从缓存取,取不到再从数据库获取并写回缓存”的原则。为了避免多个线程同时修改缓存数据,我们需要加锁来保证数据一致性。

逻辑概述

  1. 读取缓存:缓存命中直接返回。
  2. 缓存未命中:加锁,然后再次读取缓存,缓存命中直接返回。
  3. 缓存还是未命中:执行数据库查询并更新缓存。
  4. 返回数据。

代码大致这样写

public class CacheService
{
    private readonly ICache _cache;
    private readonly IDatabase _database;
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public CacheService(ICache cache, IDatabase database)
    {
        _cache = cache;
        _database = database;
    }

    public string GetDataFromCacheOrDb(string key)
    {
        // 1. 尝试从缓存获取数据
        var data = _cache.Get(key);
        if (data != null)
        {
            return data;  // 缓存命中,直接返回
        }

        // 2. 如果缓存中没有,尝试加锁并获取数据
        _semaphore.Wait();

        try
        {
            // 再次检查缓存(可能另一个线程已经填充了缓存)
            data = _cache.Get(key);
            if (data != null)
            {
                return data;  // 缓存命中,直接返回
            }

            // 3. 从数据库获取数据
            data = _database.Query(key);

            // 4. 将数据写入缓存
            _cache.Set(key, data);

            return data;
        }
        finally
        {
            // 释放锁
            _semaphore.Release();
        }
    }
}

关键是缓存在锁前和锁后都要读一次,为了减少多个线程都在等待锁的情况,同时进来查询数据库写缓存。

分布式情况下会更复杂一些