数据库和缓存一致性一般分为两类,最终一致性和强一致性。
最终一致性
名词解释:db更新后经过一段时间后要求能访问到更新后的数据,是最终一致性。
适用场景:对于实时性要求不是很高的业务。
方案一:
读取:先从缓存读取,读取不到则读取db,然后写入缓存。
写入/更新/删除:先写db,再写入/更新/删除缓存。
可能遇到的问题:T0时刻请求A读取的时候缓存为空,从db读取数据,T1时刻请求B更新了DB和缓存,T2时刻更新了缓存,T3时刻请求A将老数据写入缓存,此时缓存数据为脏数据。
方案二
读取:先从缓存读取,读取不到则读取db,然后写入缓存。
写入/更新/删除:先写入/更新/删除缓存,再更新db。
可能遇到的问题:T0时刻请求A读取的时候缓存为空,从db读取数据,T1时刻请求B删除了缓存,T2时刻请求A将老数据写入缓存,T3时刻请求B更新了缓存,此时缓存数据为脏数据。
对于方案一二,既然先更新缓存和数据库都不行,那能否给更新db和缓存的整体操作中加上锁呢,的确可以,但是这样读取也需要加锁,会影响性能,除非是一致性要求很高的场景,不然不建议这样使用。
上面两种情况都是读取和更新都操作了缓存,那么将缓存只放到更新操作里可不可以。
方案三:
读取:从缓存读取,读取不到则返回空。
写入/更新/删除:更新db再更新缓存。
可能遇到的问题:依然会有不同请求造成的并发问题,除非把更新db和缓存的整个操作加锁,不过会有性能问题,尤其是redis的key和value需要经过复杂计算的场景,而且若更新db成功,更新缓存失败,会有脏数据。由于缓存读取不到不再读取db,防止缓存过期,需要有全量job定时刷db到缓存。
方案四:
读取:从缓存读取,读取不到则返回空。
写入/更新/删除:先更新db,异步消息更新缓存,消费者加锁消费消息,缓存数据存储时间戳,若缓存时间戳大于db时间戳则需要进行更新,若缓存为空则直接写入。另用全量job定期刷新缓存。
可能遇到的问题:
1.若先更新再删除,消费者先消费到了删除请求,再消费到更新请求,则缓存为脏数据。
2.时间戳精度不够表示版本号。
3.更新db成功,发送消息失败。
解决方案:
1.加上isDel字段,删除操作视为更新操作,只是将isDel字段置为true。待缓存自动过期删除。
2.需要根据业务场景选取字段,或者在db加上版本号。
3.发送消息失败记录消息到本地消息表,定时任务轮询重发。
强一致性:
名词解释:对于关系型数据库,要求更新过的数据能被后续的访问都能看到,是强一致性。
适用场景:对于实时性要求比较高的业务。
方案一:
读取:先从缓存读取,读取不到则读取db,然后写入缓存。
写入/更新/删除:先写入/更新/db,再删除缓存。
可能遇到的问题:
1.如图,查询操作在db更新后查询的还是老数据。
2.和最终一致性的方案一一样,查询操作查询数据时缓存不存在,从db写入老缓存缓存,导致缓存存在脏数据。
方案二:
新增/修改/删除: 将更新db和删除缓存整体操作加锁lock1。
查询:查询时先判断锁lock1是否存在,若锁存在则读取db并返回数据,锁不存在直接查询缓存,若缓存存在则直接返回结果,缓存不存在同样加锁lock1,执行查询db和更新缓存操作,这样可以保证强一致性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。