基于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" ...
随机推荐
- MySQL日志(redo log、binlog)刷盘策略
通过上篇文章,我们知道MySQL是采用两段提交策略来保证事务的原子性的,redo log刷盘的时机是在事务提交的commit阶段采取刷盘的,在此之前,redo log都存在于redo log buff ...
- LED虚拟拍摄-跟踪算法
LED虚拟拍摄-跟踪算法 图引用拍摄黑科技,LED虚拟影棚揭秘 标定流程 上面是一台Track设备,现精度比较高的主要是Redspy,Mosys,一般影视用这二种,其底层技术参考SMAL单目+惯性传感 ...
- LVS介绍与配置
目录 LVS(Linux Virtual Server) 1. 概述 1.1 LVS简介 1.2 LVS架构 2. LVS工作模式 2.1 NAT模式(Network Address Translat ...
- k8s 实战 3----标签
如果你对k8s还不了解,可以看下前文k8s 实战 1 ---- 初识 (https://www.cnblogs.com/jilodream/p/18245222)k8s 实战 2 ---- pod 基 ...
- CF414B
这道题dp状态表示需要一点思维,而且会卡到时间复杂度 之前题主用的是试除法,时间复杂度为n^2.5,然后被卡了,但是换一种写法就是对的 #include <iostream> #inclu ...
- 使用gzexe加密shell脚本
使用 gzexe 加密 shell 脚本是一个相对简单的过程.以下是具体的步骤: 编写你的 shell 脚本:首先,你需要有一个 shell 脚本文件,比如 myscript.sh. 确保脚本可执行: ...
- Error: Dynamic require of "path" is not supported
failed to load config from D:\BaiduSyncdisk\vue3\sys-manager\vite.config.jserror when starting dev s ...
- oeasy教您玩转vim - 22 - 配置文件
配置文件 回忆上节课内容 我们上次了解到了状态横条 通过转义表示 item 控制 item 宽度的方法 将 item 成组的方法 还有一个总开关 laststatus 但是每次都要写很长的一段话来配置 ...
- JMeter 配置元件之按条件读取CSV Data Set Config
实践环境 win10 JMeter 5.4.1 需求描述 需求是这样的,需要压测某个接口(取消分配接口),请求这个接口之前,需要先登录系统(物流WMS系统),并在登录后,选择并进入需要操作的仓库,然后 ...
- STL 算法 <algorithm>,
STL 算法部分主要由头文件 <algorithm>,<numeric>,<functional > 组成.要使用 STL 中的算法函数必须包含头文件 < a ...