CAP理论
在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
Consistency 一致性: 对某个指定的客户端来说,读操作保证能够返回最新的写操作结果
A read is guaranteed to return the most recent write for a given client.
Availability 可用性: 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
A non-failing node will return a reasonable response within a reasonable amount of time (no error or timeout).
Partition Tolerance 分区容忍性: 当出现网络分区后,系统能够继续“履行职责”。
The system will continue to function when network partitions occur.
一致性、可用性、分区容忍性的选择: 虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。
如果我们选择了 CA(一致性 + 可用性) 而放弃了 P(分区容忍性),那么当发生分区现象时,为了保证 C(一致性),系统需要禁止写入。
当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A(可用性) 冲突了,因为 A(可用性)要求返回 no error 和 no timeout。
因此,分布式系统理论上不可能选择 CA (一致性 + 可用性)架构,只能选择 CP(一致性 + 分区容忍性) 或者 AP (可用性 + 分区容忍性)架构,在一致性和可用性做折中选择。
CP模型
因为 Node1 节点和 Node2 节点连接中断导致分区现象,Node1 节点的数据已经更新到 A,但是 Node1 和 Node2 之间的复制通道中断,数据 B 无法同步到 Node2,Node2 节点上的数据还是旧数据 B。
这时客户端 C 访问 Node2 时,Node2 需要返回 error,提示客户端 “系统现在发生了错误”,这种处理方式违背了可用性(Availability)的要求,因此 CAP 三者只能满足 CP。
AP模型
同样是 Node2 节点上的数据还是旧数据 B,这时客户端 C 访问 Node2 时,Node2 将当前自己拥有的数据 B 返回给客户端了。
而实际上当前最新的数据已经是 A 了,这就不满足一致性(Consistency)的要求了,因此 CAP 三者只能满足 AP。
BASE理论
它的核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
- Basically Available 基本可用:分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。这里的关键词是“部分”和“核心”,实际实践上,哪些是核心需要根据具体业务来权衡
- Soft State 软状态:允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是 CAP 理论中的数据不一致。
- Eventual Consistency 最终一致性:系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。“一定时间”和数据的特性是强关联的,不同业务数据能够容忍的不一致时间是不同的。而“最终”的含义就是不管多长时间,最终还是要达到一致性的状态。
常见一致性模型
- 强一致性:要求无论更新操作是在哪个数据副本上执行,之后所有的读操作都要能获得最新的数据。
- 弱一致性:在这种一致性下,用户读到某一操作对系统特定数据的更新需要一段时间,我们将这段时间称为"不一致性窗口"。
- 最终一致性:是弱一致性的一种特例,在这种一致性下系统保证用户最终能够读取到某操作对系统特定数据的更新(读取操作之前没有该数据的其他更新操作)。
常见分布式事务模型
两阶段提交
二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
核心思想就是对每一个事务都采用先尝试后提交的处理方式,处理后所有的读操作都要能获得最新的数据,因此也可以将二阶段提交看作是一个强一致性算法。
优点:
- 实现简单
缺点:
- 所有参与者同步阻塞,性能低下
- 协调者存在单点故障问题
- 局部收到提交消息,造成数据不一致
三阶段提交
这里与两阶段提交协议有两个主要的不同:
- 增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生
- 在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大
三阶段提交协议与两阶段提交协议相比,具有如上的优点,但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞和永远锁定资源。
TCC事务
TCC 是服务化的二阶段编程模型,其 Try、Confirm、Cancel 3 个方法均由业务编码实现:
- Try 操作作为一阶段,负责资源的检查和预留。
- Confirm 操作作为二阶段提交操作,执行真正的业务。
- Cancel 是预留资源的取消。
优点:
- 性能提升:具体业务来实现控制资源锁的粒度大小
- 数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
- 可靠性:由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。
缺点:
- TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
本地消息表
优点:
- 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
- 方案轻量,容易实现。
缺点:
- 与具体的业务场景绑定,耦合性强,不可公用。
- 消息数据与业务数据同库,占用业务系统资源。
- 业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。
事务消息
优点:
- 降低业务系统与消息系统的耦合
缺点:
- 一次消息需要两次网络请求(half+commit/rollback)
- 业务系统需要实现回查事务状态接口
- 主流MQ不支持事务消息
Saga事务
Saga 事务核心思想是将长事务拆分为多个本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
Saga 事务基本协议如下:
- 每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。
- 每个 Ti 都有对应的幂等补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果。
命令协调模式
优点:
- 服务之间关系简单,避免服务之间的循环依赖关系,因为 Saga 协调器会调用 Saga 参与者,但参与者不会调用协调器。
- 程序开发简单,只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性。
- 易维护扩展,在添加新步骤时,事务复杂性保持线性,回滚更容易管理,更容易实施和测试。
缺点:
- 中央协调器容易处理逻辑容易过于复杂,导致难以维护。
- 存在协调器单点故障风险。
事件编排模式
优点:
- 避免中央协调器单点故障风险。
- 当涉及的步骤较少服务开发简单,容易实现。
缺点:
- 服务之间存在循环依赖的风险。
- 当涉及的步骤较多,服务间关系混乱,难以追踪调测。
各方案使用场景及对比
- 2PC/3PC:依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。
- TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
- 本地消息表/事务消息:都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。
- Saga 事务:由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 并且Saga不适用于发送操作,比如发短信、发券,这种操作的补偿操作不一定能补偿成功。
| 方案 | 数据一致性 | 容错性 | 复杂性 | 性能 | 维护成本 |
|---|---|---|---|---|---|
| 2PC | 强 | 低 | 中 | 低 | 低 |
| 3PC | 强 | 低 | 高 | 低 | 中 |
| TCC | 弱 | 高 | 高 | 中 | 高 |
| 本地消息表 | 弱 | 高 | 低 | 中 | 中 |
| 事务消息 | 弱 | 高 | 低 | 高 | 中 |
| Saga事务 | 弱 | 高 | 中 | 中 | 高 |