缓存读写代码逻辑的正确姿势

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

逻辑概述

  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();
        }
    }
}

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

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

posted @ 2026-01-30 22:26  Rick Carter  阅读(0)  评论(0)    收藏  举报