业务背景:web
商户提交表单数据至旺铺(deco项目,如下皆称为deco),deco须要接入poi系统进行装修内容的人工审核,详细流程见下图。sql
店铺装修审核状态在deco系统和poi系统之间不一致,下图中1,2,3步提交流程会出现同一次提交审核流在deco系统中的装修状态为未装修,而在poi系统为审核中。一样在4,5,6步骤的审核回调过程也会有同类的状态不一致问题。两块问题都是同一技术问题,本文只以1,2,3步提交过程为例进行分析解决。数据库
从业务中抽离出来,问题的核心缘由可用下图流程模型来描述。服务器
系统A的某个业务功能包含两步操做,第一步把数据写入A系统本地库,第二步远程调用系统B,这两步操做在A系统用一个数据库事务来包装。伪代码以下:网络
1 $db->begain(); 2 // 第一步操做本地数据库 3 $res = $db->update($sql_a); 4 if (!$res) { 5 $db->rollback(); 6 return; 7 } 8 // 第二步远程调用B系统 9 $res = $http_request->get($url_b); 10 if ('success' != $res) { 11 $db->rollback(); 12 return; 13 } 14 $db->commit();
第一步有两种结果成功、失败,第二步则有3种结果成功、失败、超时,其中致使超时缘由多是网络中断,也多是B系统服务异常。那么咱们根据两步骤的执行结果状况来分别分析一下是否会致使A、B系统之间的数据不一致。异步
可得当第一步执行成功,第二步远程调用超时时,A系统事务会回滚,那么就发生A、B系统数据不一致的状况,A库中写入失败,可是B库中却成功写入。咱们习惯于使用关系型数据库事务的ACID特性来达到一致性的目的,可是当事务中发生跨系统的调用时ACID就无效了,只从数据库层面来看,跨系统就意味着同一个业务存在多个数据副本,对应着不一样的数据库实例,并且分布在不一样的机器上,而关系型数据库的事务只是针对同一个库的同一个connection而言的。分布式
咱们先从新认识一下什么是一致性?首先想到的是关系型数据库事务,又会想到最经典的甲给乙转钱的例子,事务的四大基本特性ACID保证了甲帐户扣钱和乙帐户入钱同时发生或同时不发生,其中的C特性就是指一致性,它是指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。在web2.0以前大部分网站或者项目都是单系统,对应底层存储也只是单数据库,因此使用数据库自己的事务特性就知足了一致性。 微服务
可是随着互联网用户和数据量的指数级增加,对于每一个系统的计算能力、吞吐量以及响应速度的要求都大大提升,因而单节点服务器确定知足不了要求,因此都在考虑拆分和系统扩展性,不管是通过水平拆分仍是垂直拆分,单机系统就变成了分布式系统,分布式系统就确定存在某节点或者网络故障的状况,以前说的事务的ACID中的强一致性就没法保证。 网站
再回到咱们的问题上来,poi系统其实就能够理解为垂直拆分出的一个微服务,deco调用poi的提交审核接口就是分布式系统之间的调用,可是deco中仍然用关系型数据库的事务来达到强一致性的目的,根据CAP理论,分布式系统的一致性、可用性、分区容错性不可能同时获得知足,只能知足其中两个,而分布式系统的分区容错性都须要获得知足,因此就须要在一致性和可用性之间进行取舍。Deco的这种实现其实就是为了保证一致性,而牺牲了可用性。url
而对于如今的系统而言,可用性是相当重要的必需要保证,要作到即便poi系统出现偶尔的网络故障或者超时,也尽量不要用户的一次提交失败掉。
再来了解一个概念BASE理论,BASE理论是CAP理论的一种实现,它对分布式系统的一致性和可用性不可兼得的问题提出了一种方案,即基本可用和最终一致是目标。既然提到了强一致性和最终一致性,再介绍一下业界对一致性的分层次定义。
强一致 :当更新操做完成以后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现须要牺牲可用性。
弱一致 :系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功以后,不承诺当即能够读到最新写入的值,也不会具体的承诺多久以后能够读到。
最终一致 :弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操做的值。在没有故障发生的前提下,不一致窗口的时间主要受通讯延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。
对上面几段分析的总结就是:关系型数据库的事务能够知足单系统的强一致性,大部分分布式系统只能把最终一致性做为追求。而咱们的deco和poi系统显然也是应该追求最终一致性,由于对于poi和deco之间装修审核数据出现短期的不一致是彻底能够接受的。
基于上述理论,咱们能够有如下两种方案来达到可用性和最终一致性:
解耦,异步任务处理,由原来同步调用poi系统变为异步调用,将deco系统信息入库和调用poi建立审核任务这两个操做分开。
deco系统收到用户请求以后先信息入库,而后在本地数据库同时新建一个任务(任务初始状态为待执行),当调用poi完成以后该任务改成已经执行,信息入库和建立任务在同一个数据库事务下。
后台定时脚原本执行待执行状态的任务。
若是异步调用poi返回失败,则须要对以前入库的信息进行回退。
若是异步调用poi遇到网络问题或者超时,则考虑重试机制,注意重试机制要避免重复提交,可采起在deco系统重试前确认 或者 在poi系统保证接口的幂等性。
方案2、
引入消息队列,至关于对方案一的升级版。
deco系统为消息生产者,poi系统为消息消费者。
生产者接收到用户请求,业务数据处理入库,而后写入一条任务到消息队列,而且要新建一张消息状态表用于记录消息的执行状态,以上三个操做要在同一个本地事务中进行。其实也能够在业务表增长一个字段用来表示消息执行状态,这样有一个缺点就是生产消息和自己业务处理发生耦合,可是能够接受,由于既然放在一个事务中耦合就不可避免。
消费者取出一条消息,进行业务处理,处理完成后须要告诉Deco系统消息执行结果,成功或者失败,若是失败须要从新把消息写入队列,注意这里说的失败并非业务处理的正常返回“失败”状态,而是因为一些异常缘由致使业务处理没有正常完成的状况。Poi系统方须要重试时才会发送失败的通知。
要保证最终一致性,该方案还有一个关键点是消息队列自己的可靠性和写数据库和写消息队列事务的一致性。好比淘宝的notify的两阶段提交机制就是为了知足这一点。也能够参考糯米技术文章平台有一篇技术文章《轻量级高可靠消息队列》
对于单机单库系统,数据一致性可经过关系型数据库的事务来知足,并且ACID特性中的C是指强一致性,各数据库自己都支持,并且很成熟。
分布式系统则须要以BASE理论做为指导,即以基本可用性和最终一致性做为目标。
远程RPC调用是一致性问题主要缘由,异步解耦+消息队列可做为分布式系统知足最终一致性的优秀方案。