社区编辑申请
注册/登录
聊聊微服务中的事务管理
开发 架构
在单体架构中,通常是一套程序对应一个数据库,事务基于数据库本身的能力,如果你在 .NET Core 中使用 dapper 或 sqlsugar ,可以很容易进行事务的处理。

几乎所有的信息管理系统都会涉及到事务,事务的目的是为了保证数据的一致性,这里说的一致性是数据库状态的一致性。

说到数据库状态的一致性,相信大家都会想到 ACID :

  • 原子性(Atomic):在一个事件的多个数据库操作中,要么同时成功,要么同时失败,例如:转账业务。
  • 隔离性(Isolation):不同的业务之间处理数据相互独立,互不影响。
  • 持久性(Durability):正常提交的数据能够被持久化,不丢失数据,比如 mysql 天然就能持久化,redis 、 rabbitmq 也能通过设置进行持久化。
  • 一致性(Consistency):最终的数据正确,所以说是通过 AID 这些手段来保证了 C 。

在单体架构中,通常是一套程序对应一个数据库,事务基于数据库本身的能力,如果你在 .NET Core 中使用 dapper 或 sqlsugar ,可以很容易进行事务的处理,可以参考下面文档:

https://dapper-tutorial.net/transaction 。

https://www.donet5.com/Home/Doc?typeId=1183。

但是,在微服务架构,分布式的场景中,事务的处理就会变得复杂,会存在多个节点,多个节点的同步、可用性等都是需要考虑的问题,在分布式中有一个著名的 CAP 理论:

  • C:数据一致性(Consisitency):分布式中存在多个节点,对某个指定的客户端来说,从任一节点读取的数据保证获取到的是最新写入的数据。
  • A:可用性(Acailability),非故障节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
  • P:分区容错性(Partition Tolerance),节点之间的数据传递是基于网络的,由于网络本身不是 100% 可靠,极端情况下会出现网络不可用的情况,进而将网络两端的节点分隔开来,这就是所谓的「网络分区」现象。在出现网络分区时,两部分的数据是不一致的,如果要保证数据的一致性,就必须要让没有及时同步数据的节点变为不可用,这就牺牲了可用性,否则就会牺牲一致性,所以在 P 一定存在的情况下,需要在 C 和 A 中间做取舍。

我们在 CAP、ACID 中讨论的一致性称为「强一致性」(Strong Consistency),而把牺牲了 C 的 AP 系统,但又要保证最终的结果是一致的,称为「弱一致性」,也叫最终一致性。最终一致性的概念由 eBay 的系统架构师丹 · 普利切特(Dan Pritchett)在 2008 年发表于 ACM 的论文「Base: An Acid Alternative」中提出的。

本文主要说下保证一致性的几种方式:TCC、SAGA 和消息队列。

TCC

TCC 是 Try-Confirm-Cancel 的缩写,表示将整个过程分为了三个阶段:

  • Try:一个请求涉及到多个服务,多个服务会同时进行 Try,这个阶段为尝试执行阶段,在这个阶段中会进行数据的校验、检查,保障一致性,并准备资源,都成功会进入到 Confirm 阶段。
  • Confirm:确认执行阶段,不进行任何业务检查,多个服务的 Try 都执行成功了,多个服务都进入到 Confirm 阶段,在这个阶段直接使用 Try 阶段准备的资源来完成业务处理。注意,Confirm 阶段可能会重复执行,因此需要满足幂等性。
  • Cancel:如果在 Try 阶段有一个服务没有成功,那么所有的服务都进入到 Cancel 阶段,在该阶段,释放 Try 阶段预留的业务资源。注意,Cancel 阶段也可能会重复执行,因此也需要满足幂等性。

在 .NET Core 中可以参考:

https://github.com/simpleway2016/JMS。

在 Java 中可以使用 seata:

https://github.com/seata/seata https://seata.io/zh-cn/。

因为在 TCC 中的第一步 Try 需要预留资源,进行检查和校验,但在某些场景下,资源不是我们所能控制的,比如支付中,余额是银行管理的,我们通常没有权限。所以这时就不太适合 TCC ,可以考虑用 SAGA 来代替 TCC。

SAGA

SAGA 起源于 1987 年普林斯顿大学的赫克托 · 加西亚 · 莫利纳(Hector Garcia Molina)和肯尼斯 · 麦克米伦(Kenneth Salem)在 ACM 发表的一篇论文《SAGAS》。

SAGA 和 TCC 最大的区别是基于数据补偿机制来代替回滚。一个 SAGA 表示处理多个服务中数据的一系列操作,由一连串的本地事务组成,每个独立的本地事务中还是能够使用 ACID 。

SATA 由两部分组成:

  • 将一个大的事务拆分成的若干个小的事务,比如一个大的事务 T ,拆分成 T1、T2、T3。
  • 每一个子事务有对应的补偿动作,例如对应上面的 T1、T2、T3 有 C1、C2、C3 的补偿动作。

在 ACID 中如果出现异常,可以很容易进行回滚,但 SAGA 没办法自己回滚,必须依赖补偿动作来进行回滚。

如果 T1、T2、T3 都提交成功了,整个事务 T 就提交成功,如果执行 T2 时出现异常,这时有两种方式进行处理:

正向(不断重试):不断对 T2 进行重试操作,直到成功(不排除人工干预),等 T2 重试成功后,继续执行后面的 T3。

反向(补偿):T2 出现异常时,执行对应的补偿 C2,C2 必须执行成功(不排除人工),然后执行 T1 对应的补偿动作 C1 。

在上面提到的 seata 中也同样可以支持 SAGA 模式。

除了 seata ,还有一个用 go 语言写的 DTM 分布式事务框架也不错:

https://dtm.pub/ https://github.com/dtm-labs/dtm。

重要的是,DTM 支持 C# 客户端:

https://github.com/dtm-labs/dtmcli-csharp。

消息队列

消息队列相信大家都不陌生,我们零代码产品中调用外部接口的组件,会被用在一些复杂的业务逻辑编排中,对外部接口的调用就是使用消息队列,RabbitMQ 的延时队列加上死信队列可以来进行重试的操作,来保证数据的最终一致。

还有另一种方式就是使用事务消息表,比如有这样一个场景,在系统列表中删除一条流程数据,这时需要做:

1、列表服务中对数据进行删除;。

2、文件服务对这条数据相关的附件进行删除。

3、流程服务对该业务数据的所有流程信息进行删除。

具体的步骤如下:

1、列表服务删除数据成功后,在数据库中创建一张事务消息表,该表中记录事务 ID、数据删除成功的状态、业务数据 ID、附件待删除的状态、流程信息待删除的状态等。

2、列表服务删除数据成功后,发送消息分别进行附件删除处理和流程信息删除处理。

3、消息被正确处理后,修改事务消息表的状态。

4、创建一个单独的消息服务程序,轮询扫描事务消息表,如果发现状态没有变成已完成,就重新发送一个新的消息,这样附件删除和流程信息删除就会进行多次执行,这也要求这些操作必须是幂等的。

RabbitMQ 本身不支持分布式事务,不过有一些消息中间件是支持的,例如:RocketMQ,原生就支持分布式事务操作,可以更方便进行事务处理。

本文是一些理论的梳理,要想更彻底地掌握,可以选择一个框架,找几个场景,写写代码演练一下。

责任编辑:姜华 来源: 今日头条
相关推荐

2021-09-28 09:43:11

2017-09-19 09:36:24

2020-06-08 18:41:07

Kafka微服务Web

2016-09-22 15:36:15

微服务架构

2022-07-06 12:03:55

事务解决方案

2017-04-19 08:58:54

微服务领域事件事件

2017-07-10 10:51:21

微服务领域事件Microservic

2022-04-26 21:49:55

2020-04-14 10:06:20

2021-10-29 07:49:22

2009-06-30 16:57:42

Spring事务管理

2009-06-17 14:43:47

2020-11-24 11:30:51

SpringJava代码

同话题下的热门内容

该不该将单体架构迁移到微服务?软件架构中的跨层缓存微服务架构的数据设计模式终于有人把灰度发布架构设计讲明白了聊聊分布式定时任务框架选型揭秘短视频推荐系统的技术架构及四大模块为什么策略梯度法在协作性MARL中如此高效?微服务架构的通信设计模式

编辑推荐

终于有人把Elasticsearch原理讲透了!花了一个星期,我终于把RPC框架整明白了!拜托!面试不要再问我Spring Cloud底层原理陌陌基于K8s和Docker容器管理平台的架构实践收藏 | 第一次有人把“分布式事务”讲的这么简单明了
我收藏的内容
点赞
收藏

51CTO技术栈公众号