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

在OLTP系统领域,咱们在不少业务场景下都会面临事务一致性方面的需求,例如最经典的Bob给Smith转帐的案例。传统的企业开发,系统每每是以单体应用形式存在的,也没有横跨多个数据库。spring

咱们一般只需借助开发平台中特有数据访问技术和框架(例如Spring、JDBC、ADO.NET),结合关系型数据库自带的事务管理机制来实现事务性的需求。关系型数据库一般具备ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
sql

而大型互联网平台每每是由一系列分布式系统构成的,开发语言平台和技术栈也相对比较杂,尤为是在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次那也是无济于事的。那岂不是就发生了“明明已经付款,却显示未付款不发货”相似的悲剧?

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

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

小结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 课程介绍 介绍该课程的内容、学习成果、实例,还有学习所需的前提知识。 1-1 导学-分布式事务实践 第2章 事务原则与实现 介绍了事务的四大原则,并通过实例介绍数据库实现事务的方法,以及使用JDBC实现事务的方法。 2-1 事务原则与实现:事务 2-2 事务原则与实现:SQL事务 2-3 事务原则与实现:JDBC事务(上) 2-4 事务原则与实现:JDBC事务(下) 第3章 使用Docker搭建环境 介绍了Docker的使用,通过Docker将课程环境搭建起来,方便那些不了解这些技术的同学之后的学习。 3-1 docker简介与mysql安装-1 3-2 docker简介与mysql安装-2 3-3 SpringBoot基础 第4章 Spring事务机制 介绍了Spring的事务机制、事物抽象、内部事务和外部事物,以及常用的几种事务管理的实现,包括DataSource、JPA、JMS、JTA都通过实例进行说明。还有XA以及两阶段提交,并通过实例演示了使用JTA,通过两阶段提交,实现多数据源的事务实现。... 4-1 Spring事务机制_基本接口 4-2 Spring事务机制_实现 4-3 Jpa事务实例 4-4 Jms事务原理 4-5 Jms-session事务实例 4-6 Jms-spring事务实例 4-7 外部事务与JTA 4-8 JTA单数据源事务实例 4-9 JTA多数据源事务实例 第5章 分布式系统 介绍了分布式系统的定义、实现原则和几种形式,详细介绍了微服务架构的分布式系统,并使用Spring Cloud框架演示了一个完整的微服务系统的实现过程。 5-1 CAP原则和BASE理论简介 5-2 分布式系统综述 5-3 SpringCloud微服务架构 5-4 实现registry 5-5 实现proxy 5-6 user服务 5-7 order服务 5-8 添加hystrix 5-9 使用feign 5-10 优化服务间调用 第6章 分布式事务实现,模式和技术 介绍分布式事务的定义、原则和实现原则,介绍使用Spring框架实现分布式事务的几种方式,包括使用JTA、Spring事务同步、链式事务等,并通过实战介绍其实现。除此以外还介绍了一些分布式事务相关的技术,如幂等性、全局一致性ID、分布式对象等。... 6-1 分布式事务介绍 6-2 spring分布式事务实现_使用JTA 6-3 spring分布式事务实现_不使用JTA 6-4 实例1-DB-DB 6-5 实例1-DB-DB.链式事务管理器 6-6 实例2-JPA-DB.链式事务管理器 6-7 实例3-JMS-DB.最大努力一次提交 6-8 分布式事务实现模式与技术 6-9 全局一致性ID和分布式对象_ 第7章 分布式事务实现:消息驱动模式 详细介绍3种分布式事务实现的模式中的消息驱动模式并通过完整实例演示了消息驱动模式下,实现微服务系统的分布式事务的完整过程。 7-1 分布式事务实现:消息驱动模式 7-2 消息驱动模式实例:设计 7-3 消息驱动模式实例:创建ticket服务 7-4 消息驱动模式实例:实现基本ticket功能 7-5 消息驱动模式实例:锁票1 7-6 消息驱动模式实例:锁票2 7-7 按消息流程实现业务 7-8 支付过程 7-9 票转移 7-10 错误处理:锁票失败 7-11 错误处理:扣费失败 7-12 并发时的错误处理 第8章 分布式事务实现:Event Sourcing模式 详细介绍了分布式事务实现的模式中的Event Sourcing模式,并通过完整实例演示了Event Sourcing模式下,实现微服务系统的分布式事务的完整过程。 8-1 事件溯源模式介绍 8-2 事件溯源模式与Axon框架-1 8-3 事件溯源模式与Axon框架-2 8-4 使用Axon框架的设计过程介绍 8-5 Axon框架-实例(上) 8-6 Axon框架-实例(下) 8-7 Saga模式和Axon Saga 8-8 聚合命令事件(上) 8-9 聚合命令事件(下) 8-10 实现saga 8-11 实现query 8-12 处理超时 8-13 并发测试 8-14 cloud-axon实例:分布式处理介绍 8-15 事件设计 8-16 事件与队列设计 8-17 实现User服务 8-18 实现Ticket服务 8-19 实现Order服务 8-20 实现读写分离 8-21 测试与并发 8-22 事件溯源模式与Axon框架总结 第9章 TCC模式和微服务架构的设计模式 本章介绍TCC模式,也对微服务系统的几种设计模式,以及这些模式下分布式事务的实现模式进行了介绍。 9-1 TCC模式介绍 9-2 微服务架构的设计模式 第10章 课程总
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值