基于donetcore/CAP实现分布式事务一致性
官网:https://cap.dotnetcore.xyz
相关介绍
CAP 是一个EventBus,同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。
在微软的 eShop 微服务示例项目中,推荐使用 CAP 作为生产环境可用的 EventBus。
什么是 EventBus?
事件总线是一种机制,它允许不同的组件彼此通信而不彼此了解。 组件可以将事件发送到Eventbus,而无需知道是谁来接听或有多少其他人来接听。 组件也可以侦听Eventbus上的事件,而无需知道谁发送了事件。 这样,组件可以相互通信而无需相互依赖。 同样,很容易替换一个组件。 只要新组件了解正在发送和接收的事件,其他组件就永远不会知道.
相对于其他的 Service Bus 或者 Event Bus, CAP 拥有自己的特色,它不要求使用者发送消息或者处理消息的时候实现或者继承任何接口,拥有非常高的灵活性。我们一直坚信约定大于配置,所以CAP使用起来非常简单,对于新手非常友好,并且拥有轻量级。
CAP 采用模块化设计,具有高度的可扩展性。你有许多选项可以选择,包括消息队列,存储,序列化方式等,系统的许多元素内容可以替换为自定义实现。
简单实战
使用CAP需要依赖的第三方库,这里我们持久化选用了SqlServer数据库,消息队列使用了Rabbitmq

相关配置(上游系统、下游系统配置相同)
// 注入EF上下文对象
var CapConnectionString = builder.Configuration.GetSection("CAPConnectionStrings").GetValue<string>("DefaultConnection");
builder.Services.AddDbContext<CAPDbContext>(options =>
options.UseSqlServer(CapConnectionString));
//注入CAP
builder.Services.AddCap(x =>
{
// 使用RabbitMQ作为消息队列
x.UseRabbitMQ(opt =>
{
opt.HostName = "192.168.3.128";
opt.Port = 5674;
opt.UserName = "guest";
opt.Password = "guest";
opt.VirtualHost = "/";
});
// 使用SqlServer作为CAP的存储
x.UseSqlServer(opt =>
{
opt.ConnectionString = CapConnectionString;
});
// 设置CAP的其他选项
x.FailedRetryCount = 5; // 失败重试次数
x.FailedRetryInterval = 60; // 失败重试间隔(秒)
x.UseDashboard(dashoptions =>
{
dashoptions.PathMatch = "/cap"; //面板地址
});
});
上游系统发布
/// <summary>
/// CAP:基于消息队列通过事件驱动+回调方法补偿的机制实现分布式事务的最终一致性
/// 1、消息被消费者至少接收到一次,需要通过接口幂等性/redis唯一ID实现消息不会重复消费
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class CAPController : ControllerBase
{
private readonly ICapPublisher capPublisher;
private readonly CAPDbContext cAPDbContext;
public CAPController(ICapPublisher capPublisher, CAPDbContext cAPDbContext)
{
this.capPublisher = capPublisher;
this.cAPDbContext = cAPDbContext;
} [HttpGet("CreateOrder")]
public IActionResult CreateOrder()
{
using (var trans = cAPDbContext.Database.BeginTransaction(capPublisher, autoCommit: true))
{
//业务代码
try
{
var order = new OrderInfo { OrderId = Guid.NewGuid().ToString(), ProId= "Pro_13327530-e706-4ef3-aa5b-02aac9227499", Status = 0 };
cAPDbContext.orderInfos.Add(order);
capPublisher.Publish("test_createorder_v1", order, "test_createorder_callback_v1");
cAPDbContext.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
return Ok();
}
[CapSubscribe("test_createorder_callback_v1")]
private void CreateOrderCallback(JsonElement param)
{
var isSuccess = param.GetProperty("IsSuccess").GetBoolean();
var OrderId = param.GetProperty("OrderId").GetString();
if (isSuccess)
{
var order = cAPDbContext.orderInfos.FirstOrDefault(c => c.OrderId == OrderId);
order.Status = 1;
cAPDbContext.Update(order);
cAPDbContext.SaveChanges();
Console.WriteLine("修改订单状态成功");
}
else
{
var order = cAPDbContext.orderInfos.FirstOrDefault(c => c.OrderId == OrderId);
order.Status = -1;
cAPDbContext.Update(order);
cAPDbContext.SaveChanges();
Console.WriteLine("下单失败,执行补偿");
}
}
}
下游订阅处理
[Route("api/[controller]")]
[ApiController]
public class CapConsumerController : ControllerBase
{
private readonly ICapPublisher capPublisher;
private readonly CAPDbContext cAPDbContext;
public CapConsumerController(ICapPublisher capPublisher, CAPDbContext cAPDbContext)
{
this.capPublisher = capPublisher;
this.cAPDbContext = cAPDbContext;
}
[HttpGet("CreatePro")]
public IActionResult CreatePro()
{
cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品1" });
cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品2" });
cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品3" });
cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品4" });
cAPDbContext.SaveChanges();
return Ok();
}
[CapSubscribe("test_createorder_v1")]
private object KjProCount(JsonElement param)
{
var obj = param.Deserialize<OrderInfo>();
try
{
if (obj != null)
{
using (var trans = cAPDbContext.Database.BeginTransaction(capPublisher, autoCommit: true))
{
var pro = cAPDbContext.proInfos.FirstOrDefault(c => c.ProId == obj.ProId);
if (pro != null && pro.Count > 0)
{
pro.Count--;
cAPDbContext.Update(pro);
cAPDbContext.proOrderRecords.Add(new ProOrderRecord { RecordId = Guid.NewGuid().ToString(), OrderId = obj.OrderId, ProId = pro.ProId });
cAPDbContext.SaveChanges();
throw new Exception("Error");
Console.WriteLine($"下单成功{JsonConvert.SerializeObject(obj)}");
return new { IsSuccess = true, OrderId = obj.OrderId };
}
else
{
Console.WriteLine($"库存不足{JsonConvert.SerializeObject(obj)}");
}
}
}
return new { IsSuccess = false, OrderId = obj.OrderId };
}
catch (Exception ex)
{
Console.WriteLine("发生异常回滚");
return new { IsSuccess = false, OrderId = obj.OrderId };
}
}
}
在上游和下游系统中会自动创建2个表(发布、接收的消息)

监控界面

基于donetcore/CAP实现分布式事务一致性的更多相关文章
- .Net Core with 微服务 - 使用 AgileDT 快速实现基于可靠消息的分布式事务
前面对于分布式事务也讲了好几篇了(可靠消息最终一致性 分布式事务 - TCC 分布式事务 - 2PC.3PC),但是还没有实战过.那么本篇我们就来演示下如何在 .NET 环境下实现一个基于可靠消息的分 ...
- 微服务痛点-基于Dubbo + Seata的分布式事务(AT)模式
前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...
- 微服务痛点-基于Dubbo + Seata的分布式事务(TCC模式)
前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...
- Asp.Net Core&CAP实现分布式事务
需要注意的是标题中的CAP不是指的CAP理论,而是园区大神杨晓东实现的框架,CAP框架基于本地消息表用最终一致性实现分布式事务. 本地消息表 首先我们考虑一个场景,在将用户信息更改后,需要发送一条消息 ...
- 三:分布式事务一致性协议2pc和3pc
一:分布式一致性协议--->对于一个分布式系统进行架构设计的过程中,往往会在系统的可用性和数据一致性之间进行反复的权衡,于是就产生了一系列的一致性协议.--->长期探索涌现出一大批经典的一 ...
- 六:分布式事务一致性协议paxos的分析
最近研究paxos算法,看了许多相关的文章,概念还是很模糊,觉得还是没有掌握paxos算法的精髓,所以花了3天时间分析了libpaxos3的所有代码,此代码可以从https://bitbucket.o ...
- Dubbo 分布式事务一致性实现
我觉得事务的管理不应该属于Dubbo框架, Dubbo只需实现可被事务管理即可, 像JDBC和JMS都是可被事务管理的分布式资源, Dubbo只要实现相同的可被事务管理的行为,比如可以回滚, 其它事务 ...
- 【原】ActiveMq实现分布式事务一致性
前言:关于分布式事务话题一直是颇有争议的话题,在本文中通过ActiveMq 实现分布式事务做一个简单的demo;同时也让自己能在实践中可以获取经验和对分布式事务自己的一些思考. 1.本地事务 我们通常 ...
- MySQL 中基于 XA 实现的分布式事务
1 XA协议 首先我们来简要看下分布式事务处理的XA规范可知XA规范中分布式事务有AP,RM,TM组成: 其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务 ...
- 四:分布式事务一致性协议paxos通俗理解
转载地址:http://www.lxway.com/4618606.htm 维基的简介:Paxos算法是莱斯利·兰伯特(Leslie Lamport,就是 LaTeX 中的"La" ...
随机推荐
- 在Linux驱动中使用notifier通知链
在Linux驱动中使用notifier通知链 背景 在驱动分析中经常看到fb_notifier_callback,现在趁有空学习一下. 介绍 linux中的观察者模式是最显然的就是"通知链& ...
- 微信小程序day04基础加强
一.自定义组件 1.1 组件的创建与引用 首先创建组件 然后我们组件的引用分为局部和全局引用 局部引用就是在当前页面能使用,在当前页面的json文件里面配置 全局引用同样的道理,注意跟page等是同级 ...
- Java进阶:HashMap底层原理(通俗易懂篇)
1.底层结构 Java 7及之前版本 在Java 7及之前的版本中,HashMap的底层数据结构主要是数组加链表.具体实现如下: 数组:HashMap的核心是一个Entry数组(Entry<K, ...
- R语言将多景遥感影像拼接在一起的方法
本文介绍基于R语言中的raster包,遍历文件夹,读取文件夹下的大量栅格遥感影像,并逐一对每一景栅格图像加以拼接.融合,使得全部栅格遥感影像拼接为完整的一景图像的方法. 其中,本文是用R语言来 ...
- 诺基亚8110 4G手机强制格式化方法
关机状态下,先按住上键,再按住开机键,出现开机动画时松开开机键,出现硬割界面下键选择第一个wipe用开机键确认yes,再次出现硬格界面,下键选择第二个wipe用开机键确认yes,再次出现硬格界面开机键 ...
- 图扑低代码数字孪生 Web SCADA 智慧钢厂
2024 年 4 月,中国钢铁工业协会发布了<钢铁行业数字化转型评估报告(2023年)>(以下简称<报告>).<报告>指出,绝大部分钢铁企业建立了数字化转型相关管理 ...
- Docker通信全视角:原理、实践与技术洞察
本文全面深入地探讨了Docker容器通信技术,从基础概念.网络模型.核心组件到实战应用.详细介绍了不同网络模式及其实现,提供了容器通信的技术细节和实用案例,旨在为专业从业者提供深入的技术洞见和实际操作 ...
- MViTv2:Facebook出品,进一步优化的多尺度ViT | CVPR 2022
论文将Multiscale Vision Transformers (MViTv2) 作为图像和视频分类以及对象检测的统一架构进行研究,结合分解的相对位置编码和残差池化连接提出了MViT的改进版本 来 ...
- 题解:P10672 【MX-S1-T1】壁垒
暑期集训=依托答辩. 分析 种类数是奇数一定无解. 否则每种数字先输出一次,在此过程中每增加两个数时,因为每个数字种类数都不一样,所以前缀种类数也同时增加 \(2\),保证一定为偶数. 然后输出完以后 ...
- 《最新出炉》系列入门篇-Python+Playwright自动化测试-53- 处理面包屑(详细教程)
1.简介 面包屑(Breadcrumb),又称面包屑导航(BreadcrumbNavigation)这个概念来自童话故事"汉赛尔和格莱特",当汉赛尔和格莱特穿过森林时,不小心迷路了 ...