前面对于分布式事务也讲了好几篇了(可靠消息最终一致性 分布式事务 - TCC 分布式事务 - 2PC、3PC),但是还没有实战过。那么本篇我们就来演示下如何在 .NET 环境下实现一个基于可靠消息的分布式事务。基于可靠消息的分布式事务流程上还是比较清晰明了的,但是要用代码去一个个实现还是比较费事的。通过分析可以发现这个事务的关键点就是要在真正的业务逻辑的前面、后面插入对应的流程。很明显这种流程是可以通过 AOP 技术来简化操作的。于是就有了 AgileDT 。AgileDT 使用 Natasha 在启动的时候动态生成代理类,来为你完成跟消息部分的操作,使用者只需关心核心业务逻辑就可以了。

https://github.com/kklldog/AgileDT 开源不易,大家多多

回顾

前面一篇文章(可靠消息最终一致性 )我们详细介绍了基于可靠消息的分布式事务。为了更好的理解 AgileDT 的代码,我们还是有必要简单的来回顾下。



该方案总体流程上可分为以下步骤:

  1. 主动方在真正的业务开始前先向可靠消息服务发送一个“待确认”的消息
  2. 可靠消息服务收到待确认消息后持久化消息到数据库
  3. 如果以上操作成功则主动方开始真正的业务,如果失败则直接放弃执行业务
  4. 如果业务执行成功则发送“确认”消息给可靠消息服务,如果执行失败则发送“取消”给可靠消息服务。
  5. 如果可靠消息服务收到“确认”消息则更新数据库里的消息记录的状态为“待发送”,如果收到的消息为“取消”则更新消息状态为“已取消”
  6. 如果上一步更新的数据库为“待发送”,那么会开始往MQ投递消息,并且更改数据库里的消息记录的状态为“已发送”
  7. 上一步往MQ投递消息成功后,MQ会给被动方推送消息。
  8. 被动方收到消息后开始处理业务
  9. 如果业务处理成功,则被动方对MQ进行ACK回复,则这条消息会从MQ内移除掉
  10. 如果业务处理成功,则发送“已完成”消息给可靠消息服务
  11. 可靠消息服务收到“已完成”消息后更新数据库消息记录未“已完成”

废话不多说了,下面让我们演示下如何使用 AgileDT 来快速实现一个基于可靠消息的分布式事务。

以下我们还是以经典的订单下单完成给会员赠送积分的场景来演示。

使用 AgileDT

依赖组件

  • mysql
  • rabbitmq

目前支持 mysql 数据库,但是数据访问组件使用的是 freesql 所以后续要实现支持别的数据库也很简单。目前框架使用的可靠消息服务为 rabbitmq 。

运行服务端

在服务新建一个数据库并且新建一张表

// crate event_message table on mysql
create table if not exists event_message
(
event_id varchar(36) not null
primary key,
biz_msg varchar(4000) null,
status enum('Prepare', 'Done', 'WaitSend', 'Sent', 'Finish', 'Cancel') not null,
create_time datetime(3) null,
event_name varchar(255) null
);

使用docker-compose运行服务端

version: "3"  # optional since v1.27.0
services:
agile_dt:
image: "kklldog/agile_dt"
ports:
- "5000:5000"
environment:
- db:provider=mysql
- db:conn= Database=agile_dt;Data Source=192.168.0.115;User Id=root;Password=mdsd;port=3306
- mq:userName=admin
- mq:password=123456
- mq:host=192.168.0.115
- mq:port=5672

安装客户端

在主动方跟被动方都需要安装AgileDT的客户端库

Install-Package AgileDT.Client

主动方使用方法

  1. 在业务数据库添加事务消息表
// crate event_message table on mysql
create table if not exists event_message
(
event_id varchar(36) not null
primary key,
biz_msg varchar(4000) null,
status enum('Prepare', 'Done', 'WaitSend', 'Sent', 'Finish', 'Cancel') not null,
create_time datetime(3) null,
event_name varchar(255) null
);
  1. 修改配置文件
在appsettings.json文件添加以下节点:
"agiledt": {
"server": "http://localhost:5000",
"db": {
"provider": "mysql",
"conn": "Database=agile_order;Data Source=192.168.0.125;User Id=dev;Password=dev@123f;port=13306"
//"conn": "Database=agile_order;Data Source=192.168.0.115;User Id=root;Password=mdsd;port=3306"
},
"mq": {
"host": "192.168.0.125",
//"host": "192.168.0.115",
"userName": "admin",
"password": "123456",
"port": 5672
}
}
  1. 注入 AgileDT 客户端服务
       public void ConfigureServices(IServiceCollection services)
{
services.AddAgileDT();
...
}
  1. 实现IEventService方法

    处理主动方业务逻辑的类需要实现IEventService接口,并且标记那个方法是真正的业务方法。AgileDT在启动的时候会扫描这些类型,并且使用AOP技术生成代理类,在业务方法前后插入对应的逻辑来跟可靠消息服务通讯。

    这里要注意的几个地方:
  • 实现IEventService接口
  • 使用DtEventBizMethod注解标记业务入口方法
  • 使用DtEventName注解来标记事务的方法名称,如果不标记则使用类名

注意:业务方法最终一定要使用事务来同步修改消息表的status字段为done状态,这个操作框架没办法帮你实现

注意:业务方法如果失败请抛出Exception,如果不抛异常框架一律认为执行成功

 public interface IAddOrderService:IEventService
{
bool AddOrder(Order order);
} [DtEventName("orderservice.order_added")]
public class AddOrderService : IAddOrderService
{
private readonly ILogger<AddOrderService> _logger; public AddOrderService(ILogger<AddOrderService> logger)
{
_logger = logger;
} public string EventId {
get;
set;
} [DtEventBizMethod]
public virtual bool AddOrder(Order order)
{
var ret = false; //3. 写 Order 跟 修改 event 的状态必选写在同一个事务内
FreeSQL.Instance.Ado.Transaction(() =>
{
order.EventId = EventId;//在订单表新增一个eventid字段,使order跟event_message表关联起来
var ret0 = FreeSQL.Instance.Insert(order).ExecuteAffrows();
var ret1 = FreeSQL.Instance.Update<OrderService.Data.entities.EventMessage>()
.Set(x => x.Status, MessageStatus.Done)
.Where(x => x.EventId == EventId)
.ExecuteAffrows(); ret = ret0 > 0 && ret1 > 0;
}); return ret; } /// <summary>
/// 构造后续业务处理需要的消息内容
/// </summary>
/// <returns></returns>
public string GetBizMsg()
{
//这里可以构造传递到MQ的业务消息的内容,比如传递订单编号啊 ,以便后续的被动方处理业务时候使用
var order = FreeSQL.Instance.Select<Order>().Where(x => x.EventId == EventId).First();
return order?.Id;
} }

在实现好 IAddOrderService 接口后,你可以像平常一样使用 IAddOrderService 来注入实现类。比如在 Controller 的构造函数注入进去。因为 AgileDT 在启动的时候会自动帮你注册。

注意:IAddOrderService 跟实现类的生命周期是 Scoped 。

被动方使用方法

  1. 在业务方数据库建表或者在业务表上加字段

    对于被动方来说这里不是必须要建一个表。但是至少要有个地方来存储event_id的信息,最简单的是直接在业务主表上加event_id字段。
  2. 修改配置文件
在appsettings.json文件添加以下节点:
"agiledt": {
"server": "http://localhost:5000",
"db": {
"provider": "mysql",
"conn": "Database=agile_order;Data Source=192.168.0.125;User Id=dev;Password=dev@123f;port=13306"
//"conn": "Database=agile_order;Data Source=192.168.0.115;User Id=root;Password=mdsd;port=3306"
},
"mq": {
"host": "192.168.0.125",
//"host": "192.168.0.115",
"userName": "admin",
"password": "123456",
"port": 5672
}
}
  1. 注入AgileDT服务
       public void ConfigureServices(IServiceCollection services)
{
services.AddAgileDT();
...
}
  1. 实现IEventMessageHandler接口

    被动方需要接收MQ投递过来的消息,这些处理类需要实现IEventMessageHandler接口。AgileDT启动的时候会去扫描这些类,然后跟MQ建立绑定关系。
  • 这里必须使用DtEventName注解标记需要处理的事件名称
  • Reveive 方法必须是冥等的
    public interface IOrderAddedMessageHandler: IEventMessageHandler
{
} [DtEventName("orderservice.order_added")]
public class OrderAddedMessageHandler: IOrderAddedMessageHandler
{
static object _lock = new object(); public bool Receive(EventMessage message)
{
var bizMsg = message.BizMsg;
var eventId = message.EventId;
string orderId = bizMsg; lock (_lock)
{
var entity = FreeSQL.Instance.Select<PointHistory>().Where(x => x.EventId == eventId).First();
if (entity == null)
{
var ret = FreeSQL.Instance.Insert(new PointHistory
{
Id = Guid.NewGuid().ToString(),
EventId = message.EventId,
OrderId = orderId,
Points = 20,
CreateTime = DateTime.Now
}).ExecuteAffrows(); return ret > 0;
}
else
{
return true;
}
}
}
}

总结

通过以上演示,我们快速的实现了一个订单下单会员赠送积分的服务。可以看到使用 AgileDT 可以很快速的实现一个分布式事务。特别是在实现过一个分布式事务后,后面实现起来就特别简单,只要实现几个接口就可以了。AgileDT 才刚刚起步,希望大家多多支持,多多 ,多多 PR 。

https://github.com/kklldog/AgileDT

.Net Core with 微服务 - 使用 AgileDT 快速实现基于可靠消息的分布式事务的更多相关文章

  1. 微服务架构 | 11.1 整合 Seata AT 模式实现分布式事务

    目录 前言 1. Seata 基础知识 1.1 Seata 的 AT 模式 1.2 Seata AT 模式的工作流程 1.3 Seata 服务端的存储模式 1.4 Seata 与 Spring Clo ...

  2. spring cloud+dotnet core搭建微服务架构:服务发现(二)

    前言 上篇文章实际上只讲了服务治理中的服务注册,服务与服务之间如何调用呢?传统的方式,服务A调用服务B,那么服务A访问的是服务B的负载均衡地址,通过负载均衡来指向到服务B的真实地址,上篇文章已经说了这 ...

  3. spring cloud+.net core搭建微服务架构:服务发现(二)

    前言 上篇文章实际上只讲了服务治理中的服务注册,服务与服务之间如何调用呢?传统的方式,服务A调用服务B,那么服务A访问的是服务B的负载均衡地址,通过负载均衡来指向到服务B的真实地址,上篇文章已经说了这 ...

  4. .Net Core with 微服务 - 架构图

    上一次我们简单介绍了什么是微服务(.NET Core with 微服务 - 什么是微服务 ).介绍了微服务的来龙去脉,一些基础性的概念.有大佬在评论区指出说这根本不是微服务.由于本人的能力有限,大概也 ...

  5. 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)

    背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...

  6. spring cloud+dotnet core搭建微服务架构:Api网关(三)

    前言 国庆假期,一直没有时间更新. 根据群里面的同学的提问,强烈推荐大家先熟悉下spring cloud.文章下面有纯洁大神的spring cloud系列. 上一章最后说了,因为服务是不对外暴露的,所 ...

  7. spring cloud+dotnet core搭建微服务架构:配置中心(四)

    前言 我们项目中有很多需要配置的地方,最常见的就是各种服务URL地址,这些地址针对不同的运行环境还不一样,不管和打包还是部署都麻烦,需要非常的小心.一般配置都是存储到配置文件里面,不管多小的配置变动, ...

  8. spring cloud+dotnet core搭建微服务架构:配置中心续(五)

    前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...

  9. spring cloud+dotnet core搭建微服务架构:Api授权认证(六)

    前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...

随机推荐

  1. JDK 5.0新特性

    时间:2016-11-5 12:03 JDK5.0新特性    泛型.枚举.静态导入.自动拆装箱.增强for循环.可变参数1.Junit单元测试    测试的对象是类中的一个方法.    junit不 ...

  2. zap高性能日志

    摘要 日志在整个工程实践中的重要性不言而喻,在选择日志组件的时候也有多方面的考量.详细.正确和及时的反馈是必不可少的,但是整个性能表现是否也是必要考虑的点呢?在长期的实践中发现有的日志组件对于计算资源 ...

  3. linux7(centos7)新系统安装后要做的事!

    前言: 初学者在安装linux(centos)系统后,需要对服务器的环境做些简单配置! 怎么联网? 怎么对SSH优化设置? 怎么在众多服务器中识别谁是谁? 常用的小工具推荐等等... ###网络优化设 ...

  4. 基本ServletWEB项目

    项目搭建 项目链接https://gitee.com/zhangjzm/smbms.git 前置知识,Servlet JSP 结构图 搭建maven web项目 1.搭建一个maven web项目 2 ...

  5. JPA实现泛型baseServcie+Mybatis

    在开发的过程中,我们总无法避免不同的实体类会去实现相同的操作(增删查改,分页查询等),因此在开发时,我们期望泛型将通用的方法进行包装,使我们能够专注于实体类自身的独特方法,而非一般性常用且重复性高的方 ...

  6. Linux 配置Maven(避免踩坑篇)

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 一.访问Maven官网下载压缩文件. 二.下载好的maven安装包放在磁盘的 /usr/local/ 目录下,如下图: 三.解压该压缩文 ...

  7. Python - poetry(2)命令介绍

    poetry 语法格式 poetry [-h] [-q] [-v [<...>]] [-V] [--ansi] [--no-ansi] [-n] <command> [< ...

  8. 【第九篇】- Git 标签之Spring Cloud直播商城 b2b2c电子商务技术总结

    Git 标签 如果你达到一个重要的阶段,并希望永远记住那个特别的提交快照,你可以使用 git tag 给它打上标签. 比如说,我们想为我们的 xxx 项目发布一个"1.0"版本. ...

  9. zip命令常用选项

    大家都知道,在linux上一切皆文件,在实际生产环境中,如果我们需要部署一些系统的服务,我们会将一些软件包提前下载下来统一放到一个文件夹中, 然后将部署的过程用shell或者python写成一个脚本, ...

  10. 洛谷P1160——队列安排(双向链表)

    题目描述 一个学校里老师要将班上N个同学排成一列,同学被编号为1-N,他采取如下的方法: 1.先将1号同学安排进队列,这时队列中只有他一个人: 2.2-N号同学依次入列,编号为i的同学入列方式为:老师 ...