环境:
- window10
- vs2022
- .net core 3.1
前言: 缓存是个好东西,转为解决高并发问题,在性能的这场较量中屡试不爽。但我们使用它的姿势可能并不对,甚至能给自己挖一个大坑。
参考:
《Redis缓存一致性问题解决方案》
1. 问题举例
以最常见的redis缓存举例,看下面的伪代码:
public class TestClass
{
private Redis redis;
private DB db;
public UserEntity GetUserById(int id)
{
UserEntity user = null;
var key = $"user:{id}";
if (redis.Get<UserEntity>(key) == null)
{
user = db.Select<UserEntity>($"select * from user where id={id}");
redis.SetString(key, user.ToJson());
}
return user;
}
public void UpdateUser(UserEntity user)
{
var key = $"user:{user.Id}";
db.Execute($"update user set Name='{user.Name}' where id={user.Id}");
redis.Delete(key);
}
}
上面的代码有问题:有可能造成数据库和缓存数据不一致情况。
按照上面注释中的标记的顺序理解,我们就会发现问题所在: 上面说的是分布式下redis缓存的情况,那么单机呢?单机应该没这个问题吧?
答:把上图中的程序A、程序B换成 线程A和线程B。。。
那把Redis改成程序内存呢?
答:假装上图中的redis是嵌在程序内的呢,和放在外面不是一个道理吗。。。
那么问题的本质是什么?
本质是:多线程环境下,对缓存和数据库有双写的情况。我们无法保证“5. 缓存查询结果” 这一步写入的是最新的DB数据!!!
既然这样,那么我们在程序中加个锁不就行了吗? 这种想法不可取,在查询中加锁吗?查询变成单线程的了?
2. 解决方案
那么,有解决办法吗? 有,参照数据库的主从复制架构,看下面的架构图:
- 当查询时:
首先从cache中查询,查询失败后,拼接查询sql并将查询sql和key发送到队列,程序本身要临时从db中查询返回; 消费者接到缓存请求后,执行sql并将结果以key存到redis中,等待下次使用;
- 当写入是:
程序先更新db数据,然后发送刷新cache请求; 消费者接到刷新cache请求后,就将redis中对应的key删掉或更新key对应的值(sql重新执行一遍);
现在来看看是否会有缓存和db数据不一致问题? 没有了吧(当然,如果程序意外挂掉仍然会有问题,比如:"a. 更新DB" 刚执行完,程序就crash了,"b. 发送清空某个key请求" 还没来得及执行)。
|