一、问题描述
正常的缓存步骤是:
1、查询缓存数据是否存在
2、不存在即查询数据库
3、将数据添加到缓存同时返回结果,
4、下一次访问发现缓存存在即直接返回缓存数据。
那么当更新数据库数据的时候,该如果更新缓存呢,至少要考虑尽量短时间的一致性,这个看业务需求,比如用户信息缓存时间越短越好,比如排行榜可能是一天更新一次,本文纯技术讨论,就是尽量缩短非一致性的时间以此来学习思路。
二、当更新数据库时候,缓存应该如何更新
1.1、更新缓存VS淘汰缓存
更新缓存很直接,但是涉及到本次更新的数据结果需要一堆数据运算,复杂度就增加了。而淘汰缓存仅仅会增加一次cache
miss,代价可以忽略,所以建议淘汰缓存
1.2、先淘汰后写数据库vs先写数据库后淘汰
先写后淘汰,如果淘汰失败,cache里一直是脏数据
先淘汰后写,下次请求的时候缓存就会miss
hit一次,这个代价是可以忽略的,(如果淘汰失败return false)
综合比较统,推荐先淘汰缓存再写数据库,下次请求直接从数据库取然后再写在缓存里。(当然这里可能会大并发一起击穿(通过下面1.3的方式可以解决),还有在淘汰缓存再写数据库的这一瞬间,再来一个读取请求,这个读取比上一个请求的写先完成,那么就会出现脏数据。网上有人说
修改数据库的连接池方法,就是对于同一个ID的数据请求,比如query(id),edit(id)都使用同一个连接对象,这样来保证先来的先完成,貌似还是挺复杂的,关于后来的读取请求先与先来的写请求完成,只能通过这样的串行方式执行
关于脏数据,如果需要强一致性
1、可以通过数据库无论是读或写操作都是通过一个请求db
connection连接完成(目的是串行),这样就需要修改连接池
2、可以采用更新缓存而不是淘汰缓存,前提是更新的代价比较低
3、可以先更新数据库再淘汰缓存,不过一般情况,淘汰缓存失败的可能性很小,可以以缓存处理100%不失败为前期。
4、双淘汰发,即:淘汰缓存-更新数据库-淘汰缓存,可以尽量减少脏数据的留存时间
5、以上实现起来,要么极短时间的不一致要么一致性代价比较高,实际项目我会这样处理,更新数据库的地方和读取的地方上同样key的分布式锁,这样就能保证,先操作(或读或写)数据的先获得结果,实际中这样的强一致需求比较少,参考思路即可。
当然数据既然都缓存起来了,绝大部分都不要求强一致性,为了尽可能的缩短一致性的时间,可以如下处理:
6、异步消息总线esb更新法,即:修改数据库往消息总线里发送一个消息,在接收端去处理这个消息更新缓存,缺点是有代码入侵
7、异步binlog扫描更新法,增量的去扫描binlog中的修改记录,符合条件的更新缓存,相比消息总线法没有代码入侵