❝ 分布式事务系列文章
DTM 简介
前面章节提及的MassTransit
、dotnetcore/CAP
都提供了分布式事务的处理能力,但也仅局限于Saga和本地消息表模式的实现。那有没有一个独立的分布式事务解决方案,涵盖多种分布式事务处理模式,如Saga
、TCC
、XA
模式等。有,目前业界主要有两种开源方案,其一是阿里开源的Seata
,另一个就是DTM
。其中Seata
仅支持Java、Go和Python语言,因此不在.NET 的选择范围。DTM
则通过提供简单易用的HTTP和gRPC接口,屏蔽了语言的无关性,因此支持任何开发语言接入,目前提供了Go、Python、NodeJs、Ruby、Java和C#等语言的SDK。DTM,全称Distributed Transaction Manager,是一个分布式事务管理器,解决跨数据库、跨服务、跨语言更新数据的一致性问题。它提供了Saga、TCC、 XA和二阶段消息模式以满足不同应用场景的需求,同时其首创的「子事务屏障」技术可以有效解决幂等、悬挂和空补偿等异常问题。
DTM 事务处理过程及架构
那DTM是如何处理分布式事务的呢?以一个经典的跨行转账业务为例来看下事务处理过程。对于跨行转账业务而言,很显然是跨库跨服务的应用场景,不能简单通过本地事务解决,可以使用Saga模式,以下是基于DTM提供的Saga事务模式成功转账的的时序图:

从以上时序图可以看出,DTM整个全局事务分为如下几步:
用户定义好全局事务所有的事务分支(全局事务的组成部分称为事务分支),然后提交给DTM,DTM持久化全局事务信息后,立即返回
DTM取出第一个事务分支,这里是TransOut,调用该服务并成功返回
DTM取出第二个事务分支,这里是TransIn,调用该服务并成功返回
DTM已完成所有的事务分支,将全局事务的状态修改为已完成
基于以上这个时序图的基础上,再来看下DTM的架构:

整个DTM架构中,一共有三个角色,分别承担了不同的职责:
RM-资源管理器:RM是一个应用服务,通常连接到独立的数据库,负责处理全局事务中的本地事务,执行相关数据的修改、提交、回滚、补偿等操作。例如在前面的这个Saga事务时序图中,步骤2、3中被调用的TransIn和TransOut方法所在的服务都是RM。
AP-应用程序:AP是一个应用服务,负责全局事务的编排,他会注册全局事务,注册子事务,调用RM接口。例如在前面的这个SAGA事务中,发起步骤1的是AP,它编排了一个包含TransOut、TransIn的全局事务,然后提交给TM
TM-事务管理器:TM就是DTM服务,负责全局事务的管理,作为一个独立的服务而存在。每个全局事务都注册到TM,每个事务分支也注册到TM。TM会协调所有的RM来执行不同的事务分支,并根据执行结果决定是否提交或回滚事务。例如在前面的Saga事务时序图中,TM在步骤2、3中调用了各个RM,在步骤4中,完成这个全局事务。
总体而言,AP-应用程序充当全局事务编排器的角色通过DTM提供的开箱即用的SDK进行全局事务和子事务的注册。TM-事务管理器接收到注册的全局事务和子事务后,负责调用RM-资源管理器来执行对应的事务分支,TM-事务管理器根据事务分支的执行结果决定是否提及或回滚事务。
快速上手
百闻不如一见,接下来就来实际上手体验下如何基于DTM来实际应用Saga进行分布式跨行转账事务的处理。
创建示例项目
接下来就来创建一个示例项目:
使用
dotnet new webapi -n DtmDemo.Webapi
创建示例项目。添加Nuget包:
Dtmcli
和Pomelo.EntityFrameworkCore.MySql
。添加DTM配置项:
{
"dtm": {
"DtmUrl": "http://localhost:36789",
"DtmTimeout": 10000,
"BranchTimeout": 10000,
"DBType": "mysql",
"BarrierTableName": "dtm_barrier.barrier",
}
}
定义银行账户
BankAccount
实体类:
namespace DtmDemo.WebApi.Models
{
public class BankAccount
{
public int Id { get; set; }
public decimal Balance { get; set; }
}
}
定义
DtmDemoWebApiContext
数据库上下文:
using Microsoft.EntityFrameworkCore;
namespace DtmDemo.WebApi.Data
{
public class DtmDemoWebApiContext : DbContext
{
public DtmDemoWebApiContext (DbContextOptions<DtmDemoWebApiContext> options)
: base(options)
{
}
public DbSet<DtmDemo.WebApi.Models.BankAccount> BankAccount { get; set; } = default!;
}
}
注册DbContext 和DTM服务:
using Microsoft.EntityFrameworkCore;
using DtmDemo.WebApi.Data;
using Dtmcli;
var builder = WebApplication.CreateBuilder(args);
var connectionStr = builder.Configuration.GetConnectionString("DtmDemoWebApiContext");
// 注册DbContext
builder.Services.AddDbContext<DtmDemoWebApiContext>(options =>
{
options.UseMySql(connectionStr, ServerVersion.AutoDetect(connectionStr));
});
// 注册DTM
builder.Services.AddDtmcli(builder.Configuration, "dtm");
执行
dotnet ef migrations add 'Initial'
创建迁移。为便于初始化演示数据,定义
BankAccountController
如下,其中PostBankAccount
接口添加了await _context.Database.MigrateAsync();
用于自动应用迁移。
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using DtmDemo.WebApi.Data;
using DtmDemo.WebApi.Models;
using Dtmcli;
namespace DtmDemo.WebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BankAccountsController : ControllerBase
{
private r