前言

Github:https://github.com/yihonglei/daisy-framework

一 概述

随着系统交易量增加,业务规模的发展,团队规模扩大,为了提高系统高可用,高并发,高性能,可维护性,可扩展性等,

对其进行治理,分为多个独立服务。以前的单系统本地事务,解决不了各个独立服务数据一致性问题,诞生了分布式事务的思想,

用于解决分布式服务调用数据一致性问题。常用的分布式事务解决方案主要有2PC,TCC,本地消息表,事务消息四大核心思想,

可以在对应思想上一生二,二生三,三生万物的变种处理,永远需要结合自己的业务现实去处理,不好高骛远,最终目的就是

实现数据一致性。同时,数据一致性,并不一定是技术手段去处理,有时候可以从业务层面进行优化处理。

二 前置条件

大家现在打车几乎都是免密支付的方式,也有些网约车平台还有用户余额账户的概念,即用户先充值,充得多送得多,司机也有个账户。

假设场景1:打车订单金额 100,交易可以简化为用户账户扣减 100,司机账户加 100 的转账过程,订单扭转等,先不考虑。

1、交易完成前

用户账户余额:1000

司机账户余额:1000

2、交易完成后

用户账户余额:900

司机账户余额:1100

下面探讨下用数据库事务,2PC,TCC,本地消息,MQ事务消息等处理,如何保证两个账户金额的一致性,各个方案会存在哪些

问题,以及如何处理这些问题。

单数据库事务

假如用户账户和司机账户在一个数据库里,可以通过本地事务保证数据一致性。

单数据库事务如何保证数据一致性?

1、在步骤【2】执行 SQL 从用户账户减 100 时出现异常,回滚事务即可保证数据一致性;

2、在步骤【4】执行 SQL 往司机账户加 100 时出现异常,回滚事务即可保证数据一致性;

3、在步骤【6】提交事务时出现异常,回滚事务即可保证数据一致性;

通过同一个事务保证数据一致性,任何一步出现异常,整个事务都会回滚,保证数据一致性;

适用场景?

单体应用单个数据库;

三 多数据库事务后置提交

随着平台规模的扩大,访问量急剧扩大,单台数据库支撑不了用户和司机两个账户的读写请求,

将数据库按业务垂直拆分为用户账户库和司机账户库,能够减缓数据库压力。但是,没法通过

简单的单数据库事务来保证数据的一致性。

多库正常交易时序图:

正常情况下,如果任何一步都不出问题,数据能保持一致性。

但是,如果在步骤【7】添加失败 或 在步骤【9】提交失败司机账户回滚,数据会不一致,因为在步骤【5】提交成功,

结果就会出现用户的钱扣减了,但是司机的账户没有加钱。可以通过后置提交事务方式提高数据的一致性。

多库后置提交交易时序图:

后置提交事务如何避免数据一致性?

1、在步骤【3】执行 SQL 用户账户扣减时出现异常,回滚用户数据库上的事务即可保证数据一致;

2、在步骤【5】执行 SQL 司机账户添加时出现异常,回滚司机数据库上的事务与用户数据库上的事务即可保证数据一致性;

3、在步骤【7】执行提交用户数据库上的事务时出现异常,回滚司机数据库上的事务与用户数据库上的事务即可保证数据一致性;

4、在步骤【9】执行提交司机数据库上的事务时出现异常,只能回滚司机数据库上的事务,用户数据库上由于提交成功了,回滚不了,

出现了用户扣钱和司机加钱不一致情况。

使用后置事务提交只能尽可能的提高数据一致性,还是避免不了数据一致性问题,需要引入新的事务提交方式。

在引入新的事务提交方式时,需要先了解 CAP 和 BASE 理论,因为没有百分百的方案,需要基于相关理论进行选择取舍。

适用场景?

使用的比较少吧,没实际用过!

四 CAP 原则

CAP 原则又称为 CAP 定理,指的是分布式系统中一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),

三者不可兼得,实战常在 CP 和 AP 之间选择。

1、一致性(Consistency)

分布式系统中,客户端知道一系列操作都会同时发生。

比如,用户看自己的钱被扣了,但是司机看自己的钱并没有对应加上,司机读到的还是老数据,出现了数据不一致情况,

所以,要想让用户和司机同时看到减钱和加钱,就必须是强一致的。

2、可用性(Availability)

分布式系统中,每个操作必须以可预期的响应结束。

比如,用户扣钱了,司机预期是加上对应的钱,如果用户和司机看不到预期结果,就是不可用的。

3、分区容错性(Partition tolerance)

分布式系统中,任何系统之间交互网络等问题彼此不能相连接,也要保证独立的服务是继续运行的。

比如,用户服务直接调司机服务,就算内部网络不连通,用户服务调不通司机服务了,也不能影响用户服务

或 司机服务独立对外提供服务的能力。这个在分布式系统中需要保证的,要不然就失去了分布式的核心意义。

分区容错性是必须保证的,不能说我有N个微服务,一但其中一个出问题,相关连的都不能用。

一致性和可用性需要根据业务进行选择,比如,用户扣钱成功,如果司机账户需要保证强一致性,

哪就会牺牲司机服务的可用性,相当于在处理数据一致的这段时间,司机服务是不能用的,如果

要保证司机系统的可用性,哪一致性就保证不了强一致。

在分布式系统中,大部分系统都是追求可用性,因为比一致性要高,但是一致性你也不能不管吧,

所以,CAP 理论太刚了,在 CAP 理论基础上扩展了 BASE 理论。

五 BASE 理论

为了追求分布式系统中的可用性,对 CAP 理论进行进一步扩展为 BASE 理论。

包含基本可用(Basically Available)、软状态( Soft State)、最终一致( Eventual Consistency)。

BASE 理论是对 CAP 的一致性和可用性进行一个权衡的结果,核心思想就是我们无法做到强一致性,

但每个应该用可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual Consistency)

1、基本可用(Basically Available)

指分布式系统在出现不可预知故障的时候,允许损失部分可用性。

时间上的损失:正常情况下一个查询0.5秒内返回,但是有时候网络延迟,查询结果响应增加到1~2秒。

功能上的损失:购物网站正常情况下都能完成购物,完成每笔消费,但是赶上大促销,消费行为急剧增加,

为了保护购物系统的稳定性,部分消费者引导到降级页面,搞了下限流操作。如果要保证100%人都没问题,

系统可能都挂掉了,一个都用不了。

2、软状态( Soft State)

指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的

数据副本之间进行数据同步的过程存在延时。

3、最终一致( Eventual Consistency)

强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的

本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

比如,用户扣钱可以单独处理,司机入账最后保持这笔钱的最终一致性即可,司机晚看几秒没啥影响,

不需要保持用户扣钱和司机入账的强一致性,要不然会影响司机的可用性。

基于 CAP 和 BASE 理论才能讨论分布式事务,因为没有 100% 都 Ok 的方案,一切都要有个取舍。

六 分布式事务

基于后置事务提交在分布式系统中也处理不了最终一致性问题,下面开始分析目前主流的分布式数据一致性处理方案。

七 两阶段提交协议(2PC)

两阶段提交协议是基于 XA 协议实现的,MySQL, Oracle 等支持XA协议。

同时 Java 的 JTA 接口规范定义了 XA 协议操作 api。

跟后置事务提交逻辑很像,大概实现逻辑时序图。

二阶段协议如何保证数据一致性?

1、首先根据两个连接,通过协调者开启全局事务,下面操作基于全局事务实现,可以用开源工具 atomikos 实现;

2、用户减钱,司机加钱先根据 xid(XA 协议基于 xid 进行两阶段提交)进行事务的预提交,如果预提交失败,直接都回滚;

3、预提交成功后在进行事务的提交,保证数据的一致性;

大家可以看到,在步骤【13】提交失败时,我们对用户账户扣减的钱是无法回滚的,这个时候可以记录日志或尝试提交,

到达一定次数报警人工干预,出现提交失败的可能性很小。

适用场景?

1、通过协调者参与,引入中间件,损耗性能,只能适用于数据规模相对较小的应用场景;

2、因为需要开启全局事务,只能适用在一个系统里面多数据源处理,无法处理服务调用场景一致性;

八 补偿事务(TCC)

平台规模不断增大,以前的一个应用服务抗不住了,现在做了SOA,独立出来用户服务和司机服务,

通过接口调用交互。刚才说的 2PC 处理不了这种应用场景。可以通过 TCC 进行数据一致性操作。

TCC分别是 Try(尝试)、Confirm(确定)、Cancel(取消),每一个接口分三个接口实现。

比如,用户减钱就不能像之前哪样直接在 amount 字段上进行加减,需要多设计一个 frozen(冻结字段),

司机账户也是一样逻辑设计。大概接口逻辑如下:

1、用户账户

源数据:id = 1,amount = 1000,frozen = 0

用户Try接口:update user_account set amount = amount - 100, frozen = frozen + 100 where id = 1; // 执行后 amount = 900,frozen = 100

用户Confirm接口:update user_account set frozen = frozen - 100 where id = 1;// 执行后 amount =900,frozen = 0

用户Cancel接口:update user_account set amount = amount + 100, frozen = frozen - 100 where id = 1;// 执行后 amount = 1000,frozen = 0

2、司机账户

源数据:id = 1,amount = 1000,frozen = 0

司机Try接口:update driver_account set frozen = frozen + 100 where id = 1;// 执行后 amount = 1000,frozen = 100

司机Confirm接口:update driver_account set amount = amount + 100, frozen = frozen - 100 where id = 1;// 执行后 amount = 1100,frozen = 0

司机Cancel接口:update driver_account set frozen =frozen - 100 where id = 1;// 执行后 amount = 1000,frozen = 0

正常情况下:用户 amount = 900,frozen = 0,司机 amount = 1100,frozen = 0,保证数据一致性。

异常情况下:用户 amount = 1000,frozen = 0,司机  amount = 1000,frozen = 0,保证数据一致性。

看上去很完美,解决了数据的强一致性问题,然而当某些接口调用异常时,他可就不完美了,基于下面时序图分析下会出哪些毛病。

TCC 如何保证数据一致性?

1、在步骤【1】调用用户扣钱try接口时出现异常,这个时候调用户的Cancel或不调都可能出问题,因为如果是执行成功,网络超时,

调Cancel是能完美回滚,接口保证数据一致性,但是如果调Try时服务没有调通,但是调了Cancel了,这个时候回滚完用户的amount = 1100,

frozen = -100,尴尬不,怎么规避呢?写业务时需要加上相关数据库乐观锁条件处理下,司机Cancel也是一样道理。

2、在步骤【3】调用司机扣钱try接口时出现异常,调用用户Cancel接口与司机的Cancel接口即可保证数据一致性。

3、在步骤【5】调用用户confirm接口时出现异常,调用用户Cancel接口与司机的Cancel接口即可保证数据一致性。

4、在步骤【7】调用司机confirm接口时出现异常,由于调用用户扣钱已经确定,不能取消操作,会出现数据的一致性,

这个时候一般还是得记录日志、尝试、报警人工干预。

适用场景?

TCC 实现比较复杂,业务扩展困难,代码维护也很头疼,一般用于对强一致性要求高的场景;

免费的ByteTCC开源:https://github.com/liuyangming/ByteTCC

九 本地消息表

本地消息表方案也叫可靠消息最终一致性方案,基于 BASE 原则,因为司机对入账是不需要强一致性的,使用最终一致性方式处理。

当用户入账成功,发送消息,司机消费消息入账,这么处理正常情况下都是 OK的。但是一但用到消息中间件,至少会面临四个重要问题,

消息丢失、顺序性、积压、消费重复问题。这里主要先讨论丢失和消费重复问题。

如上,如果用户扣钱成功,消息发送失败,将会导致司机无法消费入账,数据满足不了最终一致性,

所以,加个本地消息表,先将消息落库,在采用定时任务轮询发送,生产端消息发送成功,可能会说,

用事务消息就不需要使用本地消息表了,但是常用消息中间件Kafka,RabbitMQ,ActiveMQ不支持事务消息,

加个本地消息表,主要是保证消息的发送成功。

实现流程图

实现时序图

本地消息表如何保证数据一致性?

消息大概状态:预发送(prepare),确认发送(confirm),已发送(sent),消费成功(success),取消(cancel)。

1、在步骤【2】如果预发送失败,则回滚,即可保证数据一致性,可能是网络问题,可能已经存入到消息平台DB了,

所以消息服务针对这种消息发送时需要回查业务系统,向业务系统确认是否需要发送到 MQ,需要发送成功改为 sent,

否则重试3次,重试失败报警人工干预,如果不需要发送,改为取消状态(cancel)成为终态。

2、在步骤【5】如果提交失败,事务回滚,即可保证数据一致性,同时将消息状态更新为取消(cancel),如果失败了没关系,

上面说了,发送时候发现是prepare状态的,会进行回查处理的。

3、司机消费成功后需要对中间件进行ack,也需要对消息服务ack状态进行终结,消费端需要处理消费的幂等性,

因为消息可能发送重复,在【6】发送如果已经发送到MQ,因为网路原因等返回失败了,还会再次发送,所以消费端会消费到

两条一样的消费,需要处理消费重复,即幂等性处理。这里面还有一个消息丢失问题,不在发送端丢失,而是丢失在MQ里面,

跟不同MQ中间件有关系,所以,一般消息中间件允许持久化的,会开启消息持久化功能。

根据业务需要做相关数据对账处理,避免数据异常,根据自己实际业务做相关补偿。

幂等性是什么,如何处理?

幂等性是指同一个操作无论请求多少次,其结果都是相同的。

1、业务操作之前在业务方法进行判断如果执行了就不再执行;

2、缓存所有请求和处理结果,已经处理的请求则直接返回结果;

3、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处理;

适用场景?

用这种方案的还是很多的,不是所有公司用的消息中间件都支持事务消息。

十 MQ 事务消息

事务消息方式,是将本地消息表的逻辑集成到 MQ 中,主要目的也是为了消息可靠性发出,

RocketMQ 是支持事务消息的中间件。

如何使用保证数据一致性?

1、事务发起方(消息发送者)首先发送 prepare 消息到 MQ;

2、事务发起方(即消息发送者)在发送 prepare 消息成功后执行本地事务;

3、根据本地事务执行结果发送 commit 或者是 rollback 给 MQ;

如果消息是 rollback,MQ 将删除该 prepare 消息不进行下发。

如果消息是 commit,MQ 将会把这个消息发送给 consumer 端。

正常情况下是没问题的,但是,当 commit 消息失败时,MQ 是无法下发消息的,RocketMQ 为了保证这个消息有一个最终状态,

到底是发还是不发,需要实现 check 方法,RocketMQ 定时轮询调用对应的 check 方法,在里面写自己的逻辑,告诉 MQ 到底发还是不发。

RocketMQ 是支持消息持久化的,发送成功了,几乎可以保证不丢,消费端需要处理消费幂等性。

根据业务需要做相关数据对账处理,避免数据异常,根据自己实际业务做相关补偿。

十一 开源框架

阿里 Seata、华为 ServiceComb Pack、LCN、ShardingProxy、齐牛金融EasyTransaction、ByteTCC、GTS等。

十二 总结

分布式事务使用需要深深结合自身系统业务去选择合适自己的方式,并做好相关数据的补偿任务和接口幂等性等处理。

【daisy-framework】分布式事务技术选型相关推荐

  1. 分布式事务解决方案选型建议

    分布式事务解决方案对比分析 1.一致性,吞吐量,复杂度对比 2PC 3PC TCC 可靠消息 最大努力通知 SAGA 一致性 强一致 强一致 最终一致 最终一致 最终一致 吞吐量 低 低 中 高 高 ...

  2. 分布式定时任务技术选型

    1.目前的定时任务方案 Java中开发大多数使用Spring-Scheduler,只需要在Spring中的bean的对应方法加上@sheduler注解即可完成我们的定时任务,但是光是用这个注解还远远不 ...

  3. 基于MySQL和DynamoDB的强一致性分布式事务实践

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 在单体应用向微服 ...

  4. 「分布式专题」分布式事务 就这?太简单了吧

    目录 基础概念 本地事务 分布式事务 基础理论 CAP理论 BASE理论 分布式事务解决方案 2PC TCC 可靠消息最终一致性 基础概念 本地事务 在计算机系统中,更多的是通过关系型数据库来控制事务 ...

  5. 亚信科技数据库AntDB通过金融分布式事务数据库标准测试

    近日,南京, 中国信息通信研究院云计算与大数据研究所(以下简称"中国信通院云大所")组织专家对亚信科技(股票代码:01675.HK)的数据库产品AISWare AntDB(以下简称 ...

  6. 微服务技术选型【转】

    转自:https://www.cnblogs.com/wangdaijun/p/9322175.html 转: http://www.youmeek.com/microservice/ 后端类开发总结 ...

  7. mysql 事务补偿_TCC补偿性策略_彻底学习数据库事务 seata分布式事务 共享 排它锁 死锁 索引 Spring事务 隔离级别等_MySQL视频-51CTO学院...

    课程总计41课时,从什么是事务讲起,直到分布式事务解决方案,很的0基础基础与提升系列课程.对于难以理解的知识点,全部用画图+实战的方式讲解. 彻底明白事务的四个特性:原子性.一致性.隔离性.持久性,用 ...

  8. 阿里开源分布式事务解决方案 Fescar 全解析

    广为人知的阿里分布式事务解决方案:GTS(Global Transaction Service),已正式推出开源版本,取名为"Fescar",希望帮助业界解决微服务架构下的分布式事 ...

  9. Seata阿里分布式事务中间件(一):Seata的基本介绍

    Fescar 是 阿里巴巴 开源的 分布式事务中间件,以 高效 并且对业务 0 侵入 的方式,解决 微服务 场景下面临的分布式事务问题. 什么是微服务化带来的分布式事务问题? 首先,设想一个传统的单体 ...

最新文章

  1. UVA10391复合词
  2. 【Android】开源图表库MPAndroidChart的学习
  3. Python 技术篇-打开指定文件夹、目录、路径方法,运行指定文件演示
  4. leetcode_two sum()
  5. 微服务设计原则和解决方案
  6. ScrumChina 2008 上海之行
  7. 接口测试用例设计的方法
  8. Java毕设项目电影票网上订票系统计算机(附源码+系统+数据库+LW)
  9. 「经济读物」第一本经济书 罗伯特.墨菲
  10. windows10+Ubuntu双系统卸载旧Ubuntu并重装Ubuntu(绝对安全)
  11. 对前端构建工具的一些理解
  12. Android 7.0图片裁剪问题
  13. 用winrar压缩工具切分文件和合并文件
  14. matlab毕业设计工作日志通用,毕业论文日志100篇通用_毕业论文日志100篇_万能工作日志100篇...
  15. 基于JAVA的KTV交易_Java 基于sshktv预定管理系统
  16. GHM(anchor based)
  17. Servlet 04
  18. 升鲜宝V2.0_生鲜配送行业,对生鲜配送行业的思考及对系统流程开发的反思_升鲜宝生鲜配送系统_15382353715_余东升...
  19. Hello Redis,我有7个问题想请教你!
  20. 夏普MX-M2658N复印机显示请放入载体组件

热门文章

  1. 分享77个NET源码,总有一款适合您
  2. UVa 1252 - Twenty Questions(记忆化搜索,状态压缩dp)
  3. 凤姐 经典语录 雷死人不要命
  4. 写魔兽改键时遇到的问题
  5. Windows下的钩子
  6. 常见拉丁文读法,肯定用得着~
  7. Spring的xml配置文件中tx命名空间
  8. 为字体文件增添自定义图标
  9. 13 分钟内 3 个进球被判越位,让阿根廷“崩溃”的半自动越位技术是什么
  10. 吃透Chisel语言.18.Chisel模块详解(五)——Chisel中使用Verilog模块