.net core 微服务之 分布式事务

概念

什么是事务

事务是由一组操作组成的一个工作单元。

事务特性

原子性:事务内部的一组操作要么同时成功,要么同时失败

隔离性:不同事务之间是互相不影响的

一致性:事务内部一组操作,各自操作产生的结果数据,要能够保证都是预期的状态

持久性:事务内部一组操作,各个操作产生的数据要能够持久的效应

什么是分布式事务

分布式事务就是一组服务操作的集合

例如:在分布式系统或者微服务系统内,完成一个任何,需要涉及到多个服务来共同完成,这一组服务操作组成的集合,就是分布式事务

分布式事务类型

1.  不同服务不同数据库

​2.  不同服务相同数据库

3.  相同服务不同数据库

 

 

 

 

事务分类和分布式事务演化

刚性事务

分为两阶段提交和三阶段提交

刚性事务适用于-----相同服务不同数据库-----的场景

刚性事务---两阶段

事务参与者: 所有需要操作到的服务都是事务参与者

事务协调者: 统一协调参与者的事务

 

第一阶段: 准备阶段  prepare : 事务协调者向所有事务参与者询问是否准备好,并且参与者准备好之后返回yes

第二阶段: 提交阶段  commit :事务协调者向所有事务参与者提交,并且参与者提交成功之后返回ack

 

 

 

刚性事务---三阶段

在两阶段的基础上在最前面新增了一个确认阶段

第一阶段:确认阶段 canCommit  :事务协调者向所有事务参与者确认服务是否正常,并且参与者确认好之后返回yes

第二阶段: 准备阶段  preCommit : 事务协调者向所有事务参与者询问是否准备好,事务参与者写提交日志,并且参与者准备好之后返回ack

第三阶段: 提交阶段  doCommit :事务协调者向所有事务参与者提交,并且参与者提交成功之后返回havaCommit

 

 

缺点:

1. 同步阻塞: 如果其中一个阶段其中一个服务出现问题,会导致其他服务阻塞,所以性能低

2. 数据不一致: 如果提交阶段其中一个服务提交失败,未能返回ack ,那么就会造成数据的不一致

3. 单点故障: 如果事务协调者出现异常,会造成所有的服务阻塞

虽然会有这么多缺点,但是都是在微服务之间异常或者通信异常导致的,同一个服务就不会存在这个问题,所以

刚性事务适用于-----相同服务不同数据库-----的场景

 

柔性事务

就是不完全遵守事务4特性的分布式事务-----主要体现在一致性(不完全一直,最终一致性)

基于CAP理论以及BASE理论

Base理论核心思想 :理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性

可查询操作:服务操作具有全局唯一标识,操作唯一确定的时间

幂等操作:重复调用多次产生的业务结果与调用一次产生的结果相同,一是通过业务操作实现幂等性,二是系统缓存所有请求与处理结果,最后是检测到重复请求之后,自动返回之前的处理结果。

柔性事务可以分为

1. 同步事务(http,rpc) :Tcc分布式事务  和 Saga分布式事务

2. 异步事务(消息队列MQ)

 

柔性事务之TCC

操作数据库会产生三个数据状态:1、未确认状态,2、确认状态,3、取消未确认状态

Tcc分为子事务和全局事务

子事务:每个要操作的服务都是一个子服务, 分为三个阶段:

1、Try阶段  : 所有要操作的服务中都要先执行一个try阶段,把需要添加的数据进行预添加,此时会将改数据标记为未确认的状态

2、Confirm阶段 :所有子服务的try阶段成功之后,执行Confirm阶段,把需要添加的数据进行添加,此时会将改数据标记为确认状态

3、Cancel阶段:如果在第二阶段出现某个子服务异常,会通知其他服务进行回滚,将第一步Try阶段添加的预添加数据进行删除

全局事务:多个子事务的集合, 操作到的第一个服务就会产生一个全局事务,每次同子服务交互都会带上全局服务的ID进行关联

 

 

 

优点

1.解决了跨服务的业务操作原子性问题,例如组合支付,订单减库存等场景非常实用
2.TCC的本质原理是把数据库的二阶段提交上升到微服务来实现,从而避免了数据库2阶段中锁冲突的长事务低性能风险。
3.TCC异步高性能,它采用了try先检查,然后异步实现confirm,真正提交的是在confirm方法中。

缺点

1.对微服务的侵入性强,微服务的每个事务都必须实现try,confirm,cancel等3个方法,开发成本高,今后维护改造的成本也高。
2.为了达到事务的一致性要求,try,confirm、cancel接口必须实现等幂性操作。
(定时器+重试)
3.由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个TCC事务时间拉长,建议采用redis的方式来记录事务日志。

TCC每个子事务会自己执行,不会造成阻塞,不会造成性能消耗过大,就算其中一个子事务造成了异常,会产生一个重试机制不停的重试,从而达到最终一致性

所以TCC适用于微服务-----(  不同服务不同数据库、不同服务相同数据库)-----的场景

 

柔性事务之Saga

子事务:每个要操作的服务都是一个子服务, 分为两个阶段:

第一个阶段(Ti): 直接执行业务阶段, 直接像数据库中添加数据,添加成功后向协调器返回 成功

第二个阶段(Ci): 直接取消阶段,第一个执行阶段所有服务都执行成功了就不会执行第二个取消阶段,当第一个阶段其中某个服务失败了就会执行子事务中的Ci取消阶段,然后向协调器发送命令,协调器向其他执行成功的子服务也发送命令

 

 

 

优点

1、避免服务之间的循环依赖,因为saga协调器会调用saga参与者,但参与者不会调用协调器

2、集中分布式事务编排

3、降低参与者的复杂性

4、回滚更容易管理

Saga模式的一大优势是它支持长事务。因为每个微服务仅关注其自己的本地原子事务,所以如果微服务运行很长时间,则不会阻止其他微服务。这也允许事务继续等待用户输入。此外,由于所有本地事务都是并行发生的,因此任何对象都没有锁定。

缺点

协调器集中太多逻辑的风险

Saga模式很难调试,特别是涉及许多微服务时。此外,如果系统变得复杂,事件消息可能变得难以维护。Saga模式的另一个缺点是它没有读取隔离。例如,客户可以看到正在创建的订单,但在下一秒,订单将因补偿交易而被删除

Saga结合了刚性事务和TCC一些优势,但是相对于TCC没有那么复杂,相对于刚性事务中事务协调器做了集群, 每个子事务会自己执行,不会造成阻塞,不会造成性能消耗过大,就算其中一个子事务造成了异常,会产生一个重试机制不停的重试,从而达到最终一致性

所以TCC适用于微服务-----(  不同服务不同数据库、不同服务相同数据库)-----的场景

 

使用saga的 ServiceComb Pack 框架构建微服务

Saga Pack 架构是由alpha和omega组成,其中:alpha充当协调者的角色,主要负责对事务进行管理和协调。

服务端omega是微服务中内嵌的一个agent,负责对网络请求进行拦截并。客户端向alpha上报事务事件。saga数据库,存储事务参与者的事务数据(mysql,postsql)

 

 

 环境搭建

搭建 Alpha 服务端环境

1. 安装  Java Jdk 

2. 下载 ServiceComb Pack , 下载后解压

3.  安装 Mysql 或者  PostgreSQL 数据库。 由于ServiceComb Pack 只支持两种数据库,我这里就用的mysql 

4. 官网下载 mysql 的数据库Java连接驱动  ,我这里用的是 mysql-connector-java-8.0.15.jar

5. 在解压好的 ServiceComb Pack 根目录下面创建一个 plugins 的文件夹,文件夹中将第四步的驱动放在这个文件夹中

 

 

6. 在mysql 中创建一个名为 saga 的数据库, 在当前目录使用CMD ,然后运行命令,注意修改数据库地址信息和账号密码

java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username={账号}" -D"spring.datasource.password={密码}" -jar alpha-server-0.5.0-exec.jar

如图,运行成功,表示 alpha 服务端搭建成功了,数据库也会生成相关表结构

 

 

 

 可以看到生成的表不仅有Saga ,还有 Tcc, 说明ServiceComb Pack 同时还兼容Tcc协议,这里我们用不到Tcc,直接忽略Tcc的表 

搭建 Omega 环境

由于Nuget源中没有引入Omega,所以我们只有下载源码之后,把源码引入我们的文件当中。源码中还是测试项目示例,可以看看使用

   Github 上下载  Omega c# 源码

 

分布式事务示例

简单的使用分布式事务

1.  在我们 项目中引入源码中Src中的项目

2. 在我们需要用到分布式事务项目中引入两个  Servicecomb.Saga.Omega.Core  和   Servicecomb.Saga.Omega.AspNetCore  

 

 

 

3. 注入服务

services.AddOmegaCore(option =>
            {
                option.GrpcServerAddress = "localhost:8080"; // 1、协调中心地址
                option.InstanceId = $"ConsulApi-1";// 2、服务实例Id
                option.ServiceName = $"ConsulApi";// 3、服务名称
            });

4. 分布式事务开始的接口方法上面打上特性  [SagaStart] 。  我这里懒没有用多个服务,就用的本身这个consulapi运行了多个不同端口的实例模拟多个服务

[SagaStart]
        [HttpGet("AddUser")]
        public async Task<IActionResult> AddUser()
        {
            //获取当前端口,根据端口号的不同进行不同的业务
            var localPort = Request.HttpContext.Connection.LocalPort;
            if (localPort == 5001)
            {
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri("http://localhost:5002");
                await client.GetAsync("/api/User/AddUser");

                HttpClient client2 = new HttpClient();
                client2.BaseAddress = new Uri("http://localhost:5003");
                await client2.GetAsync("/api/User/AddUser");
                return Ok("添加成功");
            }
            else if (localPort == 5002)
            {
                _userService.Create(new UserInfo()
                {
                    Name = "Darcy",
                    Age = 18
                });
            }
            else if (localPort == 5003)
            {
                _cityService.Create(new City()
                {
                    CityName = "成都市"
                });
            }

            return Ok("添加失败");
        }

5. 在每个操作数据库的方法上面打上 特性  [Compensable(nameof(补偿方法名称))]  。  注意这里有个坑,补偿方法一定不要用public ,否则会报未将对象引用到实例

private string connStr = "server=localhost;port=3306;user=root;password=hua3182486;database=fcbsaga;SslMode=none;";
        [Compensable(nameof(Delete))]
        public void Create(UserInfo model)
        {
            using (var conn=new MySqlConnection(connStr))
            {
                conn.Execute($"insert into UserInfo(name, sex) values(@name,@age)",model);
            }
        }
        void Delete(UserInfo model)
        {
            using (var conn = new MySqlConnection(connStr))
            {
                conn.Execute($"delete from  UserInfo where id ='{model.Id}'");
            }
        }

 

6.  添加程序集信息文件  AssemblyInfo.cs

using Servicecomb.Saga.Omega.Abstractions.Transaction;


[module: SagaStart]
[module: Compensable]

 

7. 重点: Nuget 引入 MethodDecorator.Fody ,然后重新生成项目,会生成 FodyWeavers.xml 文件 。 fody会在生成IL时将一些代码自动生成进去,这里目的是为了在执行方法前后去执行  SagaStart  和  Compensable ,方便协调器监控各个子事务,去判断是否执行补偿机制。(官方也没有看到这个操作,花了两天时间踩坑,羊了个羊)

Nuget:  MethodDecorator.Fody

 

运行之后,可以看到数据库里面添加了几条记录,从数据中可以看出,在SagaStart开始时会生成一个全局事务ID,然后会把全局事务ID传给 Compensable 中,Compensable也会生成一个子事务ID,和全局事务ID关联起来,这里也就是执行了saga第一个阶段TI ,如果子事务中出现了异常,就会通知全局事务ID,从而去触发第二个补偿阶段。

 

posted @ 2023-02-15 16:26  Joni是只狗  阅读(2112)  评论(1编辑  收藏  举报