1. 缓存使用目的
缓存由于其高并发和高性能的特性,已经在项目中被广泛使用(IO cost比数据库低了几个数量级,redis是从内存中读取数据, mysql是从磁盘读取数据)
2. 读取数据
流程无异议:
- 判断是否有缓存
- 有,直接返回数据给调用端. 无,跳到3
- 从数据库加载数据,有数据直接写入缓存,无数据则跳到4
- 若不写入缓存, 可能发生缓存穿透(缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。)。可以缓存空对象来解决缓存穿透问题。
- 返回空数据。
3. 更新缓存
3.1更新策略
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
3.2 策略分析
- 先更新数据库,再更新缓存
技术角度分析: 多线程更新数据库和缓存时,并不能保证次序,比如A线程先发起,然后B线程后发起,但是B线程可能比A线程先更新缓存,这就导致了缓存最后的值是A线程的值导致数据库和缓存不一致
业务角度分析:1. 加入业务是写多读少的场景,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。2. 加入写入缓存的值是要在数据库更新后,经过一系列计算再更新缓存的,采用此方案会造成性能浪费。
- 先删除缓存,再更新数据库
比如一个写线程A, 一个读线程B,在A线程删除缓存后,B线程发现缓存不存在就去读取数据库旧值,然后更新缓存,然后A线程才更新数据库,如此也会造成数据库缓存不一致。
- 先更新数据库,再删除缓存
这种方式同样有并发访问问题。同样2个线程,缓存失效,线程A探知缓存失效,查询数据库,取得旧值,线程B更新数据库并且删除缓存key,线程A将旧值写入缓存。
但一般来说数据库IO远远慢比缓存IO,所以一般线程A写入缓存会发生在线程B删除缓存之前。
另外还有一个问题就是删除缓存不成功,用重试机制保障删除操作,一个是用消息队列,另一个是订阅binlog日志来更新缓存(canal)。