一、概述

二阶段消息是DTM新提出的,可以完美代替现有的事务消息和本地消息表架构。无论从复杂度、性能、便利性还是代码量都是完胜现有的方案。

相比现有的消息架构借助于各种消息中间件比如RocketMQ等,DTM自己实现了无需额外的学习成本。它能够保证本地事务的提交和全局事务提交是“原子的”,适合解决不需要回滚的分布式事务场景

二阶段消息保证提交的原子性和如何保证业务成功执行如下时序图:

二阶段消息主要是指PrepareSubmit两个阶段,主程序向DTM服务发送Prepare消息,成功后执行本地事务,完成本地事务后发送Submit消息至DTM服务,之后DTM会调用分支事件执行其他服务,最后完成全局事务。

当发送了Prepare但是Submit没有提交的话,会进行回调请求来确认消息的情况,具体工作过程如下:

1、在处理本地事务时,会将gid插入到barrier表中,同时带上插入原因为committed。该表有一个唯一索引,主要字段为gid

2、当进行回查时,二阶段消息的操作不是直接查gid是否存在,而是再insert ignore一条带有相同gid的数据,同时带上插入原因为rollbacked。此时如果表中如果已有gid的记录,那么新的插入操作就会被ignore,否则数据会被插入。

3、然后再用gid查询表中的记录,如果查到记录的reasoncommitted,那么说明本地事务已提交;如果查到记录的reasonrollbacked,那么说明本地事务已回滚。

二、安装DTM

我使用二进制包下载安装地址,我是Window环境所以下载后解压,点击dtm.exe进行运行即可,如下启动成功

启动成功后可以访问http://localhost:36789,进入管理后台

三、创建DTM所需的表

我们需要创建一个表处理消息的回查,表里保存全局事务ID,具体作用在后续说明,我这里用的SqlServer数据库,所以执行如下:

CREATE TABLE [dbo].[barrier]
(
[id] bigint NOT NULL IDENTITY(1,1) PRIMARY KEY,
[trans_type] varchar(45) NOT NULL DEFAULT(''),
[gid] varchar(128) NOT NULL DEFAULT(''),
[branch_id] varchar(128) NOT NULL DEFAULT(''),
[op] varchar(45) NOT NULL DEFAULT(''),
[barrier_id] varchar(45) NOT NULL DEFAULT(''),
[reason] varchar(45) NOT NULL DEFAULT(''),
[create_time] datetime NOT NULL DEFAULT(getdate()) ,
[update_time] datetime NOT NULL DEFAULT(getdate())
) GO CREATE UNIQUE INDEX[ix_uniq_barrier] ON[dbo].[barrier]
([gid] ASC, [branch_id] ASC, [op] ASC, [barrier_id] ASC)
WITH(IGNORE_DUP_KEY = ON) GO

这里比较关键的是那个唯一索引,有一个IGNORE_DUP_KEY = ON,这个其实就是为了等价mysqlinsert ignore表示存在相关字段的信息则不插入,否则就插入数据

当然还支持很多其他的数据库,建表语句可以从这里查看地址

四、创建项目

我们简单的创建两个.net core webapi项目进行测试,两个项目都进行相同的如下操作:

 1、安装Dtmcli和Microsoft.EntityFrameworkCore.SqlServer

安装Dtmcli是因为其中已经帮我们集成了DTM客户端SDK HTTP版本,想要GRPC版本可以安装Dtmgrpc

安装Microsoft.EntityFrameworkCore.SqlServer很显然是为了处理数据库。

Install-Package Dtmcli
Install-Package Microsoft.EntityFrameworkCore.SqlServer

2、配置

接下来我们配置服务,先在配置文件appsetting.json中添加如下

  "AppSettings": {
"DtmUrl": "http://localhost:36789",
"BusiUrl": "http://localhost:5056",
"QueryPreparedUrl": "http://localhost:5046",
"BarrierConn": "Data Source=.;Initial Catalog=HTGL;TrustServerCertificate=True;;Integrated Security=True"
}

DtmUrlDTM的监听地址,http的是36789grpc的是36790

BusiUrl:访问其他服务的地址

QueryPreparedUrl:回查的地址

BarrierConn:数据库连接语句

添加一个配置类:

    public class AppSettings
{
public string DtmUrl { get; set; }
public string BusiUrl { get; set; }
public string BarrierConn { get; set; }
public string QueryPreparedUrl { get; set; }
}

之后注入服务如下:

builder.Services.AddDtmcli(dtm => {
dtm.DtmUrl = builder.Configuration.GetValue<string>("AppSettings:DtmUrl");
dtm.SqlDbType = DtmCommon.Constant.Barrier.DBTYPE_SQLSERVER;
dtm.BarrierSqlTableName = "[HTGL].[dbo].[barrier]";
});
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));

SqlDbType:表示使用的数据库类型

BarrierSqlTableNameBarrier表的名字

3、添加代码

我们在其中一个项目添加主程序代码如下:

    [ApiController]public class DtmController : ControllerBase
{ private readonly ILogger<DtmController> _logger;
private readonly IDtmClient _dtmClient;
private readonly IDtmTransFactory _transFactory;
private readonly AppSettings _settings;
private readonly IBranchBarrierFactory _factory;
public DtmController(ILogger<DtmController> logger, IDtmClient dtmClient,IDtmTransFactory transFactory, IOptions<AppSettings> settings, IBranchBarrierFactory factory)
{
_logger = logger;
_dtmClient = dtmClient;
_transFactory = transFactory;
_settings = settings.Value;
_factory = factory;
}
private DbConnection GetConn() => new Microsoft.Data.SqlClient.SqlConnection(_settings.BarrierConn);
[HttpPost("post-dtm-msg")]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
//1、创建gid
var gid = await _dtmClient.GenGid(cancellationToken);
//2、设置分支事务
var msg = _transFactory.NewMsg(gid)
.Add(_settings.BusiUrl + "/TransOut", new { id = 123 })
.Add(_settings.BusiUrl + "/TransIn", new { id = 321 });//3、执行submit
using (DbConnection conn = GetConn())
{
await msg.DoAndSubmitDB(_settings.QueryPreparedUrl + "/msg-queryprepared", conn, async tx =>
{
//4、执行本地事务
await Task.CompletedTask;
});
}
_logger.LogInformation("result gid is {0}", gid);
return Content("SUCCESS");
}
[HttpGet("msg-queryprepared")]
public async Task<IActionResult> QueryPrepared(CancellationToken cancellationToken)
{
var bb = _factory.CreateBranchBarrier(Request.Query);
_logger.LogInformation("bb {0}", bb);
using (DbConnection conn = GetConn())
{
//回调查询消息状态
var res = await bb.QueryPrepared(conn);
return Ok(new { dtm_result = res });
}
}
}

然后我们向另一个服务项目添加如下代码,作为一个简单的服务方法,没有任何操作只是返回成功:

[ApiController]
public class TransController : ControllerBase
{
private readonly ILogger<TransController> _logger;
private readonly IBranchBarrierFactory _factory;
private readonly AppSettings _settings;
private DbConnection GetConn() => new Microsoft.Data.SqlClient.SqlConnection(_settings.BarrierConn);
public TransController(ILogger<TransController> logger, IBranchBarrierFactory factory, IOptions<AppSettings> settings)
{
_logger = logger;
_factory = factory;
_settings = settings.Value;
}
[HttpPost("TransIn")]
public async Task<IResult> In()
{
return Results.Ok(new { dtm_result = "SUCCESS" });
//return Results.Ok(new { dtm_result = "FAILURE" });
}
[HttpPost("TransOut")]
public async Task<IResult> Out()
{
return Results.Ok(new { dtm_result = "SUCCESS" });
}
}

五、执行查看结果

我们正常执行,可以看到下面的动图结果,在执行完本地事务后会访问分支事务,然后数据库表中添加了一条记录

可以在管理后台看到我们请求成功的信息

如果要演示失败,需要做以下修改直接报错,我们可以看到访问了回调方法,然后数据库中看到rollback标记的消息

using (DbConnection conn = GetConn())
{
await msg.DoAndSubmitDB(_settings.QueryPreparedUrl + "/msg-queryprepared", conn, async tx =>
{
throw new Exception("报错了");
//4、执行本地事务
await Task.CompletedTask;
});
}

提交后再宕机演示比较麻烦,我就不演示了,大家意会即可。

如果分支事务返回的不是SUCCESS而是FAILURE会由DTM隔一段时间重新请求,dtm对每个事务的重试是指数退避策略,具体为间隔是每失败一次,间隔加倍,避免过多的重试,导致系统负载异常上升。

如果您经过长时间的的宕机,因指数退避算法导致要很久才会重试。如果您想要手动触发立即重试,您可以手动把相应事务的next_cron_time(Redis存储引擎的该功能还在开发中)修改为当前时间,就会在数秒内被定时轮询,事务就会继续往前执行。

.NetCore中使用分布式事务DTM的二阶段消息的更多相关文章

  1. 分布式事务解决方案(二)消息系统避免分布式事务 & MQ事务消息 & Sagas 事务模型

    参考文档: 如何用消息系统避免分布式事务:http://blog.jobbole.com/89140/ https://www.cnblogs.com/savorboard/p/distributed ...

  2. SpringBoot多数据源中的分布式事务

    虽然现在微服务越来越流行,我们的系统随之也拆分出来好多的模块功能.这样做的目的其实就是为了弥补单体架构中存在的不足.随着微服务的拆分,肯定设计到分库分表,但这之中肯定设计到分布式事务.最典型的例子就是 ...

  3. .NetCore中EFCore的使用整理(二)-关联表查询

    EF常用处理关联加载的方式有3中:延迟加载(Lazy Loading).贪婪加载 (Eager Loading)以及显示加载. 一.EF Core  1.1 1.当前的版本,还不支持延迟加载(Lazy ...

  4. DDD~领域事件中使用分布式事务

    回到目录 对于一个聚合来说,它可能会被附加很多事件,这里我们叫它领域事务,因为一个聚会我们可以把它理解成一个领域,一个业务.对于领域事件不清楚的同学可以看看我的这篇文章<DDD~领域事件与事件总 ...

  5. Sql Server中启用分布式事务小结

    1.web服务器与数据库服务器同时启动msdtc服务 2. 2台服务器做出如下配置: 控制面板->管理工具->组件服务->计算机->我的电脑->本地DTC .Net示例: ...

  6. 微服务痛点-基于Dubbo + Seata的分布式事务(AT)模式

    前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...

  7. 微服务分布式事务之LCN、TCC

    在亿级流量架构之分布式事务解决方案对比中, 已经简单阐明了从本机事务到分布式事务的演变过程, 文章的最后简单说明了TCC事务, 这儿将会深入了解TCC事务是原理, 以及理论支持, 最后会用Demo举例 ...

  8. 分布式事务 | 使用DTM 的Saga 模式

    DTM 简介 前面章节提及的MassTransit.dotnetcore/CAP都提供了分布式事务的处理能力,但也仅局限于Saga和本地消息表模式的实现.那有没有一个独立的分布式事务解决方案,涵盖多种 ...

  9. SpringCloud微服务实战——搭建企业级开发框架(二十七):集成多数据源+Seata分布式事务+读写分离+分库分表

    读写分离:为了确保数据库产品的稳定性,很多数据库拥有双机热备功能.也就是,第一台数据库服务器,是对外提供增删改业务的生产服务器:第二台数据库服务器,主要进行读的操作. 目前有多种方式实现读写分离,一种 ...

  10. .NetCore中EFCore for MySql整理(二)

    一.简介 EF Core for MySql的官方版本MySql.Data.EntityFrameworkCore 目前正是版已经可用当前版本v6.10,对于以前的预览版参考:http://www.c ...

随机推荐

  1. base64EncodedStringWithOptions iOS

    // 64位编码//先将string转换成dataNSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];NSData *base ...

  2. chap3第三小组总结

      本周我们第三小组在张庆老师的带领下,走向编程的新一扇大门--分支结构.   我们第三小组是线下聚在一起学习,这样可以使我们的学习效率大大提高,我们在线下学习可以让我们的小组长更方便的指导我们的学习 ...

  3. qt的其他窗口

    一.qt的其他类族 2.Qlabel ui->setupUi(this); QFont font;//确立一个字体对象 font.setFamily("华文行楷");//字体 ...

  4. 最短路算法之 Dijkstra

    部分内容参考了李煜东的<算法竞赛进阶指南>,在此声明. 单源最短路径 单源最短路径问题,是说,给定一张有向图(无向图)\(G=(V,E)\) ,\(V\) 是点集,\(E\) 是边集,\( ...

  5. PLC入门笔记9

    梯形图电路之电机控制 电机直接启动控制电路 电机正反停控制电路 我的图.. 但愿最后说的不要发生吧 例如下错了程序 导致... 最好外部电路互锁一下.. 电机故障判断电路 我的图.. 电机故障转换电路 ...

  6. 认识flutter

    flutter是谷歌的移动的ui框架,可以快速的在ios和安卓上构建高质量的原生用户界面.最主要的是完全免费开源.开发快,最重要的是使用flutter开发的开发工作者也越来越多了,生态圈也越来越好了. ...

  7. 禁止的回文子串 Dyslexic Gollum

    UVA1633 一个长的回文串都可以由短的回文串拓展而来,只要短的回文在左右两端增加相同的字符即可.因此,在考虑长度为NNN的01串时,只要在从长度为1向NNN拓展的过程中,保证后KKK个字符不是回文 ...

  8. JavaScript的Object.defineProperty( )方法

    Object.defineProperty方法可以在一个对象上定义一个新的属性,或者修改该对象原有的属性,并返回该对象. 基础的语法格式如下: 1 var data = {}//定义一个对象 2 Ob ...

  9. 微信内置浏览器的JsAPI(WeixinJSBridge)

    参考: https://www.baidufe.com/item/f07a3be0b23b4c9606bb.html https://github.com/zxlie/WeixinApi

  10. 【转】【进程管理】Linux进程调度:调度时机

    转自:https://zhuanlan.zhihu.com/p/163728119 概述: 进程切换分为自愿(voluntary)和强制(involuntary)两种.通常自愿切换是指任务由于等待某种 ...