持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
一、前言
在日常开发中,经常使用到缓存来避免所有的请求打在数据库上,减轻数据库的压力,虽然这样确实能大大缓解压力,但是也会造成数据的不一致问题,如何解决和生产中如何解决是一个值得思考的问题。
二、问题产生
问题发生的原因有两点:
操作失败问题
操作有两步,一是操作数据库、二是操作缓存,不管顺序如何,总是会有一方失败。
并发问题
在并发情景下,两个线程同时操作数据,B线程在A线程操作的过程中更新了数据,A线程最终更新了缓存,由于B已经修改,导致A更新的内容为脏数据。
ps:具体的产生原因这一篇博客讲的非常通俗易懂。缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹
三、如何处理不一致问题
读了上述的文章后,得出的结论是在并发情况下,先修改数据库,后删除缓存是可行的(发生异常的情况极低),问题是如何保证两步操作是能够正确执行,是问题的关键。
答案也很明显,一次失败我们可以多次执行:重试。
通过补偿的方式对程序增强,那也就引申出了另外一个问题,什么时候补偿呢?
及时补偿,一般的如果失败的请求及时调用大概率还是失败,所以我们选择异步调用补偿。
四、异步补偿
通过MQ的方式,虽然有段时间还是旧数据,但是随着消费成功有用户查询后,结果保存到缓存,这样一来达到更新的效果。
五、生产案例
1.购物车
在购物车业务中,用户添加的商品都会存在于缓存中,基本不会写入数据库(网上也有写入数据库的例子,大多都不实用)。在购物车中,一般只会存储商品的唯一标识,当商品的一些属性发生变化,购物车也会随之发生变化。若反之修改一个商品,更改所有有此商品的购物车显然不合理。我们不能直接在修改商品后发MQ,然后清缓存,清缓存到底清的是谁的呢,所有用户吗?也不合理。
这里我们的解决方案是,另起一个缓存,缓存以下数据时间点、商品标识。当用户查询购物车时,先查询此缓存,查看当前时间和缓存中的时间,若不一致,说明当前购物车的数据已经滞后于最新数据,需要更新购物车,更新缓存,更新之后把购物车的时间标识更改为上述缓存中的时间戳,这样保证下一次后端更改商品数据后以同样的方式更新到用户购物车。
这样的好处是不必在同一时间删除大量购物车数据,减小压力,也能满足实际需求。
2.排行榜
排行榜此类的实现方式略有不同,因为他不需要那么的实时性,一般的半天到一天更新一次即可。日常的检索、点击只会更新到缓存,等到了时间节点就会刷入数据库。
但是在需求中总会有一种特殊的数据,即只有你登录/权限之后才能看到的数据,即权限数据。这类数据也是需要进入排行榜的,但是又不可以和公共的排行数据混为一起。
我们的解决方案是设立多个排行榜数据,每个排行榜设立一个标识,标识出此类排行榜需要什么权限才可以加入最终结果的排行榜,这样一来不同权限的人员拥有不一样的排行数据。