基于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" ...
随机推荐
- Stable Diffusion(一)Stable Diffusion 原理
Stable Diffusion原理 此文为译文,原文见: https://stable-diffusion-art.com/how-stable-diffusion-work/ Stable Dif ...
- Linux内核中的static-key机制
# Linux内核中的static-key机制 背景 在移植某个TP时,发现频繁操作屏幕会导致i2c总线死掉.在跟踪代码的时候,我发现了这个static-key. 因此,学习一下这块的知识. refe ...
- 一次STM32进入HardFault_Handler 的解决情况
STM32进入HardFault_Handler调试 --- title: STM32进入HardFault_Handler调试 date: 2020-06-18 10:26:48 categorie ...
- 使用kk在centos7上离线部署kubesphere v3.0.0详解
环境准备 以三台centos 7.7 64bit 为例: 确保所有机器已经安装所需依赖软件(sudo curl openssl ebtables socat ipset conntrack docke ...
- JDK各个版本汇总
JDK1.4 正则表达式,异常链,NIO,日志类,XML解析器,XLST转换器 JDK1.5 自动装箱.泛型.动态注解.枚举.可变长参数.遍历循环 JDK1.6 提供动态语言支持.提供编译API和卫星 ...
- 阿里云服务器Docket安装RabbitMQ 3.8.12
DocketMQ安装RabbitMQ 地址:https://hub.docker.com/ 拉取镜像 docker pull rabbitmq:3.8.12-management-alpine 运行 ...
- bean的二次加工-Spring5.X后置处理器BeanPostProcessor
什么是BeanPostProcessor 是Spring IOC容器给我们提供的一个扩展接口 在调用初始化方法前后对Bean进行额外加工,ApplicationContext会自动扫描实现了BeanP ...
- 使用FModel提取游戏资产
目录 前言 FModel简介 FModel安装 FModel使用 初次使用 资产预览 资产导出 附录 dumper Dumper-7生成usmap文件 向游戏中注入dll 前言 这篇文章仅记录我作为初 ...
- 接口防刷!利用redisson快速实现自定义限流注解
问题: 在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资��损失. 如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如 ...
- java web 开发框架编
学习web 框架上开发需要的是安装 mysql 8.0 idea 2022 git 2.2.23 node 16以上 (新版本不好拉有些库了)jdk 最好是17以上 jdk8也是行的,反正不管 ...