基于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" ...
随机推荐
- 【动手学深度学习】第三章笔记:线性回归、SoftMax 回归、交叉熵损失
这章感觉没什么需要特别记住的东西,感觉忘了回来翻一翻代码就好. 3.1 线性回归 3.1.1 线性回归的基本元素 1. 线性模型 \(\boldsymbol{x}^{(i)}\) 是一个列向量,表示第 ...
- Kubernetes(三)实战入门
实战入门 本章介绍如何在kubernetes集群中部署一个nginx服务,并能够对其进行访问. 1. Namespace Namespace主要作用是实现多套环境的资源隔离或者多租户的资源隔离. 默认 ...
- Linux gpio子系统:gpio_direction_output 与 gpio_set_value的区别
Linux gpio子系统:gpio_direction_output 与 gpio_set_value的区别 背景 最近改驱动程序,看到驱动代码中既有gpio_direction_output也有g ...
- Ubuntu访问samba共享文件
Ubuntu访问samba共享文件 参考:https://www.cnblogs.com/Wolf-Dreams/p/11241198.html 做法 安装samba-client.cifs-util ...
- Canvas绘制圆角图片
效果图: 思路: 先绘制一个圆角长方形 在画布中裁剪下来 在圆角长方形内绘制图片 图片四个角超出圆角长方形的区域被隐藏 具体代码: <!DOCTYPE html> <html lan ...
- 【韦东山】嵌入式全系统:单片机-linux-Android对硬件操作的不同侧重点
我是韦东山,一直从事嵌入式Linux培训,最近打算连载一系列文章. 正在录制全新的嵌入式Linux视频,使用新路线,不再从裸机/uboot开始,效率更高. 对应文档也会写成书<<嵌入式Li ...
- UWP WinUI 制作一个路径矢量图标按钮样式入门
本文将告诉大家如何在 UWP 或 WinUI3 或 UNO 里,如何制作一个路径按钮.路径按钮就是使用几何路径轮廓表示内容的按钮,常见于各种图标按钮,或 svg 系贴图矢量图按钮 在网上有非常多矢量图 ...
- SpringBoot快速插入Mysql 1000万条数据
导读 有时候为了验证系统瓶颈,需要往数据库表中插入大量数据,可以写sheel脚本插入,前几天为了插入100万条数据,走的sheel脚本(点我直达),插入速度简直无法直视,花了3小时,才插入了10万条, ...
- docker-compose创建haproxy教程
本文主要讲解通过docker-compose创建haproxy并进行代理 一.haproxy简介 HAProxy是一款基于事件驱动.单进程模型设计的四层与七层负载均衡器,它能够在TCP/UDP层面以及 ...
- 搜索Python编程获取相关图书信息
1.获取相关图书信息 #搜索"Python编程"获取相关图书信息 from selenium import webdriver from selenium.webdriver.su ...