缓存读写代码逻辑的正确姿势
缓存通常用于提高数据访问的效率。一般来说,缓存读取和写入的逻辑遵循“先从缓存取,取不到再从数据库获取并写回缓存”的原则。为了避免多个线程同时修改缓存数据,我们需要加锁来保证数据一致性。
逻辑概述
- 读取缓存:缓存命中直接返回。
- 缓存未命中:加锁,然后再次读取缓存,缓存命中直接返回。
- 缓存还是未命中:执行数据库查询并更新缓存。
- 返回数据。
代码大致这样写
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();
}
}
}
关键是缓存在锁前和锁后都要读一次,为了减少多个线程都在等待锁的情况,同时进来查询数据库写缓存。
分布式情况下会更复杂一些
作者:Rick Carter
出处:http://pains.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
浙公网安备 33010602011771号