1.什么情况下会产生缓存和数据库不一致的情况
在高并发情况下,往往会极其频繁的去操作数据库,当请求量大了之后,数据库就会扛不住,挂了,这时我们会引入缓存,将部门数据放在缓存中,减少数据库的压力。
这就意味着我们读取数据可以从两个地方读取,那必然会导致数据不一致的情况。
2.如何去更新缓存和数据库
大致有四种方法
- 先更新数据库,在更新缓存
- 先更新缓存,在更新数据库
- 先删除缓存,在更新数据库
- 先更新数据库,在删除缓存
2.1 先更新数据库,在更新缓存
产生数据不一致
产生原因
更新数据库和更新缓存都是两个独立的操作,没有对操作做并发控制,那么在并发下,写入缓存和数据的顺写就可能会发生变化,导致数据不一致。
解决方法
- 加锁,在更新缓存前加一个分布式锁,保证同一时间内只有一个请求可以更新缓存。(缺点:加锁必然会影响数据写入的性能)
- 给缓存加过期时间,在更新缓存后,给缓存加一个较短的过期时间,出现数据不一致,也可以很快的更新。(缺点:还是会出现数据不一致的情况,具体看业务场景是否允许)
2.2 先更新缓存,在更新数据库
产生数据不一致
产生原因(和2.1一样)
更新数据库和更新缓存都是两个独立的操作,没有对操作做并发控制,那么在并发下,写入缓存和数据的顺写就可能会发生变化,导致数据不一致。
解决方法(和2.1一样)
- 加锁,在更新缓存前加一个分布式锁,保证同一时间内只有一个请求可以更新缓存。(缺点:加锁必然会影响数据写入的性能)
- 给缓存加过期时间,在更新缓存后,给缓存加一个较短的过期时间,出现数据不一致,也可以很快的更新。(缺点:还是会出现数据不一致的情况,具体看业务场景是否允许)
2.3 先删除缓存,在更新数据库
产生数据不一致
解决方法
延迟双删,先删除缓存,在更新数据库,延迟一段时间,再删除缓存
注意:
延迟一段时间,这个时间必须大于一次写入操作的时间
延迟多长时间具体看调试而定,极端情况下还是会出现数据不一致
2.4 先更新数据库,在删除缓存(推荐)
产生数据不一致
推荐原因
实际中,这个问题出现的概率并不高。因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。
解决方法
- 加入缓存过期时间
- 加入缓存过期时间,在异步操作缓存(重试机制和订阅mysql binlog)
重试机制(删除失败重试删除)
借助消息队列完成,把删除缓存放入到消息队列,由消费者完成删除缓存操作
如果第一次删除缓存失败,消息队列重新读取数据,进行删除;
删除成功,就把数据移除消息队列。
订阅 MySQL binlog,再操作缓存
只要更新数据库成功,就会产生一条变更日志,记录在 binlog 里。
于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。
Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。