缓存数据库一致性
文章目录
- 常用的一致性策略
- Cache Aside旁路缓存
- read through或write through
- write back
- 写请求先写缓存还是先写数据库,写缓存还是删除缓存,怎么操作才能保证缓存和数据库的一致性?
- 先更新缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 延迟双删
- 先更新数据库,再删除缓存
- 说明
常用的一致性策略
Cache Aside旁路缓存
- 定义:读请求先从缓存中去读,读不到再从数据库中读,然后写入缓存
- 特点:业务代码既要操作缓存,又要操作数据库,以数据库的数据为主,缓存只是暂时存储数据而已。
read through或write through
- 设计思想:不直接操作数据库,只操作缓存,然后让缓存自身去操作数据库。
- 定义:
read through指每次读都要读缓存,如果有就返回数据,没有缓存自身从数据库中读数据,然后写入缓存;write through指写数据的时候直接写缓存,缓存自身去更新数据库。也就是说我们只需操作缓存即可,剩余操作由缓存中间件自身去完成。
write back
- 定义:只操作缓存,然后让缓存自身去操作数据库。与上面不同之处是写回是批量的、异步的去写数据库。
- 适合写多读少的场景。但是由于是异步写入数据库,所以有数据丢失的风险。
写请求先写缓存还是先写数据库,写缓存还是删除缓存,怎么操作才能保证缓存和数据库的一致性?
先更新缓存,再更新数据库
可以看到此时缓存值为B,数据库值为A,出现缓存和数据库不一致。因为更新缓存和数据库的操作不是原子性的,所以会出现并发问题。
先更新数据库,再更新缓存
可以看到此时缓存值为A,数据库值为B,也出现缓存和数据库不一致。
先删除缓存,再更新数据库
这个方法也会出现缓存和数据库不一致的情况
延迟双删
先删除缓存再更新数据库,让线程等待一会儿然后再删数据库。为什么要等一会再删,这是要让另外一个线程能在等一会的这个时间去完成读数据库并写缓存这个操作,然后给它删了。
那么问题来了,要延迟多久(等多久)?
这要考虑当时的网络情况怎么样,服务器的负载和数据库的负载等情况,没有一个标准的时间。
先更新数据库,再删除缓存
这个方法也会出现不一致的情况,但是概率非常小,因为必须满足两个条件:
- 刚好是这个 key 刚好过期,然后刚好又要更新数据
- 请求2刚更新完数据,然后删除缓存之后,请求1才写入缓存。
也就是说写缓存的速度要慢于更新数据库+删除缓存的速度,这个概率是非常低的。所以先更新数据库再删除缓存在绝大多数情况下都是可行的。
那么问题来了,如果更新数据库成功,删除缓存失败了怎么解决呢?
- 把删除缓存的操作放到 MQ 中,MQ 去异步地删除缓存。如果删除失败了,那就重试。
- 监听数据库的
bin log,更新数据库成功会产生一条bin log,因此就可以监听这个bin log,然后删除缓存。同样地,如果删除失败了,那就重试。这个方法的优点是不入侵业务代码,比使用 MQ 的方案好。
但是实际上公司的 MySQL 都是主从部署读写分离的,主库只能写从库只能读,然后主库更新的数据需要花一点时间同步给从库。所以当请求1先更新了这个数据库,然后异步地删除了缓存,但是写的是主库,从库还没写,从库需要等待主库同步数据过来,那从库还没等到主库传来的数据此时又来了一个请求读了从库的旧数据然后写入了缓存,缓存和数据库又出现不一致了,但是这种概率也是非常低的,如果非要解决这个问题,那可以在主库更新之后的一段时间之内,让其他请求强制去读主。
综上所述,要想保证缓存和数据库的完全一致性,几乎是完全不可能的,现有的手段只能尽可能地去保证缓存和数据库在绝大多数的情况下是一致的,然后给数据设置一个过期时间。即便在非常极端的情况下不一致了,也能让这个脏数据过期失效。
说明
本文参考@程序员回家养猪博主的视频进行总结和编写。
