在执行一次写操作后,需要保证从缓存中读取的数据与持久化到数据库中的数据是一致的,所以需要更新缓存。因为涉及到数据库和缓存的两步操作,所以很难保证更新的原子性。在设计更新策略时,我们需要考虑很多方面:对系统吞吐量的影响:比如更新缓存策略产生的数据库负载小于删除缓存策略并发安全:一些异常的操作顺序可能会导致数据不一致,例如缓存中长期存储过时数据的影响。更新失败:如果操作失败,如何将对业务的影响降到最低故障检测和修复难度:操作失败导致的错误会在日志中留下详细的记录EasyDetectandfix。并发问题导致的数据错误很难无迹可寻,并且在流量高峰期更容易出现并发错误,带来更大的业务风险。更新缓存有两种方式:删除无效缓存:读取时,由于缓存未命中,会从数据库中读取新的数据更新到缓存中。更新缓存:直接将新数据写入缓存,覆盖过期数据。更新数据库有两种顺序:先数据库,再缓存,先缓存,再数据库。更新策略有四种成对出现,下面我们一一分析。并发问题通常是由于线程启动较晚但操作先完成,这种现象我们称之为“超前运行”。下面我们一一分析四种策略中“早跑”造成的错误。先更新数据库,再删除缓存。如果数据库更新成功,但是缓存删除操作失败,之后读取的数据都是缓存中的过期数据,造成不一致。可能并发错误:先更新数据库,再更新缓存,与删除缓存策略相同。如果数据库更新成功,缓存更新失败,会造成数据不一致。可能并发错误:当两个写线程发生冲突时,线程A可以通过比较数据版本来避免写入旧数据。先删除缓存,再更新数据库可能出现的并发错误:先更新缓存,再更新数据库。如果缓存更新成功,数据库更新失败,之后会读取所有未持久化的数据。因为缓存中的数据是易变的,这种状态是非常危险的。这种策略风险更大,因为数据库由于键约束而写入失败的可能性更高。可能并发错误:异步更新双写更新逻辑复杂,一致性问题较多。现在我们可以通过订阅数据库更新来更新缓存。阿里巴巴开源了mysql数据库binlog的增量订阅消费组件-canal。我们可以采用API服务器只写数据库,另一个线程增量订阅数据库binlog进行缓存更新的策略。关注Java知音公众号,回复“面试题聚合”,送你面试题集锦。这种策略有类似先更新数据库再删除缓存的并发问题:这个问题也可以使用异步线程来更新缓存,写解决方案就是在缓存的时候比较数据版本。
