分布式一致性是一个常见的问题,比如在本地表中插入了一条记录,同时又远程调用了一个服务,很有可能插入记录成功了,但远程调用超时了,亦或本地记录插入成功了,此时刚好机器重启了......我们遇到的实际问题远非这个例子,根据自己的经验,在业务初期,很少关注这种一致性问题,有两方面的问题:一是这种发生的概率较小,就没有花时间去解决;二是业务在快速跑,容忍了这种异常场景存在。在本篇文章中会全方面位分析分布式一致性的问题、业内标准解决方案、实际工作解决方法
01
分布式一致性问题
1、计算机发展阶段
分布式其实是很早就被提出来了,计算机在发展的过程中是经历了一些阶段,大致画了下发展阶段。从下面这副图可以看出,计算机从开始到一定的时期是不断变强大的(更多的指单机性能),到了后面就进入到分布式系统。自然有一个问题就出来:为什么不发展单机?分布式系统产生的动力是什么?
2、单机发展受限
单机发展受限是由于两方面的原因:一是摩尔定律失效;二是价格原因。
摩尔定律其实在 10 年前就已失效了,Intel 公司转变发展多核而非提升单机的性能。记得在 2012 年时在一家企业,他们买的刀片机就要花 36w,这个价格就很昂贵了,后面又有幸接触到天河计算机,它的性能是非常之高的,一些企业在上面付费跑大型计算数据。
3、分布式发展
分布式产生的原因有两点:一是系统可靠性;二是数据量。
提升系统可靠性一个最直接的做法就是冗余备份,一个节点变成多个节点,这样就组成了一个分布式系统。另一个是数据量因素,单机的存储、计算能力是有效的,分布式一个典型的思路是分而治之,虽然单机机器能力有限,但多个单机加起来的能力就不容小看了。
4、分布式特性
分布式的定义是分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。从本质上讲,分布式可以有一个公式来说明:分布式 = 拆分 + 连接。
由单机应用迁移到分布式上,会面临很多问题。如缺乏全局时钟、网络不可靠性、数据不一致性......在这些挑战中,数据不一致性是一个常见但又比较难解决的问题。
分布式系统具有两个特性:CAP 和 BASE。CAP 是三个单词的缩写,从下面的图中可以看出,CAP 中至多只能满足两个。
可以论证为什么只能最多满足两个特性:为了提高系统可靠性,采用冗余备份思想,但同一份数据在多个节点上都存在,就存在数据同步的问题,
有两种方案同步:
一是等所有的节点都同步完了就返回,这牺牲了可用性;
二是部分节点同步完就返回,这产生了数据不一致性。
在技术上我们习惯离散的视角看问题:是 or 不是,后面发现可以从连续区间的视角看问题,可用的概念是什么?100% 可用、50% 可用......所以就引出了 BASE 理伦。
BASE 主要包含几点:
在分布式环境下,聊的一致性是最终一致性。一致性分类
02
一致性分类
对一致性划分四大类:严格一致性、强一致性、因果一致性、最终一致性。
在具体讲这些分类之前,有一个著名的公式是:W + R > N,其中 W 表示接受请求要写的副本数量(注意并不是全部,比如写超过一半就成功返回),R 是读到最新的数据要读的副本数量,N 是副本总数。
严格一致性:在分布式环境是不存在的,仅仅只是一个概念而已。
强一致性:如果满足 W + R > N 就是强一致性,以我们熟知的 Zookeeper 举例,你可以说它是强一致性,这个和很多人交流时这里有疑问,大家在之前的印象中强一致性就是访问任何一个节点都能读到最新的数据,而这个强一致性是根据上面的公式定义的。强一致性又分为两类:
因果一致性:就是存在因果关系,最典型的就是消息通知,只有接收到消息后才能知道最新数据变化。
最终一致性:这个是我们最常见的,它又分为四类:
03
一致性问题分类及业内解决方案
1、一致性问题分类
其实我们说的一致性问题可以分类两大类型:事务一致性和共识性,其中事务一致性我们经常遇到,如更新 DB 的同时又有远程调用,微服务中不同服务的调用等等,这些是我们要解决的实际的问题。而共识性是有框架的,如 raft 等,这些我们只是利用它的特性完成我们的目标,所以这篇文章中只讨论事务一致性,因为它是最常用的。
2、 事务一致性业内解决方案
总的来讲,业内解法有:2pc、3pc、TCC、Saga、Fescar(txc)、MQ。下面分别介绍这些解法的原理,通过这个介绍可以知道每个解法的优缺点是什么,为我们技术选型提供一定的参考。
2.1、2pc
2pc 大家应该听过,它就是将事务分成两个阶段:prepare 和 commit阶段。
马上你会发现这种模式是存在问题:
所以 2pc 只是一种思想,面临了很多被挑战的地方。
2.2、3pc
2pc 在众多的缺点中,有一个缺点是阻塞时间太长,3pc 就是为了解决了阻塞问题和协调者挂了怎么办的问题。3pc 在 2pc 的基础上改进了一点,将 prepare 阶段再细分了:询问和预提交,询问是不锁定资源的,如果询问成功再进行锁定资源。
说明:3pc 只是改进了 2pc,并不是说它就没有问题,依然还是有问题的。在 commit 阶段仍然会造成数据不一致性存在。
3.3、TCC
蚂蚁实现了 TCC 协议,它有一个开源的框架,它是将一个操作分成三个操作里:try、confirm、cancle。
上面这张图可以描述 TCC 的执行流程,一句话介绍 TCC 的精华:只要 try 成功了,confirm 认为就是成功的。
你会发现它有一个明显的缺点:对业务入侵太大。
3.4、Saga
Saga 只是一种思想,它的原理是:将一个长事务划分成若干个小事务,不管是通过流程引擎还是其它的方式,能把这些小事务串起来,从开始向前进行操作,如果失败了就进行逆向操作。
这种思想与后面将的定时任务+状态变迁很相似。
3.5、Fescar(txc)
前面提到 TCC 对业务入侵很大,阿里内部就想零入侵的分布式事务框架,在阿里内部叫 txc,它的原理是在中间件层做分布式事务,通过下面两张图可以描述 txc 的原理。
它有三个角色:
txc 是在数据库层做的,插入了一条 undo sql, TC(事务协调器)会根据执行的状态进行 commit or rollback。
3.6、MQ
MQ 也是经常被使用到的,它有一个特点是异步。它的特点是:发送方尽最大努力发送,消费方尽最大努力消费。
3.7、小结
上面讲了这么方案,有必要总结下:
04
事务一致性经验解决方法总结
上面的业内解决方案是教科级的标准方案,你会发现使用上面的方案对你现有的技术、业务还是有些影响,或大或小,总之你可能还不好掌控它,那又没有一些经验上的解法呢,答案是有的,运用的思路也是借鉴了上面的思路。
总的来讲有:数据库事务、可查询、可对账、可补偿、定时任务+状态变迁、消息队列。下面分别介绍。
4.1、数据库事务
这个是最简单的一种方案,如果能把业务放在一个本地事务中做就最简单不过了,比如常见的更新库存和插入订单,数据库天然就支持事务,一个失败整个事务就回滚了。
这个方案的优点就是简单、一致性问题由数据库帮我们保证了。同时它的缺点就是一般的数据库只支持单机事务,并不支持分布式事务,而且业务上有时就分在两个不同的库上,这样就没有办法了。在业务比较小的时候或者初期阶段一般选用这个方案。
4.2、接口查询
可查询的意思:我更新了数据,一定有一个对应的接口可以查数据的情况。举个例子来讲,比别人发奖,在我们本地有一个发奖记录,同时要调用远程公共服务进行发奖,那么这就是一个分布式事务。进一步抽象成"DB+HTTP"的模式,那么这个怎么解决呢。
网络中存在超时的情况,不知道对方有没有真的操作成功,如果业务方不重试交由客户端处理,客户端会重试再发一个请求过来,此时先在本地查数据,发现有记录,但状态是"待发",接下来就调用查询接口,判断用户是不是真正地已发奖了,如果发了奖,就更新本地状态。
4.3、对账
对账是换了一种思路来解决分布式一致性的问题,对账是不关注过程,只关注结果!之前我们的思路都是在过程上保证如何达到一致性。
对账又分为增量模式和全量模式,这个在银行中用得比较多。对账一般是 T+1 进行的,如果要实时性要求更高,可以按小时级别进行,对账的数据量一般比较大,一般要以一方为准进行校对,俗称单向对账,在阿里还有双向对账的模式。
4.4、可补偿
可补偿是在接口查询和对账的模式之后的一种操作,换言之,如果存在数据不一致性,允许中间有一段时间存在不一致,一旦发现了就进行补偿。不管是对账还是接口查询之后的结果,都可以使用这种方式,可补偿就已经默许了一致性问题的存在的,但它又是最终的处理方案。
4.5、定时任务+状态变迁
它的原理是:将一个大的操作切分成多个小步骤,每个操作都对应一个状态,先数据入库,然后定时任务扫描这张表,不断的按状态变迁前进。
个人认为这个是非常重要的一种解决思路,体现了切分、重试、补偿的思路,与上面 Saga 的思想相似,在实际的工作中这个还是用得比较多的。
4.6 消息队列
消息队列也常被用于一致性处理方案,但有一个问题大家要注意:在发送方,如何保证消息一定能发得出来?
其实这个问题的场景就是这样的,前面的操作都做完了,下面正要发消息,结果机器重启了,是不是消息没有发送出来呢。这个问题还是有解法的,可以利用数据库事务+定时任务+状态变迁来解决,步骤如下:
这里有一点要注意:接受方要保证已接收的消息,因为很有可能消息是重复发送的!
这是业务侧来实现的,有些开源的MQ是支持事务的,比如RocketMQ,我们不用再写额外的保证逻辑。
4.7 小结
上面的解法方案,你会发现一个特点,对我们的接口是有要求的,总结几点: