阅读本文前,请先阅读 聊聊「分布式系统」这篇文章。
数据一致性,既然谈到「一致」了,必然是 >= 2 个客体之间进行比较,比如缓存和 MySQL 数据库的数据一致性,比如 ZooKeeper 中各个节点数据的一致性,比如不同客户端读取同一份数据的一致性等等。
举个例子,你在 12306 上面买票,在春运抢票高峰期,同一个座位可能多个乘客都看到了并点击“购票”了,但是只能有一个人购票成功,如果同一个座位 >= 2个人购票成功,这就出问题了。12306 就必须要保证余票数据的一致性。
再比如,你从建行往工行转账,必须确保你建行账户上的钱减少了 N 元,工行账户上得多出来 N 元,不能建行这边账户余额减少了,工行那边账户余额没变化,这就出大问题了。但我们知道,建行和工行肯定是位于不同物理位置不同服务器上的两个系统,你无法用单机 ACID 事务保证数据的一致性,那应该怎么做呢?这里讨论的就是不同系统之间数据一致性的问题。
再举个例子,数据库主从复制,怎么保证主库和从库之间数据的一致性?
在我的这篇文章 聊聊「ZooKeeper」中我举了个“多个数据节点保证数据一致性”的例子,从中可以看到,在分布式系统中,如何既保证数据的一致性、又不影响系统性能,是每个分布式系统都要重点考虑和权衡的。鉴于此,我们来讨论一下一致性的级别。
(一)强一致性
这种一致性最符合用户直觉,在用户看来,我写入什么,然后读出来就是什么。不管你系统内部是如何实现的,有多少主库从库的,从哪个从库主库读出来的数据。反正在用户看来就是“我写入什么,然后读出来就是什么”。
(二)弱一致性
弱一致性,顾名思义,就是能够容忍「一定程度」的数据不一致。但这个「一定程度」怎么界定呢?这个是跟你的具体业务有关的。弱一致性大体还可以分为:
- 会话一致性:保证对于写入的值,在同一次客户端会话中,可以读到一致的值。
- 用户一致性:保证对于写入的值,在同一个用户中,可以读到一致的值。
(三)最终一致性
顾名思义,可以容忍短时间数据不一致的情况,但是最终数据还是要保证一致性。具体实现要看业务情况,看能承受哪种程度的最终一致性。
再谈数据库事务
ACID 即 A(原子性)、C(一致性)、I(隔离性)、D(持久性)
事务的隔离级别
读未提交:即一个事务能够读取另一个事务中还没有提交的数据。(这种事务隔离级别几乎没人用,存在脏读问题,隔离级别最低)
读已提交:即一个事务可以读取到另一个事务中已经提交的数据。(存在不可重复度的问题。=》其实我觉得这不能算“问题”,要看具体业务场景,你的业务场景是否要求在事务 A 里面多次读取事务 B 里的数据,每次都要读取最新的)
可重复读:在事务 A 里,多次读取事务 B 里的数据,每次读到的值,都和事务开始时读到的值是一致的)
串行化:即所有事务串行执行。(效率最低)
以上是单机数据库中事务的隔离级别。单机数据库很容易实现一套满足 ACID 的事务系统,但是在分布式系统中,要实现满足 ACID 特性的事务是非常复杂的。但现实中确实又存在这样的要求(比如跨行转账)。
CAP 理论(见我的文章 什么是「CAP」? )告诉我们:
分布式系统中,在「可用性」和「一致性」之间永远无法存在一个两全其美的方案。
BASE 理论(基本可用)
BASE 理论是对 CAP 中「可用性」和「一致性」进行权衡的结果。其核心思想是即使无法做到强一致性,也要尽量做到最终一致性。
什么是基本可用?
要看具体业务具体场景。但要注意,「基本可用」不等于「不可用」,「基本可用」也不代表「不稳定」。基本可用,是指在分布式系统中出现「不可预知故障」的时候,允许损失一部分可用性。=》其实我觉得 BASE 的更准确描述应该是「基础核心功能可用」。比如:
- 响应时间上的损失:在分布式系统出现故障的情况下,允许响应时间增加。
- 功能上的损失:在分布式系统出现故障的情况下,允许部分系统降级,但核心功能可用(比如电商系统允许下单支付,但物流系统暂时不可用)。
「最终一致性」的几种变种
- 读自己所写(Read your writes)
即“客户端A”更新的数据,“客户端A”自己总是能读到最新数据,不会读到旧值。但对于其他“客户端”来说不一定能获取到最新的数据。
- 会话一致性(Sessions consistency)
在“客户端”和服务端的一次会话中,能实现「读自己所写」的一致性。
- 单调读一致性
指如果“客户端”从服务端读取一个数据项的某值之后,后续不能再从服务端读取到更旧的值。即服务端会保证每个“客户端”的写操作是顺序执行的。