[跨数据库、微服务] FreeSql 分布式事务 TCC/Saga 编排重要性
前言
FreeSql 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/Gbase/神通/人大金仓/翰高/Clickhouse/MsAccess Ado.net 数据库,以及 Odbc 的专门实现包。
FreeSql.Cloud 为 FreeSql 提供跨数据库访问,分布式事务TCC、SAGA解决方案,支持 .NET Core 2.1+, .NET Framework 4.0+.
本文主要讲解从跨数据库访问,到分布式事务落地,再升级到微服务服务编排探讨。写下本文更多的成份是带有疑问号,希望有微服务落地经验的朋友指教一下。
TCC 事务特点:
- Try 用于资源冻结/预扣;
- Try 全部环节通过,代表业务一定能完成,进入 Confirm 环节;
- Try 任何环节失败,代表业务失败,进入 Cancel 环节;
- Confirm 失败会进行重试N次,直到交付成功,或者人工干预;
- Cancel 失败会进行重试N次,直到取消成功,或者人工干预;
SAGA 事务特点:
- Commit 用于业务提交;
- Commit 全部环节通过,代表业务交付成功;
- Commit 任何环节失败,代表业务失败,进入 Cancel 环节;
- Cancel 失败会进行重试N次,直到取消成功,或者人工干预;
由于 TCC/Saga 两种流程有相似之处,因此本文主要以 Saga 为例讲解应用代码。本文讲解的落地场景如下:
第一步:去 数据库db1 扣除 user.Point - 10
第二步:去 数据库db2 扣除 goods.Stock - 1
第三步:去 数据库db3 创建订单
第二步库存不足时,整个流程怎么执行?
快速开始
dotnet add package FreeSql.Cloud
or
Install-Package FreeSql.Cloud
public enum DbEnum { db1, db2, db3 }
var fsql = new FreeSqlCloud<DbEnum>("app001"); //提示:泛型可以传入 string
fsql.DistributeTrace = log => Console.WriteLine(log.Split('\n')[0].Trim());
fsql.Register(DbEnum.db1, () => new FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer, @"Data Source=...")
.Build());
fsql.Register(DbEnum.db2, () => new FreeSqlBuilder()
.UseConnectionString(DataType.MySql, @"Data Source=...")
.Build());
fsql.Register(DbEnum.db3, () => new FreeSqlBuilder()
.UseConnectionString(DataType.Oracle, @"Data Source=...")
.Build());
services.AddSingleton<IFreeSql>(fsql);
services.AddSingleton(fsql);
//注入两个类型,稳
FreeSqlCloud 必须定义成单例模式
关于分布式事务
FreeSqlCloud 提供 TCC/SAGA 分布式事务调度、失败重试、持久化重启后重新唤醒事务单元、等管理功能。
// 测试数据
fsql.Use(DbEnum.db1).Insert(new User { Id = 1, Name = "testuser01", Point = 10 }).ExecuteAffrows();
fsql.Use(DbEnum.db2).Insert(new Goods { Id = 1, Title = "testgoods01", Stock = 0 }).ExecuteAffrows();
var orderId = Guid.NewGuid();
await DB.Cloud.StartSaga(orderId.ToString(), "支付购买SAGA事务",
new SagaOptions
{
MaxRetryCount = 10, //重试次数
RetryInterval = TimeSpan.FromSeconds(10) //重试间隔
})
.Then<Saga1>(DbEnum.db1, new SagaBuyState { UserId = 1, Point = 10, GoodsId = 1, OrderId = orderId })
.Then<Saga2>(DbEnum.db2, new SagaBuyState { UserId = 1, Point = 10, GoodsId = 1, OrderId = orderId })
.Then<Saga3>(DbEnum.db3, new SagaBuyState { UserId = 1, Point = 10, GoodsId = 1, OrderId = orderId })
.ExecuteAsync();
由于商品库存不足,测试结果如下:
2022-08-17 05:24:00 【app001】db1 注册成功, 并存储 TCC/SAGA 事务相关数据
2022-08-17 05:24:00 【app001】成功加载历史未完成 TCC 事务 0 个
2022-08-17 05:24:00 【app001】成功加载历史未完成 SAGA 事务 0 个
2022-08-17 05:24:00 【app001】SAGA(85a95966-d5b0-4371-b54b-07d079d9fd78, 支付购买SAGA事务) Created successful, retry count: 10, interval: 10S
2022-08-17 05:24:00 【app001】SAGA(85a95966-d5b0-4371-b54b-07d079d9fd78, 支付购买SAGA事务) Unit1(第1步:数据库db1 扣除用户积分) COMMIT successful
2022-08-17 05:24:00 【app001】数据库使用[Use] db2
2022-08-17 05:24:00 【app001】SAGA(85a95966-d5b0-4371-b54b-07d079d9fd78, 支付购买SAGA事务) Unit2(第2步:数据库db2 扣除库存) COMMIT failed, ready to CANCEL, -ERR 扣除库存失败
2022-08-17 05:24:00 【app001】SAGA(85a95966-d5b0-4371-b54b-07d079d9fd78, 支付购买SAGA事务) Unit1(第1步:数据库db1 扣除用户积分) CANCEL successful
2022-08-17 05:24:00 【app001】SAGA(85a95966-d5b0-4371-b54b-07d079d9fd78, 支付购买SAGA事务) Completed, all units CANCEL successfully
- Commit 用于业务提交;
- Commit 全部环节通过,代表业务交付成功;
- Commit 任何环节失败,代表业务失败,进入 Cancel 环节;
- Cancel 失败会进行重试N次,直到取消成功,或者人工干预;
Saga1、Saga2、Saga3 的实现代码如下:
[Description("第1步:数据库db1 扣除用户积分")]
class Saga1 : SagaUnit<SagaBuyState>
{
public override async Task Commit()
{
var affrows = await Orm.Update<User>().Set(a => a.Point - State.Point)
.Where(a => a.Id == State.UserId && a.Point >= State.Point)
.ExecuteAffrowsAsync();
if (affrows <= 0) throw new Exception("扣除积分失败");
//记录积分变动日志?
}
public override async Task Cancel()
{
await Orm.Update<User>().Set(a => a.Point + State.Point)
.Where(a => a.Id == State.UserId)
.ExecuteAffrowsAsync(); //退还积分
//记录积分变动日志?
}
}
[Description("第2步:数据库db2 扣除库存")]
class Saga2 : SagaUnit<SagaBuyState>
{
public override async Task Commit()
{
var affrows = await Orm.Update<Goods>().Set(a => a.Stock - 1)
.Where(a => a.Id == State.GoodsId && a.Stock >= 1)
.ExecuteAffrowsAsync();
if (affrows <= 0) throw new Exception("扣除库存失败");
}
public override async Task Cancel()
{
await Orm.Update<Goods>().Set(a => a.Stock + 1)
.Where(a => a.Id == State.GoodsId)
.ExecuteAffrowsAsync(); //退还库存
}
}
[Description("第3步:数据库db3 创建订单")]
class Saga3 : SagaUnit<SagaBuyState>
{
public override async Task Commit()
{
await Orm.Insert(new Order { Id = State.OrderId, Status = Order.OrderStatus.Success, CreateTime = DateTime.Now })
.ExecuteAffrowsAsync();
}
public override Task Cancel()
{
return Task.CompletedTask;
}
}
class BuySagaState
{
public int UserId { get; set; }
public int Point { get; set; }
public Guid BuyLogId { get; set; }
public int GoodsId { get; set; }
public Guid OrderId { get; set; }
}
关于微服务
最近几天在整理 FreeSql.Cloud 代码及相关示例,发现 TCC/Saga 事务单元内不是只能 CRUD 操作,它还可以调用远程 webapi 甚至 gRPC 服务。
事务单元内调用远程 webapi,同样可以获取失败重试、持久化等特点。请看以下代码示例:
// HTTP 服务编排??
var orderId = Guid.NewGuid();
await DB.Cloud.StartSaga(orderId.ToString(), "支付购买webapi(saga)",
new SagaOptions
{
MaxRetryCount = 10,
RetryInterval = TimeSpan.FromSeconds(10)
})
.Then<HttpSaga>(default, new HttpUnitState
{
Url = "https://192.168.1.100/saga/UserPoint",
Data = "UserId=1&Point=10&GoodsId=1&OrderId=" + orderId
})
.Then<HttpSaga>(default, new HttpUnitState
{
Url = "https://192.168.1.100/saga/GoodsStock",
Data = "UserId=1&Point=10&GoodsId=1&OrderId=" + orderId
})
.Then<HttpSaga>(default, new HttpUnitState
{
Url = "https://192.168.1.100/saga/OrderNew",
Data = "UserId=1&Point=10&GoodsId=1&OrderId=" + orderId
})
.ExecuteAsync();
class HttpSaga : SagaUnit<HttpUnitState>
{
public override Task Commit()
{
//Console.WriteLine("请求 webapi:" + State.Url + "/Commit" + State.Data);
return Task.CompletedTask;
}
public override Task Cancel()
{
//Console.WriteLine("请求 webapi:" + State.Url + "/Cancel" + State.Data);
return Task.CompletedTask;
}
}
class HttpUnitState
{
public string Url { get; set; }
public string Data { get; set; }
}
2022-08-17 06:11:05 【app001】db1 注册成功, 并存储 TCC/SAGA 事务相关数据
2022-08-17 06:11:05 【app001】成功加载历史未完成 TCC 事务 0 个
2022-08-17 06:11:05 【app001】成功加载历史未完成 SAGA 事务 0 个
2022-08-17 06:11:06 【app001】SAGA(2cad53d3-6d7e-481e-ad9a-15d4773a5397, 支付购买webapi(saga)) Created successful, retry count: 10, interval: 10S
2022-08-17 06:11:06 【app001】SAGA(2cad53d3-6d7e-481e-ad9a-15d4773a5397, 支付购买webapi(saga)) Unit1 COMMIT successful
2022-08-17 06:11:06 【app001】SAGA(2cad53d3-6d7e-481e-ad9a-15d4773a5397, 支付购买webapi(saga)) Unit2 COMMIT successful
2022-08-17 06:11:06 【app001】SAGA(2cad53d3-6d7e-481e-ad9a-15d4773a5397, 支付购买webapi(saga)) Unit3 COMMIT successful
2022-08-17 06:11:06 【app001】SAGA(2cad53d3-6d7e-481e-ad9a-15d4773a5397, 支付购买webapi(saga)) Completed, all units COMMIT successfully
这段代码是突然想出来的,由于没接触过微服务项目,故携带代码及类似的场景在 Natasha 技术大牛群里提出来讨论。
讨论原文:
微服务这些业务编排的,比如支付购买业务,用微服务怎么做。
第一步:去 server1 扣除 user.Point - 10
第二步:去 server2 扣除 goods.Stock - 1
第三步:去 server3 创建订单
第二步扣库存失败,怎么办?
很多人会回复消息队列,业务复杂了,不编排很难维护消息队列的。编排后的代码,让维护者更加直观。
感谢 dongfo 提供的参考方案:https://dtm.pub/app/order.html
DTM 解决方案也是使用的 saga 业务流程,看来 FreeSql.Cloud 没有走偏,做跨数据库事务可行,用来做 webapi 编排也不错。
我仍然好奇,很多 .net 微服务文章介绍 服务编排 的少之又少,希望有微服务落地经验的朋友多多指教。
问:是不是缺少了条件链路呢?A条件走A,B条件走B。
答:这种应该整个判断,在分支做条件会复杂很多,直观性会变差。
if (场景A)
StartSaga(...) 流程1
if (场景B)
StartSaga(...) 流程2
结束语
FreeSql 支持很多数据库,功能强大、稳定性好,有好的想法可以一起讨论。
希望这篇文章能帮助大家轻松理解并熟练掌握 TCC/Saga 事务,为企业的项目研发贡献力量。
开源地址:https://github.com/dotnetcore/FreeSql
作者是什么人?
作者是一个入行 18年的老批,他目前写的.net 开源项目有:
| 开源项目 | 描述 | 开源地址 | 开源协议 |
|---|---|---|---|
| ImCore | 架构最简单,扩展性最强的聊天系统架构 | https://github.com/2881099/im | 最宽松的 MIT 协议,可商用 |
| FreeRedis | 最简单的 RediscClient | https://github.com/2881099/FreeRedis | 最宽松的 MIT 协议,可商用 |
| csredis | https://github.com/2881099/csredis | 最宽松的 MIT 协议,可商用 | |
| FightLandlord | 斗地主单机或网络版 | https://github.com/2881099/FightLandlord | 最宽松的 MIT 协议,学习用途 |
| FreeScheduler | 定时任务 | https://github.com/2881099/FreeScheduler | 最宽松的 MIT 协议,可商用 |
| IdleBus | 空闲容器 | https://github.com/2881099/IdleBus | 最宽松的 MIT 协议,可商用 |
| FreeSql | 国产最好用的 ORM | https://github.com/dotnetcore/FreeSql | 最宽松的 MIT 协议,可商用 |
| FreeSql.Cloud | 分布式事务tcc/saga | https://github.com/2881099/FreeSql.Cloud | 最宽松的 MIT 协议,可商用 |
| FreeSql.AdminLTE | 低代码后台管理项目生成 | https://github.com/2881099/FreeSql.AdminLTE | 最宽松的 MIT 协议,可商用 |
| FreeSql.DynamicProxy | 动态代理 | https://github.com/2881099/FreeSql.DynamicProxy | 最宽松的 MIT 协议,学习用途 |
需要的请拿走,这些都是最近几年的开源作品,以前更早写的就不发了。
QQ群:4336577(已满)、8578575(在线)、52508226(在线)
[跨数据库、微服务] FreeSql 分布式事务 TCC/Saga 编排重要性的更多相关文章
- DBPack 赋能 python 微服务协调分布式事务
作者:朱晗 中国电子云 什么是分布式事务 事务处理几乎在每一个信息系统中都会涉及,它存在的意义是为了保证系统数据符合期望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性. 按照数据库的经典理论 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式
在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...
- SpringCloud微服务架构分布式组件如何共享session对象
一.简单做一个背景说明1.为说明问题,本文简单微服务架构示例如下 2.组件说明分布式架构,每个组件都是集群或者主备.具体说明如下:zuul service:网关,API调用都走zuul service ...
- 微服务之分布式跟踪系统(springboot+zipkin+mysql)
通过上一节<微服务之分布式跟踪系统(springboot+zipkin)>我们简单熟悉了zipkin的使用,但是收集的数据都保存在内存中重启后数据丢失,不过zipkin的Storage除了 ...
- Hmily:高性能异步分布式事务TCC框架
Hmily框架特性 无缝集成Spring,Spring boot start. 无缝集成Dubbo,SpringCloud,Motan等rpc框架. 多种事务日志的存储方式(redis,mongdb, ...
- 最近整理出了有关大数据,微服务,分布式,Java,Python,Web前端,产品运营,交互等1.7G的学习资料,有视频教程,源码,课件,工具,面试题等等。这里将珍藏多年的资源免费分享给各位小伙伴们
大数据,微服务,分布式,Java,Python,Web前端,产品运营,交互 领取方式在篇尾!!! 基础篇.互联网架构,高级程序员必备视频,Linux系统.JVM.大型分布式电商项目实战视频...... ...
- 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾
https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...
- 微服务痛点-基于Dubbo + Seata的分布式事务(TCC模式)
前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...
- .Net Core with 微服务 - 分布式事务 - TCC
上一次我们讲解了分布式事务的 2PC.3PC .那么这次我们来理一下 TCC 事务.本次还是讲解 TCC 的原理跟 .NET 其实没有关系. TCC Try 准备阶段,尝试执行业务 Confirm 完 ...
随机推荐
- 【lora无线数传通信模块】亿佰特E22串口模块用于物联网地震预警传感通信方案
物联网地震预警项目介绍: 地震,俗称地动.它像平常的刮风下雨一样,是一种常见的自然现象,是地壳运动的一种表现,即地球内部缓慢积累的能量突然释放而引起的地球表层的振动.据统计,5级以上地震就能够造成破坏 ...
- 2 万字 + 20张图| 细说 Redis 九种数据类型和应用场景
作者:小林coding 计算机八股文网(操作系统.计算机网络.计算机组成.MySQL.Redis):https://xiaolincoding.com 大家好,我是小林. 我们都知道 Redis 提供 ...
- BUUCTF-LSB
LSB 看到这个题目应该是LSB隐写,StegSolve打开,在红绿蓝0号上发现图片信息 然后在Analyse选择data extract Save bin保存图片即可 得到的是个二维码,解码即可.
- Leetcode--Last Stone Weight II
Last Stone Weight II 欢迎关注H寻梦人公众号 You are given an array of integers stones where stones[i] is the we ...
- 一图读懂k8s informer client-go
概述 为什么要有k8s informer 我们都知道可以使用k8s的Clientset来获取所有的原生资源对象,那么怎么能持续的获取集群的所有资源对象,或监听集群的资源对象数据的变化呢?这里不需要轮询 ...
- 「BUAA OO Unit 4 HW16」第四单元总结与课程回顾
「BUAA OO Unit 4 HW16」第四单元总结与课程回顾 目录 「BUAA OO Unit 4 HW16」第四单元总结与课程回顾 Part 0 第四单元作业架构设计 架构设计概要 AppRun ...
- NC50999 表达式计算4
NC50999 表达式计算4 题目 题目描述 给出一个表达式,其中运算符仅包含+,-,*,/,^(加 减 乘 整除 乘方)要求求出表达式的最终值 数据可能会出现括号情况,还有可能出现多余括号情况 数据 ...
- 注册器机制Registry
在众多深度学习开源库的代码中经常出现Registry代码块,例如OpenMMlab,facebookresearch和BasicSR中都使用了注册器机制.这块的代码经常会让新使用这些库的初学者感到一头 ...
- Oracle创建用户和表空间
一.概述 1.数据库实际管理中,不同业务系统需要使用'不同的用户'进行管理维护和使用,这样做把业务数据和系统数据独立分开管理,利于数据库系统管理: 2.在数据库中创建业务系统用户时候,建议为用户创建指 ...
- 一款 IDEA 插件帮你优雅转化 DTO、VO、BO、PO、DO
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.承认你优秀很难 很多码农,把路走窄了 捧一个,喷一个,很多码农都不会多一点思路看问题,总是 ...