数据库与缓存的数据一致性
数据一致性和事务一致性的区别
数据一致性:在某一时刻同时读取分布式系统中同一份数据的多个副本,如果得到的结果相同,那么就是一致的,对应了CAP定理中的C。
事务一致性:数据库事务在开始前和结束后,数据库的完整性约束没有被破坏,但并不能保证事务的结果是正确的,这应该由应用程序来保证。参考维基百科中关于数据库一致性的定义:
附,维基百科中关于ACID的定义
数据一致性
强一致性
也称“线性一致性”,对数据的修改是同步的,数据修改前后都必须是一致的,比如库存、付款、转账等业务,大多数业务都不要求强一致性。
弱一致性
对数据的修改是异步的,可能存在某一时刻数据不一致的情况。最终一致性属于弱一致性的特殊情况。
最终一致性
对于任何人修改的数据,任何人最终都能读到修改后的数据,但不保证任何人能够立即读到修改后的数据。读写一致性、会话一致性、单调读一致性、因果一致性都属于最终一致性的特殊情况。
读写一致性
对于A修改的数据,A能够立即读到修改后的数据,但不保证B能够立即读到修改后的数据。
会话一致性
对于A修改的数据,只在同一个会话中保证读写一致性。对于A新建的会话,只保证最终一致性。
单调读一致性
任何人一旦读到了数据a的值v,那么接下来他都不会读到比v更早的值。
单调写一致性
对于A先后两次对数据a的修改操作,系统都要保证是顺序进行的。
因果一致性
如果数据a和数据b存在因果关系a->b,那么任何人如果能够读到数据b,则必须能读到数据a。
数据库与缓存的数据一致性
强一致性
将缓存和数据库理解为数据的两个副本,在任意时刻读取缓存或数据库,得到的结果都是一致的。
唯一的办法就是把写操作封装成事务,实现共享锁和排他锁的逻辑。锁的范围包括了在数据库和缓存中对应的数据及其衍生数据。只有获取了排他锁的线程才能对数据进行写操作,在排他锁释放前,其他线程无法读写。此时其他线程要么返回失败,要么进入等待队列。这种方式对并发性能影响很大。
若要提高并发性能,可以再实现MVCC的逻辑,也就是给数据增加版本号,这样锁的范围就变成了数据+版本。在排他锁释放前,其他线程的读操作可以正常进行,返回旧版本的数据,但写操作仍然阻塞。
最终一致性
实现最终一致性的方案有很多,需要根据业务场景对数据一致性的要求来设计具体方案,举一些常见的方案。总结不一定全面,思路可以参考。
写数据库-删缓存
优点:
- 实现简单
缺点:
- 写完数据库后,在删缓存前,一个进程读缓存,另一个进程读数据,就会出现数据不一致。
- 如果删缓存失败,则无法保证数据最终一致性。
- 删缓存会引入缓存击穿的风险,需要对读并发进行评估,避免删完缓存后,大量读请求打到数据库,造成数据库压力过大。
删缓存-写数据库
优点:
- 实现简单,能够保证删缓存成功(如果失败则不会写数据库),解决了删缓存失败的风险。
- 在没有读并发的情况下,写完数据库后,就能读到最新的数据。
缺点:
- 如果在删缓存和写数据库之间,其他线程往缓存中写入旧数据,则无法保证数据最终一致性。
删缓存-写数据库-再删缓存
优点:
- 实现简单,能够保证第一次删缓存成功(如果失败则不会写数据库),缓解了删缓存失败的风险。
- 一般来说,两次删缓存间隔不会太长,第一次删成功说明缓存是可用的,所以第二次删失败的概率很低,大多数情况下都能确保数据最终一致性,除非在缓存在那一瞬间挂掉或网络不通。
缺点:
- 如果在第一次删缓存和写数据库之间,其他线程往缓存中写入旧数据,并且第二次删缓存失败,则无法保证数据最终一致性。
- 两次删缓存增加了缓存击穿的风险。
删缓存-写数据库-删缓存(-删除失败-异步删缓存直到成功)
优点:
- 能够保证数据的最终一致性。
缺点:
- 引入了异步操作,增加了系统复杂度和维护成本。
定时加载缓存
优点:
- 能够保证数据的最终一致性,不会引入缓存击穿的风险。
缺点:
- 只能配置有限的定时任务,所以只适用于小范围的数据更新,比如常量类。
- 引入了全局定时任务,需要保证定时任务的可靠性,避免定时任务挂了导致缓存无法更新。
- 加载频率直接影响到数据不一致的持续时间,需要权衡数据更新频率和业务对数据不一致的容忍程度。
- 要考虑定时加载的数据量,避免反复加载大量无需更新的数据,给数据库增加不必要的压力。
写数据库-写缓存
优点:
- 实现简单,不会引入缓存击穿的风险
缺点:
- 如果写缓存失败,则无法保证数据最终一致性。
- 在写并发情况下会出现覆盖更新,出现时光倒流问题,还可能导致脏数据。
写数据库-写缓存(-写失败-异步读数据库再写缓存直到成功)
优点:
- 避免了写缓存失败,在没有写并发的情况下,可以保证数据一致性。
缺点:
- 引入了异步操作,增加了系统复杂度和维护成本。
- 在写并发情况下,增加了覆盖更新的几率,无法保证数据最终一致性。
写缓存-写数据库
优点:
- 避免了写缓存失败,在没有写并发的情况下,可以保证数据一致性。
缺点:
- 需要增加缓存的回滚操作,数据库回滚时,缓存也要一并回滚
- 发生回滚时,会出现时光倒流问题,还可能导致脏数据。
- 在写数据库完成前,缓存挂掉,如果之前已经读取了缓存中的新值,那就出现时光倒流问题,可能出现脏数据。
写缓存-写数据库(-写失败-异步写数据库直到成功)
优点:
- 避免了写缓存失败。
- 可以保证数据最终一致性。
缺点:
- 引入了异步操作,增加了系统复杂度和维护成本。
- 需要保证写数据库的顺序一致性。
参考文章
强一致性、顺序一致性、弱一致性和共识
如何理解数据库事务中的一致性的概念
数据库的ACID(原子性、一致性、隔离性与持久性)
通俗易懂 强一致性、弱一致性、最终一致性、读写一致性、单调读、因果一致性 的区别与联系