再谈缓存与数据库一致性问题

什么情况下需要引入缓存?

一般是针对读多写少、很少会发生变化的数据做缓存。例如:账号余额查询,交易记录,商品展示等等。做缓存有几个好处:

  1. 防止请求直接穿透DB。因为频繁操作DB会耗费大量数据库连接,甚至会拖垮整个应用。
  2. 提升访问速度。

市面上比较流行的缓存技术: Redis、Memcache、ehcache、guava cache,有的也会利用mongodb做缓存。

缓存的副作用

缓存技术虽然可以显著提升查询性能,但是针对那些需要保证数据库和缓存一致性的业务就没那么简单了。xxx突然反馈"明明我早上充值了10000元,怎么我的账户余额没有更新?"这种问题一般是非常严重的,苦逼的你肯定又要被叫进小黑屋谈话了..."赶紧去给我想解决方案!"。

更新缓存的几个时机
  • 先执行业务,再更新缓存
  • 先更新缓存,再执行业务
先执行业务还是先更新缓存?

就拿上面那个例子进行说明。
        如果先执行业务,清空缓存失败就会导致数据不一致,用户本来充值10000元,但是缓存中还是原来的数据。
        那么先清缓存呢?充值操作时先清除缓存,如果删除失败则抛出异常或者进行熔断,缓存删除成功再执行具体业务。此时如果业务执行成功,用户查询账户余额的时候发现缓存为空,则从数据库查询并更新缓存,能保证数据库缓存一致。如果业务执行失败,也是从数据库再查询一次,数据库缓存依旧保证一致,只是做了一次缓存穿透。
        缓存清空后,如果有不法分子对这个账户余额查询接口进行高频次访问,则有可能会导致过多请求直连DB,造成数据库崩溃,最终导致系统宕机。这种时候一定要做好限流工作,可以通过redis+lua的形式来做限流,如果查询到缓存为空,则在某个时间段内的请求数不能超过阈值。只允许少量请求直连DB,当业务执行成功,则会更新缓存,断开限流操作。

还有其它问题吗?

即使先操作缓存再执行业务能解决大多数缓存数据库不一致的问题,但是难免也有一些苛刻的场景。考虑如下一种场景:假设某个业务流程很长,里面涉及到各种数据的处理。业务开始时我就清空掉缓存,当业务执行到一半时,有查询请求进入,然后查询DB更新缓存,更新完缓存以后,业务才执行成功。此时数据库缓存仍然不一致!


数据库缓存不一致

针对这种场景,我觉得可以这样解决:

  1. 当某个业务开始时,记录一个状态,表示这个业务已经开始。
  2. 当业务结束时将此记录移除。
  3. 当业务开始尚未结束时,如果这个时间段有查询请求,则可以让其正常查询DB然后更新缓存,让业务结束后再次触发更新缓存即可,因为是触发更新,所以有可能出现小段时间内数据不一致的问题。也可以让查询请求同步等待缓存更新,只是速度上稍慢一点,等业务执行完成后查询请求才返回。

网上也有基于队列的解决方案,大体思想就是:
        操作业务时,直接将其丢进队列,等待异步执行;当出现读请求时,先读缓存,成功则返回,如果缓存不存在,再去判断队列头部是否是同一条记录的更新请求,如果是,为了不打断其操作,将读请求也丢进队列,然后同步等待缓存更新完成;如果不是,说明该更新请求早已完成,直接读数据库并缓存即可,不要入队列。

你可能感兴趣的:(再谈缓存与数据库一致性问题)