架构师的十八般武艺:一致性
一致性是指:保持所有节点在同一个时刻具有相同的、逻辑一致的数据。在很多应用场景下,都需要数据保持一致性,比如:资金转账场景需要转出方和转入方保持一致,否则就是资损;电商场景下,用户撤销订单或订单发货后,需要在 app 中看到一样的结果;在支付场景下,如果存在优惠券的场景,需要支付和退款的时候余额和优惠券同事扣减或返还。
但是,在分布式场景下,由于 CAP 理论的存在,可用性,一致性和分区不能被同时满足,至少需要对其中的一角做降级。所以,在不同的场景下,需要有不同的手段保证一致性。
降级分区容忍性的场景
大家一定会非常奇怪,互联网不就是打着高并发,如果没有分区怎么做高并发呢?其实我们要分场景去看一些事情,比如电商也存在用户端功能、商户端功能和后台功能的区分。比如用户额度规则调整、线下审核流程等,都是由运营下二进行的操作,对于操作的并发行要求并没有这么高,TPS 基本也就在百的数量级。所以,对于这类场景,一般追求 AC,事务都落在一个 DB 中,DB 通过主备做高可用,主备同步后,才真正生效。
对于非对客场景,一般可牺牲分区容忍性,从而满足一致性。
降级可用性的场景
对于非高频的操作,比如一些状态型数据的变更,或者对于一些由于国家基础设施的问题,本身就是满操作的场景比如调用某些的国家系统完成身份认证。对于可用性的要求并不这么高,如果不可用,完全可以等待系统恢复之后再进行。这类操作,在互联网场景下,基本都是通过分区保证读的可用性,通过集群同步(共识)的方式保证写操作的一致性,在集群发生大规模故障的情况下,先不可用等修复。这类的功能,在用户体验设计上就先需要做到异步化,先受理后处理。
对于可异步化设计的功能,一般可以在写入操作上牺牲可用性,从而保证数据的一致性。
最终一致性
BASE:Basically Available, Soft Sate, Eventual consistency。
通过最终一致性(牺牲强一致性),从而基本保证可用性和分区容忍性。
这类场景,基本是对客交易的场景,比如下单、支付、转账等等。
首先,我们一般按照用户进行分区,所以一个用户的数据,基本在一个分区上,基本上可以保证事务的一致性。
对于不同用户的一致性,比如下单时候商户和用户的一致性,支付的时候订单和渠道的一致性,转账时候不同用户账户的一致性,都可以采用最终一致性的方式。
实现最终一致性的方式有很多:
消息驱动:通过事务中触发消息的方式,保证 A 和 B 两个节点上数据的一致性。这种,一般在 B 操作为附属操作的场景下,如支付成功发送一个消息这种。这个设计需要保证消息的可靠发送,这个一般消息中间件都有 ack 机制,同时,在 B 处理的时候,需要回差 A 事务的状态,只有成功才处理消息;A 事务回滚丢弃消息;A 事务状态为止消息重试。
TCC:Try-Confim-Cancel:通过两阶段的框架,保证第一阶段 Try 的时候先 check 资源,第二阶段 Confirm 的时候保证能执行成功。否则就人工干预。这种,一般在两者的数据的同步性要求比较高,同时用户可见的情况下,比如用户扣了余额就需要将订单推进支付成功。
通过状态控制:在订单上,维持一个状态机,通过不同状体触发不同流程的场景,保证最终一致性。例如转账场景,在转账单上维护受理、转出成功、转入成功三个状态。在受理-转出成功的时候触发付款方的余额扣减,在转出成功-转入成功的时候触发收款方的余额增加。同时需要能保证两次操作的幂等性。
任务触发:先受理后处理的场景。受理后触发多个任务,通过任务调度的方式保证任务最终都执行陈工,从而保证最终一致性。这个方式适用于多类数据之间没有用户体验关联的场景,比如收到渠道来账信息后,内部核算和客户账充值的一致性保障。
版权声明: 本文为 InfoQ 作者【agnostic】的原创文章。
原文链接:【http://xie.infoq.cn/article/904cfcc2a8f59a6d92147db69】。文章转载请联系作者。
评论