使用Cap解决.Netcore分布式事务
一、什么是Cap
CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级、易使用、高性能等特点。
在我们构建 SOA 或者 微服务系统的过程中,我们通常需要使用事件来对各个服务进行集成,在这过程中简单的使用消息队列并不能保证数据的最终一致性, CAP 采用的是和当前数据库集成的本地消息表的方案来解决在分布式系统互相调用的各个环节可能出现的异常,它能够保证任何情况下事件消息都是不会丢失的。
你同样可以把 CAP 当做 EventBus 来使用,CAP提供了一种更加简单的方式来实现事件消息的发布和订阅,在订阅以及发布的过程中,你不需要继承或实现任何接口。
以下是CAP集在ASP.NET Core 微服务架构中的一个示意图:
二、安装
你可以运行以下下命令在你的项目中安装 CAP。
PM> Install-Package DotNetCore.CAP
CAP 支持 Kafka、RabbitMQ、AzureServiceBus 等消息队列,你可以按需选择下面的包进行安装:
PM> Install-Package DotNetCore.CAP.Kafka
PM> Install-Package DotNetCore.CAP.RabbitMQ
PM> Install-Package DotNetCore.CAP.AzureServiceBus
我们这里采用RabbitMQ,安装教程请转到另一篇文章:Winows下安装RabbitMQ
CAP 提供了 Sql Server, MySql, PostgreSQL,MongoDB 的扩展作为数据库存储:
// 按需选择安装你正在使用的数据库
PM> Install-Package DotNetCore.CAP.SqlServer
PM> Install-Package DotNetCore.CAP.MySql
PM> Install-Package DotNetCore.CAP.PostgreSql
PM> Install-Package DotNetCore.CAP.MongoDB
我们这里采用SqlServer
三、配置
首先配置CAP到 Startup.cs 文件中,如下:
public void ConfigureServices(IServiceCollection services) { ...... services.AddDbContext<AppDbContext>(); services.AddCap(x => { //如果你使用的 EF 进行数据操作,你需要添加如下配置: x.UseEntityFramework<AppDbContext>(); //可选项,你不需要再次配置 x.UseSqlServer 了 //如果你使用的ADO.NET,根据数据库选择进行配置: x.UseSqlServer("数据库连接字符串"); x.UseMySql("数据库连接字符串"); x.UsePostgreSql("数据库连接字符串"); //如果你使用的 MongoDB,你可以添加如下配置: x.UseMongoDB("ConnectionStrings"); //注意,仅支持MongoDB 4.0+集群 //CAP支持 RabbitMQ、Kafka、AzureServiceBus 等作为MQ,根据使用选择配置: x.UseRabbitMQ("ConnectionStrings"); x.UseKafka("ConnectionStrings"); x.UseAzureServiceBus("ConnectionStrings"); }); }
我们这里采用EF数据库配置和RabbitMQ如下:
services.AddCap(x => { //如果你使用的 EF 进行数据操作,你需要添加如下配置: x.UseEntityFramework<SysContext>(); x.UseRabbitMQ("localhost"); });
四、启动
运行程序,将在数据库生成Cap.Published和Cap.Received表如下图所示:
五、发布和订阅
在 Controller 中注入 ICapPublisher 然后使用 ICapPublisher 进行消息发送
private readonly ICapPublisher _capBus; public ValuesController(ICapPublisher capPublisher) { _capBus = capPublisher; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { _capBus.Publish("xxx.services.show.time", DateTime.Now); return new string[] { "value1", "value2" }; } [CapSubscribe("xxx.services.show.time")] public void ShowTime(DateTime datetime) { }
运行程序,Cap.Published和Cap.Received将生成发布消息和订阅消息的记录
如果你的订阅方法没有位于 Controller 中,则你订阅的类需要继承 ICapSubscribe:
namespace xxx.Service { public interface ISubscriberService { public void CheckReceivedMessage(DateTime datetime); } public class SubscriberService: ISubscriberService, ICapSubscribe { [CapSubscribe("xxx.services.show.time")] public void CheckReceivedMessage(DateTime datetime) { } } }
然后在 Startup.cs 中的 ConfigureServices() 中注入你的 ISubscriberService 类
public void ConfigureServices(IServiceCollection services) { //注意: 注入的服务需要在 `services.AddCap()` 之前 services.AddTransient<ISubscriberService,SubscriberService>(); services.AddCap(x=>{}); }
六、订阅者组
订阅者组的概念类似于 Kafka 中的消费者组,它和消息队列中的广播模式相同,用来处理不同微服务实例之间同时消费相同的消息。
当CAP启动的时候,她将创建一个默认的消费者组,如果多个相同消费者组的消费者消费同一个Topic消息的时候,只会有一个消费者被执行。 相反,如果消费者都位于不同的消费者组,则所有的消费者都会被执行。
相同的实例中,你可以通过下面的方式来指定他们位于不同的消费者组。
[CapSubscribe("xxx.services.show.time", Group = "group1" )] public void ShowTime1(DateTime datetime) { } [CapSubscribe("xxx.services.show.time", Group = "group2")] public void ShowTime2(DateTime datetime) { }
ShowTime1 和 ShowTime2 处于不同的组,他们将会被同时调用。
PS,你可以通过下面的方式来指定默认的消费者组名称:
services.AddCap(x => { x.DefaultGroup = "default-group-name"; });
七、Dashboard
CAP 2.1+ 以上版本中提供了仪表盘(Dashboard)功能,你可以很方便的查看发出和接收到的消息。除此之外,你还可以在仪表盘中实时查看发送或者接收到的消息。
在分布式环境中,仪表盘内置集成了 Consul 作为节点的注册发现,同时实现了网关代理功能,你同样可以方便的查看本节点或者其他节点的数据,它就像你访问本地资源一样。
services.AddCap(x => { //... // 注册 Dashboard x.UseDashboard(); // 注册节点到 Consul x.UseDiscovery(d => { d.DiscoveryServerHostName = "localhost"; d.DiscoveryServerPort = 8500; d.CurrentNodeHostName = "localhost"; d.CurrentNodePort = 5800; d.NodeId = 1; d.NodeName = "CAP No.1 Node"; }); });
仪表盘默认的访问地址是:http://localhost:xxx/cap,你可以在d.MatchPath配置项中修改cap路径后缀为其他的名字。
参考资料:https://github.com/dotnetcore/CAP/blob/master/README.zh-cn.md