精华内容
下载资源
问答
  • 如何解决分布式系统数据事务一致性问题 (HBase加Solr) 摘要:对于所有的分布式系统,我想事务一致性问题是极其非常重要的问题,因为它直接影响到系统的可用性。本文以下所述所要解决的问题是:对于入HBase和Solr...

    如何解决分布式系统数据事务一致性问题

    (HBase加Solr)

    摘要:对于所有的分布式系统,我想事务一致性问题是极其非常重要的问题,因为它直接影响到系统的可用性。本文以下所述所要解决的问题是:对于入HBase和Solr的过程,如何保证HBase中写入的数据与Solr中写入的数据完全一致。

    关键词:HBase, Solr, 分布式, 事务, 系统架构, 大数据

    作者:王安琪(博客:http://www.cnblogs.com/wgp13x/


      

    一、关于分布式系统事务一致性问题

    Java 中有三种可以的事务模型,分别称作本地事务模型(Local Transaction Model),编程式事务模型(Programmatic Transaction Model),和声明式事务模型(Declarative Transaction Model)。事务要求包含原子性(Atomicity),一致性(Consistency),独立性(Isolation),和持久性(Durability)。

    《大型网站系统与Java中间件实践》一书中分享了一些解决分步式系统一致性问题的方案构思与实践,如在第六章中谈到的消息中间件。下表展现了解决一致性方案与传统方式的对比。

     

    传统方式是,我做完了,发你消息。解决一致性的方案的意思就是,我先发你消息,我做完了再跟你确认我做完了。这是改进后的有事务的消息中间件。

    因为在非XA 环境中,消息队列的插入过程独立于数据库更新操作,ACID 准则中的原子性和独立性不能得到保证,从而整体上数据完整性受到损害。使用X/Open 的XA 接口,我们便能够做到协调多个资源,保证维持ACID 准则。

    在《淘宝技术这十年》这本书里也提到这么一段描写“用户在银行的网关付钱后,银行需要通知到支付宝,但银行的系统不一定能发出通知;如果通知发出了,不一定能通知到;如果通知到了,不一定不重复通知一遍。这个状况在支付宝持续了很长时间,非常痛苦。支付宝从淘宝剥离出来的时候,淘宝和支付宝之间的通信也面临同样的问题,那是2005年的事情,支付宝的架构师鲁肃提出用MQ(Message Queue)的方式来解决这个问题,我负责淘宝这边读取消息的模块。但我们发现消息数量上来之后,常常造成拥堵,消息的顺序也会出错,在系统挂掉的时候,消息也会丢掉,这样非常不保险。然后鲁肃提出做一个系统框架上的解决方案,把要发出的通知存放到数据库中,如果实时发送失败,再用一个时间程序来周期性地发送这些通知,系统记录下消息的中间状态和时间戳,这样保证消息一定能发出,也一定能通知到,且通知带有时间顺序,这些通知甚至可以实现事务性的操作。”

    一致性更是可以分为强一致性和弱一致性两种,弱一致性可以允许某一时间间隔内的偶尔不一致,强一致性的要求要高很多。在实际中,弱一致性往往就能达到业务要求,甚至某些银行系统都只要求弱一致性即可,允许不一致性的窗口存在,只要不造成损失即可。

    对于每一种分布式系统,其组织方式各不相同,实现形式也各有千秋,业务要求更是千变万化,因此要因地制宜的实施一致性方案。表6-5提出的解决办法是要求处理方在完成业务操作后主动发送给消息中间件这一结果,而后消息中间件确认后再做处理,这样是可以保证事务性。但对于表6-5提出的解决办法,在入HBase和Solr的流程中并不能适用。因为为了保证数据写入Solr的性能,入Solr使用的是Concurrent....方式,然而此种方式并不会返回是否入Solr成功,因此这种异步特性不是表6-5中方案所能解决的。

     

    二、针对HBase和Solr分布式系统事务一致性解决方案

    在此,我们对于HBase加Solr这种分布式系统,经过种种构思-推翻-再构思-再推翻,终于成功,特设计了如下事务一致性解决方案。

    1、写入数据到HBase和Solr

     

    图1 HBase加Solr分布式系统事务一致性解决方案(写入数据)

     

    从图1时序图中可以看出,其思想与表6-5方案还是一致的,但实现手法则完全不同。它的本质即是:需要确认数据处理成功后,方可证实数据同步。关键在于,如何确认数据处理成功,靠HBase返回?靠Solr返回?不行。那只有做个缓存,先把没确认的存着,等后期有时间了挨个确认。这里的MySQL就起到了方案所述的缓存的作用。我们先把数据写入到MySQL缓存起来,写入时数据状态为0,说明还没有提交HBase和Solr,每间隔3秒我们使用“入库线程”取状态为0的数据,提交到HBase和Solr中,并将数据状态更新为2,以此说明此数据已经入了库。如果没有“核查线程”做数据一致性检查,则数据一致性无法保证。有可能存在这样一种情况:HBase里数据写入成功了,Solr里出于某种原因没有写入成功(Solr异常了或网络不通了等等)。如果此不一致性很久没有被发现,那么就会在HBase中出现一些根本无法取得的飘浮数据。我们的“核查线程”可以保证HBase中和Solr里的数据是一致的。

     

    2、从HBase和Solr中删除数据

    现在我们已经做到了写入数据操作的事务一致性,同理的还有,删除数据操作的事务一致性,更新数据操作的事务一致性,都可以以这种思想实现。

     

    图2 HBase加Solr分布式系统事务一致性解决方案(删除数据)

     

    从图2中可以看出,删除数据先从Solr中删除,再从HBase中删除,同样的,如果发生某种不可预见的异常,HBase中也会出现一些根本无法取得的飘浮数据,这种情况很少见,然而一旦发生,我们的“核查线程”可以保证HBase中和Solr里的数据是一致的。

    3、更新数据到HBase和Solr

     

    图3 HBase加Solr分布式系统事务一致性解决方案(更新数据)

     

    更新数据的一致性解决方案要稍微复杂一些,因为对HBase和Solr中数据核查某一数据是否已经正确更新是很难做到的。你可以将HBase中的数据一个个地取出来与更新数据进行比较,查看是否已经正确更新;但你没有办法将更新数据所有的字段去Solr中查,是否更新到Solr。因此我们设计的方案是:先对要更新的RowKey-数据生成一个新的newRowKey,再将HBase和Solr中的原始数据进行删除,然后将更新后的数据添加入HBase和Solr中,这样就是完成了一次更新数据的操作,将更新分成了删除与添加两步进行操作,核查此数据是否已经正确更新也因此有迹可寻,此时只需要搜索HBase和Solr中有newRowKey即可证明数据已经更新成功。

     

    三、总结

    在这里,我们引用一下《支付宝数据平台》中的海狗系统的架构设计。海狗系统(ARSC)——准实时搜索查询,它提供千亿级别数据实时查询和全文检索、支持每天10亿+级别的数据更新。它的实时性可以保证实时搜索延迟3s、查询和插入TPS > 1.5WTPS。数据容量线性扩展,Schema扩展基于HBase列式无限扩张,基于ZK动态感知节点状态自动容灾。下图即简单表明了其流程。

     

     

     

    粗看不起眼,琢磨一下便知其是考虑到了HBase和Solr的数据一致性的。在HBase中的MQ表就是起到上面我们的设计方案中的MySQL的作用。在d步骤中,才批量删除处理过的数据,MQ表是留凭证用的。HBase在高性能处理方面还是要远远优于MySQL,如果可以,我们设计方案中的MySQL也可以用HBase取代。

    做个总结:无论是我们设计方案,还是其他类似的分布式系统事务性解决方案,其的本质思想是一样的,即是:做个缓存,先把没确认的存着,等后期有时间了挨个确认。

    “既然计算是异步的,那么反馈也应该是异步的,你完全可以让SendMail将发送结果写入数据库,并生成报表,然后让应用程序定期对报告中发送失败的邮件执行再次发送。这里需要假设失败的情况并不是很多。”在《构建高性能web站点》第17章分布式计算-异布计算中对此类问题的解决方法,也是构成我们解决HBase和Solr分布式系统事务一致性问题的重要指导,感谢作者郭欣。当然也感谢《大型网站系统与Java中间件实践》的作者曾宪杰、《构建高性能web站点》的作者郭欣。更感谢分享海狗系统设计的蒋杰(花名:平原君),以及众多乐于分享技术的人们。

    看这些书,觉得系统架构方面的技术真的是非常庞大,佩服阿里的那群将数据从小做到大的问题解决者。千里之行,始于足下。

     

     

     




    展开全文
  • 分布式系统事务一致性解决方案

    万次阅读 2016-09-09 12:29:38
    分布式系统事务一致性解决方案 本文首发于InfoQ,版权所有,请勿转载!!! http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency 开篇 在OLTP系统领域,我们在很...

    分布式系统事务一致性解决方案

    本文首发于InfoQ,版权所有,请勿转载!!!

    http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

    开篇

    在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转账的案例。传统的企业开发,系统往往是以单体应用形式存在的,也没有横跨多个数据库。我们通常只需借助开发平台中特有数据访问技术和框架(例如Spring、JDBC、ADO.NET),结合关系型数据库自带的事务管理机制来实现事务性的需求。关系型数据库通常具有ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

    而大型互联网平台往往是由一系列分布式系统构成的,开发语言平台和技术栈也相对比较杂,尤其是在SOA和微服务架构盛行的今天,一个看起来简单的功能,内部可能需要调用多个“服务”并操作多个数据库或分片来实现,情况往往会复杂很多。单一的技术手段和解决方案,已经无法应对和满足这些复杂的场景了。

    分布式系统的特性

    对分布式系统有过研究的读者,可能听说过“CAP定律”、“Base理论”等,非常巧的是,化学理论中ACID是酸、Base恰好是碱。这里笔者不对这些概念做过多的解释,有兴趣的读者可以查看相关参考资料。CAP定律如下图:

     

    在分布式系统中,同时满足“CAP定律”中的“一致性”、“可用性”和“分区容错性”三者是不可能的,这比现实中找对象需同时满足“高、富、帅”或“白、富、美”更加困难。在互联网领域的绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

    分布式事务

    提到分布式系统,必然要提到分布式事务。要想理解分布式事务,不得不先介绍一下两阶段提交协议。先举个简单但不精准的例子来说明:

    第一阶段,张老师作为“协调者”,给小强和小明(参与者、节点)发微信,组织他们俩明天8点在学校门口集合,一起去爬山,然后开始等待小强和小明答复。

    第二阶段,如果小强和小明都回答没问题,那么大家如约而至。如果小强或者小明其中一人回答说“明天没空,不行”,那么张老师会立即通知小强和小明“爬山活动取消”。

    细心的读者会发现,这个过程中可能有很多问题的。如果小强没看手机,那么张老师会一直等着答复,小明可能在家里把爬山装备都准备好了却一直等着张老师确认信息。更严重的是,如果到明天8点小强还没有答复,那么就算“超时”了,那小明到底去还是不去集合爬山呢?

    这就是两阶段提交协议的弊病,所以后来业界又引入了三阶段提交协议来解决该类问题。

    两阶段提交协议在主流开发语言平台,数据库产品中都有广泛应用和实现的,下面来介绍一下XOpen组织提供的DTP模型图:

    XA协议指的是TM(事务管理器)和RM(资源管理器)之间的接口。目前主流的关系型数据库产品都是实现了XA接口的。JTA(Java Transaction API)是符合X/Open DTP模型的,事务管理器和资源管理器之间也使用了XA协议。 本质上也是借助两阶段提交协议来实现分布式事务的,下面分别来看看XA事务成功和失败的模型图:

    在JavaEE平台下,WebLogic、Webshare等主流商用的应用服务器提供了JTA的实现和支持。而在Tomcat下是没有实现的(其实笔者并不认为Tomcat能算是JavaEE应用服务器),这就需要借助第三方的框架Jotm、Automikos等来实现,两者均支持spring事务整合。

    而在Windows .NET平台中,则可以借助ado.net中的TransactionScop API来编程实现,还必须配置和借助Windows操作系统中的MSDTC服务。如果你的数据库使用的mysql,并且mysql是部署在Linux平台上的,那么是无法支持分布式事务的。 由于篇幅关系,这里不展开,感兴趣的读者可以自行查阅相关资料并实践。

    总结:这种方式实现难度不算太高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况。但分布式事务对性能的影响会比较大,不适合高并发和高性能要求的场景。

    提供回滚接口

    在服务化架构中,功能X,需要去协调后端的A、B甚至更多的原子服务。那么问题来了,假如A和B其中一个调用失败了,那可怎么办呢?

    在笔者的工作中经常遇到这类问题,往往提供了一个BFF层来协调调用A、B服务。如果有些是需要同步返回结果的,我会尽量按照“串行”的方式去调用。如果调用A失败,则不会盲目去调用B。如果调用A成功,而调用B失败,会尝试去回滚刚刚对A的调用操作。

    当然,有些时候我们不必严格提供单独对应的回滚接口,可以通过传递参数巧妙的实现。

    这样的情况,我们会尽量把可提供回滚接口的服务放在前面。举个例子说明:

    我们的某个论坛网站,每天登录成功后会奖励用户5个积分,但是积分和用户又是两套独立的子系统服务,对应不同的DB,这控制起来就比较麻烦了。解决思路:

    1. 把登录和加积分的服务调用放在BFF层一个本地方法中。
    2. 当用户请求登录接口时,先执行加积分操作,加分成功后再执行登录操作
    3. 如果登录成功,那当然最好了,积分也加成功了。如果登录失败,则调用加积分对应的回滚接口(执行减积分的操作)。

    总结:这种方式缺点比较多,通常在复杂场景下是不推荐使用的,除非是非常简单的场景,非常容易提供回滚,而且依赖的服务也非常少的情况。

     

    这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。

    本地消息表

    这种实现方式的思路,其实是源于ebay,后来通过支付宝等公司的布道,在业内广泛使用。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。

    举个经典的跨行转账的例子来描述。

    第一步伪代码如下,扣款1W,通过本地事务保证了凭证消息插入到消息表中。

    第二步,通知对方银行账户上加1W了。那问题来了,如何通知到对方呢?

    通常采用两种方式:

    1. 采用时效性高的MQ,由对方订阅消息并监听,有消息时自动触发事件
    2. 采用定时轮询扫描的方式,去检查消息表的数据。

    两种方式其实各有利弊,仅仅依靠MQ,可能会出现通知失败的问题。而过于频繁的定时轮询,效率也不是最佳的(90%是无用功)。所以,我们一般会把两种方式结合起来使用。

    解决了通知的问题,又有新的问题了。万一这消息有重复被消费,往用户帐号上多加了钱,那岂不是后果很严重?

    仔细思考,其实我们可以消息消费方,也通过一个“消费状态表”来记录消费状态。在执行“加款”操作之前,检测下该消息(提供标识)是否已经消费过,消费完成后,通过本地事务控制来更新这个“消费状态表”。这样子就避免重复消费的问题。

    总结:上诉的方式是一种非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息会给数据库造成压力。所以,在真正的高并发场景下,该方案也会有瓶颈和限制的。

    MQ(非事务消息)

    通常情况下,在使用非事务消息支持的MQ产品时,我们很难将业务操作与对MQ的操作放在一个本地事务域中管理。通俗点描述,还是以上述提到的“跨行转账”为例,我们很难保证在扣款完成之后对MQ投递消息的操作就一定能成功。这样一致性似乎很难保证。

    先从消息生产者这端来分析,请看伪代码:

    根据上述代码及注释,我们来分析下可能的情况:

    1. 操作数据库成功,向MQ中投递消息也成功,皆大欢喜
    2. 操作数据库失败,不会向MQ中投递消息了
    3. 操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚

    从上面分析的几种情况来看,貌似问题都不大的。那么我们来分析下消费者端面临的问题:

    1. 消息出列后,消费者对应的业务操作要执行成功。如果业务执行失败,消息不能失效或者丢失。需要保证消息与业务操作一致
    2. 尽量避免消息重复消费。如果重复消费,也不能因此影响业务结果

    如何保证消息与业务操作一致,不丢失?

    主流的MQ产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制的(有些MQ可以自定义重试次数)。

    如何避免消息被重复消费造成的问题?

    1. 保证消费者调用业务的服务接口的幂等性
    2. 通过消费日志或者类似状态表来记录消费状态,便于判断(建议在业务上自行实现,而不依赖MQ产品提供该特性)

     

    总结:这种方式比较常见,性能和吞吐量是优于使用关系型数据库消息表的方案。如果MQ自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的。不过在没有充分测试的情况下,不建议在交易业务中直接使用。

    MQ(事务消息)

    举个例子,Bob向Smith转账,那我们到底是先发送消息,还是先执行扣款操作?

    好像都可能会出问题。如果先发消息,扣款操作失败,那么Smith的账户里面会多出一笔钱。反过来,如果先执行扣款操作,后发送消息,那有可能扣款成功了但是消息没发出去,Smith收不到钱。除了上面介绍的通过异常捕获和回滚的方式外,还有没有其他的思路呢?

    下面以阿里巴巴的RocketMQ中间件为例,分析下其设计和实现思路。

    RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。细心的读者可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。如下图:

    总结:据笔者的了解,各大知名的电商平台和互联网公司,几乎都是采用类似的设计思路来实现“最终一致性”的。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,所以需二次开发或者新造轮子。比较遗憾的是,RocketMQ事务消息部分的代码也并未开源,需要自己去实现。

    其他补偿方式

    做过支付宝交易接口的同学都知道,我们一般会在支付宝的回调页面和接口里,解密参数,然后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当我们回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会停止回调请求。否则,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。

    其实这就是一个很典型的补偿例子,跟一些MQ重试补偿机制很类似。

    一般成熟的系统中,对于级别较高的服务和接口,整体的可用性通常都会很高。如果有些业务由于瞬时的网络故障或调用超时等问题,那么这种重试机制其实是非常有效的。

    当然,考虑个比较极端的场景,假如系统自身有bug或者程序逻辑有问题,那么重试1W次那也是无济于事的。那岂不是就发生了“明明已经付款,却显示未付款不发货”类似的悲剧?

    其实为了交易系统更可靠,我们一般会在类似交易这种高级别的服务代码中,加入详细日志记录的,一旦系统内部引发类似致命异常,会有邮件通知。同时,后台会有定时任务扫描和分析此类日志,检查出这种特殊的情况,会尝试通过程序来补偿并邮件通知相关人员。

    在某些特殊的情况下,还会有“人工补偿”的,这也是最后一道屏障。

    小结

    上诉的几种方案中,笔者也大致总结了其设计思路,优势,劣势等,相信读者已经有了一定的理解。其实分布式系统的事务一致性本身是一个技术难题,目前没有一种很简单很完美的方案能够应对所有场景。具体还是要使用者根据不同的业务场景去抉择。

    展开全文
  • 事务是为了保障业务数据的完整和准确的。 分布式事务,常见的两个处理办法就是两段式提交和补偿。 两段式提交典型的就是XA,有个事务协调器,告诉大家,来都准备好提交,大家回复,都准备好了,然后协调器告诉...
    事务就是一个会话过程中,对上下文的影响是一致的,要么所有的更改都做了,要么所有的更变都撤销掉。就要么生,要么死。没有半死不死的中间不可预期状态。

    参考下薛定谔的猫。

    事务是为了保障业务数据的完整性和准确性的。
     
    分布式事务,常见的两个处理办法就是两段式提交和补偿。
    两段式提交典型的就是XA,有个事务协调器,告诉大家,来都准备好提交,大家回复,都准备好了,然后协调器告诉大家,一起提交,大家都提交了。
    补偿比较好理解,先处理业务,然后定时或者回调里,检查状态是不是一致的,如果不一致采用某个策略,强制状态到某个结束状态(一般是失败状态),然后就世界太平了。典型的就是冲正操作。
     
    准备好了以后,如果没有问题,收到提交,所有人都开始提交。
    这个时候,比如对数据库来说,有redo日志的。
    如果某个数据库这时候宕机了,那么它重启的时候,先执行检查,也会把上一次的这些操作都提交掉的。所以各个点的数据都是一致的。
     
    问题 1:比如 一个业务要调用很多的服务都是写操作,如果有其中一个写的服务失败了,怎么办 ?假设 4个写的吧,有2个写失败了 。

    kimmking:淘宝之类的网站一般的做法是,如果4个都成功才算成功,那么这次提交时4个写都设置成一个中间状态,先容许不一致。然后4个执行完成了以后,回调或是定时任务里检查这4个数据是不是一致的,如果一致就全部置为成功状态,如果不一致就全部置为失败。
     
    复杂的业务交互过程中,不建议使用强一致性的分布式事务。解决分布式事务的最好办法就是不考虑分布式事务。就像刚说的问题一样,把分布式的事务过程拆解成多个中间状态,中间状态的东西不允许用户直接操作,等状态都一致成功,或者检测到不一致的时候全部失败掉。就解耦了这个强一致性的过程。
     
    一般情况下准实时就成了。涉及到钱,有时候也可以这么搞。
    淘宝几s内完整一个订单处理,不是什么问题吧。
    银行也不是全部都强一致性。也会扎差,也会冲正。
    特别是涉及到多个系统的时候,我们比如买机票,支付完成以后,只支付完成状态,然后返回给用户了,我们过几分钟再刷新页面,才会看到变成已出票,订单完成状态。
    这个时候,如果我们要求所有处理,都是强一致性的,那么久完蛋了。页面要死在那儿几分钟,才把这个事务处理完成,返回给用户。
     
    这样就肯定涉及一个问题,支付了,但是最终出票没出来。那就没办法,商量换票或退款。
    淘宝的订单改成出票失败,给支付发消息通知退款。
    慢的时候,有可能是手工出票,这时出一张票半小时都可能,如果要求都必须强一致性的话,所有处理线程都挂在哪儿,系统早就完蛋了。
     
    解决分布式事务的最好办法就是不考虑分布式事务。
    拆分,大的业务流程,转化成几个小的业务流程,然后考虑最终一致性。
     
    问题2:分布式事务是你们自己开发的,还是数据库自带的?
    kimmking:
    1、只要一个处理逻辑能保证要么成功,要么跟什么也没做一样,都算是事务。数据库事务,MQ也有事务。
    你自己甚至可以写个程序生成两个文件,要么都生成了,要么都删掉不留痕迹,这也算是事务。
    2、分布式事务这一块有个XA规范,实现XA接口的事务,都可以加入到一个分布式事务中,被XA容器管理起来。

    3、补偿的办法,需要具体情况具体分析,没有一个各种场合都适用的框架。

     

    ------------------------------------------------------------------------------------------------------------------

     

    开篇

     

    在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转账的案例。传统的企业开发,系统往往是以单体应用形式存在的,也没有横跨多个数据库。我们通常只需借助开发平台中特有数据访问技术和框架(例如Spring、JDBC、ADO.NET),结合关系型数据库自带的事务管理机制来实现事务性的需求。关系型数据库通常具有ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

    而大型互联网平台往往是由一系列分布式系统构成的,开发语言平台和技术栈也相对比较杂,尤其是在SOA和微服务架构盛行的今天,一个看起来简单的功能,内部可能需要调用多个“服务”并操作多个数据库或分片来实现,情况往往会复杂很多。单一的技术手段和解决方案,已经无法应对和满足这些复杂的场景了。

    分布式系统的特性

    对分布式系统有过研究的读者,可能听说过“CAP定律”、“Base理论”等,非常巧的是,化学理论中ACID是酸、Base恰好是碱。这里笔者不对这些概念做过多的解释,有兴趣的读者可以查看相关参考资料。CAP定律如下图:

     

     

    在分布式系统中,同时满足“CAP定律”中的“一致性”、“可用性”和“分区容错性”三者是不可能的,这比现实中找对象需同时满足“高、富、帅”或“白、富、美”更加困难。在互联网领域的绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

    分布式事务

    提到分布式系统,必然要提到分布式事务。要想理解分布式事务,不得不先介绍一下两阶段提交协议。先举个简单但不精准的例子来说明:

    第一阶段,张老师作为“协调者”,给小强和小明(参与者、节点)发微信,组织他们俩明天8点在学校门口集合,一起去爬山,然后开始等待小强和小明答复。

    第二阶段,如果小强和小明都回答没问题,那么大家如约而至。如果小强或者小明其中一人回答说“明天没空,不行”,那么张老师会立即通知小强和小明“爬山活动取消”。

    细心的读者会发现,这个过程中可能有很多问题的。如果小强没看手机,那么张老师会一直等着答复,小明可能在家里把爬山装备都准备好了却一直等着张老师确认信息。更严重的是,如果到明天8点小强还没有答复,那么就算“超时”了,那小明到底去还是不去集合爬山呢?

    这就是两阶段提交协议的弊病,所以后来业界又引入了三阶段提交协议来解决该类问题。

    两阶段提交协议在主流开发语言平台,数据库产品中都有广泛应用和实现的,下面来介绍一下XOpen组织提供的DTP模型图:

     

    XA协议指的是TM(事务管理器)和RM(资源管理器)之间的接口。目前主流的关系型数据库产品都是实现了XA接口的。JTA(Java Transaction API)是符合X/Open DTP模型的,事务管理器和资源管理器之间也使用了XA协议。 本质上也是借助两阶段提交协议来实现分布式事务的,下面分别来看看XA事务成功和失败的模型图:

     

     

    在JavaEE平台下,WebLogic、Webshare等主流商用的应用服务器提供了JTA的实现和支持。而在Tomcat下是没有实现的(其实笔者并不认为Tomcat能算是JavaEE应用服务器),这就需要借助第三方的框架Jotm、Automikos等来实现,两者均支持spring事务整合。

    而在Windows .NET平台中,则可以借助ado.net中的TransactionScop API来编程实现,还必须配置和借助Windows操作系统中的MSDTC服务。如果你的数据库使用的mysql,并且mysql是部署在Linux平台上的,那么是无法支持分布式事务的。 由于篇幅关系,这里不展开,感兴趣的读者可以自行查阅相关资料并实践。

    总结:这种方式实现难度不算太高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况。但分布式事务对性能的影响会比较大,不适合高并发和高性能要求的场景。

    提供回滚接口

    在服务化架构中,功能X,需要去协调后端的A、B甚至更多的原子服务。那么问题来了,假如A和B其中一个调用失败了,那可怎么办呢?

    在笔者的工作中经常遇到这类问题,往往提供了一个BFF层来协调调用A、B服务。如果有些是需要同步返回结果的,我会尽量按照“串行”的方式去调用。如果调用A失败,则不会盲目去调用B。如果调用A成功,而调用B失败,会尝试去回滚刚刚对A的调用操作。

    当然,有些时候我们不必严格提供单独对应的回滚接口,可以通过传递参数巧妙的实现。

    这样的情况,我们会尽量把可提供回滚接口的服务放在前面。举个例子说明:

    我们的某个论坛网站,每天登录成功后会奖励用户5个积分,但是积分和用户又是两套独立的子系统服务,对应不同的DB,这控制起来就比较麻烦了。解决思路:

    1. 把登录和加积分的服务调用放在BFF层一个本地方法中。
    2. 当用户请求登录接口时,先执行加积分操作,加分成功后再执行登录操作
    3. 如果登录成功,那当然最好了,积分也加成功了。如果登录失败,则调用加积分对应的回滚接口(执行减积分的操作)。

    总结:这种方式缺点比较多,通常在复杂场景下是不推荐使用的,除非是非常简单的场景,非常容易提供回滚,而且依赖的服务也非常少的情况。

     

    这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。

    本地消息表

    这种实现方式的思路,其实是源于ebay,后来通过支付宝等公司的布道,在业内广泛使用。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。

    举个经典的跨行转账的例子来描述。

    第一步伪代码如下,扣款1W,通过本地事务保证了凭证消息插入到消息表中。

     

    第二步,通知对方银行账户上加1W了。那问题来了,如何通知到对方呢?

    通常采用两种方式:

    1. 采用时效性高的MQ,由对方订阅消息并监听,有消息时自动触发事件
    2. 采用定时轮询扫描的方式,去检查消息表的数据。

    两种方式其实各有利弊,仅仅依靠MQ,可能会出现通知失败的问题。而过于频繁的定时轮询,效率也不是最佳的(90%是无用功)。所以,我们一般会把两种方式结合起来使用。

    解决了通知的问题,又有新的问题了。万一这消息有重复被消费,往用户帐号上多加了钱,那岂不是后果很严重?

    仔细思考,其实我们可以消息消费方,也通过一个“消费状态表”来记录消费状态。在执行“加款”操作之前,检测下该消息(提供标识)是否已经消费过,消费完成后,通过本地事务控制来更新这个“消费状态表”。这样子就避免重复消费的问题。

    总结:上诉的方式是一种非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息会给数据库造成压力。所以,在真正的高并发场景下,该方案也会有瓶颈和限制的。

    MQ(非事务消息)

    通常情况下,在使用非事务消息支持的MQ产品时,我们很难将业务操作与对MQ的操作放在一个本地事务域中管理。通俗点描述,还是以上述提到的“跨行转账”为例,我们很难保证在扣款完成之后对MQ投递消息的操作就一定能成功。这样一致性似乎很难保证。

    先从消息生产者这端来分析,请看伪代码:

     

    根据上述代码及注释,我们来分析下可能的情况:

    1. 操作数据库成功,向MQ中投递消息也成功,皆大欢喜
    2. 操作数据库失败,不会向MQ中投递消息了
    3. 操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚

    从上面分析的几种情况来看,貌似问题都不大的。那么我们来分析下消费者端面临的问题:

    1. 消息出列后,消费者对应的业务操作要执行成功。如果业务执行失败,消息不能失效或者丢失。需要保证消息与业务操作一致
    2. 尽量避免消息重复消费。如果重复消费,也不能因此影响业务结果

    如何保证消息与业务操作一致,不丢失?

    主流的MQ产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制的(有些MQ可以自定义重试次数)。

    如何避免消息被重复消费造成的问题?

    1. 保证消费者调用业务的服务接口的幂等性
    2. 通过消费日志或者类似状态表来记录消费状态,便于判断(建议在业务上自行实现,而不依赖MQ产品提供该特性)

     

    总结:这种方式比较常见,性能和吞吐量是优于使用关系型数据库消息表的方案。如果MQ自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的。不过在没有充分测试的情况下,不建议在交易业务中直接使用。

    MQ(事务消息)

    举个例子,Bob向Smith转账,那我们到底是先发送消息,还是先执行扣款操作?

    好像都可能会出问题。如果先发消息,扣款操作失败,那么Smith的账户里面会多出一笔钱。反过来,如果先执行扣款操作,后发送消息,那有可能扣款成功了但是消息没发出去,Smith收不到钱。除了上面介绍的通过异常捕获和回滚的方式外,还有没有其他的思路呢?

    下面以阿里巴巴的RocketMQ中间件为例,分析下其设计和实现思路。

    RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。细心的读者可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。如下图:

     

    总结:据笔者的了解,各大知名的电商平台和互联网公司,几乎都是采用类似的设计思路来实现“最终一致性”的。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,所以需二次开发或者新造轮子。比较遗憾的是,RocketMQ事务消息部分的代码也并未开源,需要自己去实现。

    其他补偿方式

    做过支付宝交易接口的同学都知道,我们一般会在支付宝的回调页面和接口里,解密参数,然后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当我们回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会停止回调请求。否则,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。

    其实这就是一个很典型的补偿例子,跟一些MQ重试补偿机制很类似。

    一般成熟的系统中,对于级别较高的服务和接口,整体的可用性通常都会很高。如果有些业务由于瞬时的网络故障或调用超时等问题,那么这种重试机制其实是非常有效的。

    当然,考虑个比较极端的场景,假如系统自身有bug或者程序逻辑有问题,那么重试1W次那也是无济于事的。那岂不是就发生了“明明已经付款,却显示未付款不发货”类似的悲剧?

    其实为了交易系统更可靠,我们一般会在类似交易这种高级别的服务代码中,加入详细日志记录的,一旦系统内部引发类似致命异常,会有邮件通知。同时,后台会有定时任务扫描和分析此类日志,检查出这种特殊的情况,会尝试通过程序来补偿并邮件通知相关人员。

    在某些特殊的情况下,还会有“人工补偿”的,这也是最后一道屏障。

    小结

    上诉的几种方案中,笔者也大致总结了其设计思路,优势,劣势等,相信读者已经有了一定的理解。其实分布式系统的事务一致性本身是一个技术难题,目前没有一种很简单很完美的方案能够应对所有场景。具体还是要使用者根据不同的业务场景去抉择。

     

     

     

    展开全文
  • 事务一致性简述

    千次阅读 2015-11-15 14:35:40
    事务一致性简述

      服务化之后,事务一致性需要得到保证。具体要视业务场景选择合适的方案保证事务的一致性。

      数据库事务的ACID特性,分别含义如下:

      A:  指原子性,即一个事务下的n个复合操作,要么全部成功,要么全部失败。

      c:指的是一致性,即要求事务做完后,要求满足数据库的一些完整性约束,如:记录的主键约束。从业务层理解是:A帐户转钱给B帐户100元钱,这时数据库事务就必须保证A帐户钱减100,B帐户加100,且最终a,b帐户余额也是正确的。

     i: 指的隔离性,指的是一个事务未提交的业务结果是否对于其它事务可见。级别一般有:read_uncommit,read_commit,read_repeatable,串行化访问。

     d:指的是持久性。即事务操作完后,事务的结果应该是持久化的。


      传统数据库,如mysql,oracle一般采用日志来保证事务的acid特性。事务acid在单个数据库时比较容易满足。即只要将a,b,c三个业务操作放在一个事务中执行,之后的acid特性由数据库底层保证。


      而当服务化后,多个业务操作会涉及多台服务器,多个数据库存储。这就需要有相应的手段保证业务操作在多个数据库中的一致性。

      分布式服务事务比较有名的cap理论,提供了一种在分布式服务中事务设计的一种思路。

      cap理论中的c指的是,从任意多个结点(如多个数据库),它们上面的数据副本应该是一致的。a指的是对于[更新数据的服务]的可用性。

      p指的是分区的情况。

     简单来讲cap指的是无法同时满足cap三者。这样在有p分区的情况下,需要我们在c和a间做一个平衡。

     

      举例来讲,电商中,要购买一件产品,假设有:A操作-扣除产品份额,b操作-支付扣用户帐户钱,c操作将产品加到用户资产。

      这个就是典型的一种分布式服,由于a,b,c操作分别调用三个服务。而当操作a,b, c时都可能发生网络超时会数据库等错误。这样会导致a,b,c操作会处于不一致状态。

      如a操作-扣款产品份额成功,b操作超时,这时就发生了分区的情况。因为b操作-支付可能成功也可能失败,此时系统已经发生不一致的情况。

      此时我们一般使用事务补偿机制来做,即后台一个线程会扫描此笔交易,发现b支付操作如果成功,则更新订单为成功,做c操作。

      如果b支付操作失败,则回滚a操作。

      如果b支付操作为操作中,则回滚a操作产品扣份额。此笔订单之后再做处理。直到明确得到支付结果再执行正向提交操作,或逆向回滚操作。



    展开全文
  • 分布式系统事务一致性是一个技术难题,各种解决方案孰优孰劣?老司机介绍 丁浪,现就职于某垂直电商平台,担任技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等方面有深入研究和...
  • dubbo如何保证事务一致性

    千次阅读 2018-06-25 18:53:00
    作者:清风链接:...目前比较多的解决方案有几个: 一、结合MQ消息中间件实现的可靠消息最终一致性 二、TCC补偿性事务解决方案 三、最大努力通知型方案第一种方案:可靠消息最终一致性,需要业务系统结...
  • 在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转账的案例。传统的企业开发,系统往往是以单体应用形式存在的,也没有横跨多个数据库。我们通常只需借助开发平台中特有...
  • 分布式系统中的事务一致性问题

    千次阅读 2014-04-01 21:09:45
    1、任何一次修改保证数据一致性。 2、多次数据修改的一致性。 在弱一致性的算法,不要求每次修改的内容在修改后多副本的内容是一致的,对问题1的解决比较宽松,更多解决问题2,该类算法追求每次修改
  • 分布式系统事务一致性是一个技术难题,各种解决方案孰优孰劣? 在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转账的案例。传统的企业开发,系统往往是以单体应用...
  • 浅谈事务一致性问题 原文地址 https://www.jianshu.com/p/f0a1b00a6002 在高并发场景下,分布式储存和处理已经是常用手段。但分布式的结构势必会带来“不一致”的麻烦问题,而事务正是解决这一问题而引入的一种...
  • 微服务架构下的事务一致性保证

    千次阅读 2019-04-04 14:07:56
    今天我给大家分享的题目是微服务架构下的事务一致性保证。 主要内容包括4部分: 传统分布式事务不是微服务中一致性的最佳选择 微服务架构中应满足数据最终一致性原则 微服务架构实现最终一致性的三种模式 对账...
  • 上篇文章:分布式系统漫谈【玖】_分布式事务一致性:协议支持 其实对于生产环境的分布式事务一致,各大互联网公司都是自己实现的解决方案,总结起来无非是异步、补偿、实时查询、定期校对几种模式,大部分场景都...
  • 关于事务一致性,《数据库系统概念》中是这样描述的 第二段说的三个特性是指原子性、隔离性、持久性。 就算这样,相信大家也是懵懵的,我也是,所以才会写下这篇博客。 看到别的博客说,一致性事务的最终...
  • mq实现分布式事务-补偿事务一致性CAP原则Rocket mq实现思路Rabbit mq实现思路需要考虑的问题后记 严格的来说,消息中间件并不能实现分布式事务,而是通过事后补偿机制,达到和分布式事务一样的数据一致性。这里主要...
  • 事务型消息-保证最终一致性概述一、事务型消息原理1.1 消息队列简介1.2 消息队列应用实例1.3 事务型消息设计方案1.4 事务型消息总结二、操作流水2.1 库存数据库最终一致性保证2.2 业务场景决定高可用技术实现2.3 ...
  • 分布式系统事务一致性到CAP,BASE理论

    千次阅读 2017-02-08 10:44:00
     2、一致性(Consistency):事务一致性是指事务执行之前和执行之后数据保持一致。  3、隔离行(Isolation):事务的隔离性是指两个或多个事务之间相互隔离,互不影响。  4、持久性(Durability):事务持久...
  • 今天和同事讨论了下微服务架构下的事务一致性问题,查了下资料,抄录如下。说到底,最终都必须保证数据一致性,至于中间具体采用哪种模式,还是需要根据具体的业务逻辑来选择。1、可靠事件模式可靠事件模式属于事件...
  • Rabbitmq实现多系统间的分布式事务保证数据一致性一、实验环境二、实验目的三、实验方案四、实验步骤1、消息队列2、数据库准备2.1订单表2.2 消息发送状态表2.3 运单表3、订单中心3.1 订单中心分析3.2 编写代码...
  • 事务一致性

    千次阅读 2018-12-01 18:29:50
    作者:孟波 ...来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明...首先,我们需要搞清楚为什么会出现事务.[1]Transactions are not a law of nature; they were created with a purpose,...
  • 分布式系统事务一致性是一个技术难题,各种解决方案孰优孰劣? 在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转账的案例。传统的企业开发,系统往往是以单体应用...
  • 分布式事务一致性

    千次阅读 2018-05-25 17:18:45
    有人的地方,就有江湖有江湖的地方,就有纷争问题的起源在电商等业务中,系统一般由多个独立的服务组成,如何解决分布式调用时候数据的一致性? 具体业务场景如下,比如一个业务操作,如果同时调用服务 A、B、C,...
  • 事务一致性的理解

    万次阅读 多人点赞 2018-04-05 20:37:54
    要想真正弄清楚这个问题,那是必须要把数据库理论中的事务机制从头开始看起,牵扯的内容比较多。当然,如果只是想粗略的了解下,我就来举个例子吧——当然不可能太严谨。假设我们10个人,每人有一个账号,...
  • 分布式系统一致性问题(汇总)

    万次阅读 2019-09-02 15:32:19
    保证分布式系统数据一致性的6种方案 问题的起源 在电商等业务中,系统一般由多个独立的服务组成,如何解决分布式调用时候数据的一致性? 具体业务场景如下,比如一个业务操作,如果同时调用服务 A、B、C,需要...
  • 这是一个跨进程事务,需要保持两个系统的数据一致性。如果数据都保存在B系统,则没有系统一致性问题,但通常业务需要,尤其是系统拆分之后,经常需要处理分布式一致事务问题。调B系统可能出现以下三种结果:1. 成功 ...
  •  一致性是一个抽象的、具有多重含义的计算机术语,在不同应用场景下,有不同的定义和含义。在传统的IT时代,一致性通常指强一致性,强一致性通常体现在你中有我、我中有你、浑然一体;而在互联网时代,一致性的含义...
  • 答:我们的系统中通过 RocketMQ 的事务消息来保证数据的最终一致性。 面试官:那你说说它是如何来保证数据的最终一致性的? 答:分两部分来回答,第一部分先回答事务消息的实现流程,第二部分解释为什么它能保证数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 203,120
精华内容 81,248
关键字:

不同的系统如何保证事务的一致性