一致性:分布式环境下,多个节点的数据是否强一致。可用性:分布式服务能一直保证可用状态。当用户发出一个请求后,服务能在有限时间内返回结果。分区容忍性:特指对网络分区的容忍性。
举例:Cassandra、Dynamo 等,默认优先选择 AP,弱化 C;HBase、MongoDB 等,默认优先选择 CP,弱化 A。
BASE 理论
核心思想:
基本可用(BasicallyAvailable):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用;软状态(Soft state):指允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性;最终一致性(Eventual consistency):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态;原子性(A)与持久性(D)必须根本保障;为了可用性、性能与降级服务的需要,只有降低一致性( C ) 与 隔离性( I ) 的要求;酸碱平衡(ACID-BASE Balance);
BASE 是对 CAP 中 AP 的一个扩展
一致性模型
数据的一致性模型可以分成以下三类:
强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。
分布式系统数据的强一致性、弱一致性和最终一致性可以通过 Quorum NRW 算法分析。
本地事务
在单个数据库的本地并且限制在单个进程内的事务本地事务不涉及多个数据来源分布式事务典型方案
两阶段提交(2PC, Two Phase Commit)方案;本地消息表 (eBay 事件队列方案);TCC 补偿模式;分类:
两阶段型补偿型异步确保型最大努力通知型
服务模式:
可查询操作幂等操作TCC操作可补偿操作
两阶段提交2PC(强一致性)
基于XA协议的两阶段提交:
第一阶段是表决阶段,所有参与者都将本事务能否成功的信息反馈发给协调者;第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交或者回滚;
缺点:
单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能。比如:在第二阶段中,假设协调者发出了事务 Commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了 Commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
总的来说,XA 协议比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。
本地消息表(最终一致性)
eBay 的架构师 Dan Pritchett,曾在一篇解释 BASE 原理的论文《Base:An Acid Alternative》中提到一个 eBay 分布式系统一致性问题的解决方案。
它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中;之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发;消息消费方处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作;
优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
这个方案的核心在于第二阶段的重试和幂等执行。失败后重试,这是一种补偿机制,它是能保证系统最终一致的关键流程。
可靠消息的最终一致性代码示例
表结构
DROPTABLEIFEXISTSrp_transaction_message
;
CREATETABLErp_transaction_message
(
id
VARCHAR(50)NOTNULLDEFAULT‘’COMMENT‘主键ID’,
version
INT(11)NOTNULLDEFAULT‘0’COMMENT‘版本号’,
editor
VARCHAR(100)DEFAULTNULLCOMMENT‘修改者’,
creater
VARCHAR(100)DEFAULTNULLCOMMENT‘创建者’,
edit_time
datetimeDEFAULTNULLCOMMENT‘最后修改时间’,
create_time
datetimeNOTNULLDEFAULT‘0000-00-0000:00:00’COMMENT‘创建时间’,
message_id
VARCHAR(50)NOTNULLDEFAULT‘’COMMENT‘消息ID’,
message_body
LONGTEXTNOTNULLCOMMENT‘消息内容’,
message_data_type
VARCHAR(50)DEFAULTNULLCOMMENT‘消息数据类型’,
consumer_queue
VARCHAR(100)NOTNULLDEFAULT‘’COMMENT‘消费队列’,
message_send_times
SMAL