用CAP操作RabbitMQ 处理分布式事务的解决方案
一、在Nuget中引用以下包:
dotnetcore.cap DotNetCore.CAP.Dashboard DotNetCore.CAP.RabbitMQ DotNetCore.CAP.SqlServer
二、在Program.cs中注册服务
//配置CAP
builder.Services.AddCap(x =>
{
x.UseEntityFramework<AppDbContext>();//从AppDbContext获取数据库连接字符串
x.UseRabbitMQ("localhost");
x.DefaultGroupName = "cap.queue.app";//默认组名称
// Register Dashboard
x.UseDashboard(); //使用仪表盘,默认网址:http://localhost:当前网站端口/cap
//x.UseDashboard(opt => { opt.PathMatch = "/mycap"; });//配置仪表盘为新的网址
// Register to Consul
x.UseDiscovery(d =>
{
d.DiscoveryServerHostName = "localhost";
d.DiscoveryServerPort = 8500;
d.CurrentNodeHostName = "localhost";
d.CurrentNodePort = 5800;
d.NodeId = 1.ToString();
d.NodeName = "CAP No.1 Node";
});
});
三、发布消息
using CAPWebApplication.Entities;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace CAPWebApplication.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PublishController : ControllerBase
{
private readonly ILogger<PublishController> _logger;
private readonly ICapPublisher _capPublisher;
private readonly AppDbContext _dbContext;
public PublishController(ILogger<PublishController> logger, ICapPublisher capPublisher, AppDbContext dbContext)
{
_logger = logger;
_capPublisher = capPublisher;
_dbContext = dbContext;
}
//发送消息
[HttpGet("send")]
public async Task<ActionResult> SendMessage()
{//发布无事务的消息,消息重要性低时可以使用
await _capPublisher.PublishAsync("test.show.time", DateTime.Now);
return Ok();
}
[HttpGet("sendheader")]
public async Task SendHeaderMessage()
{//发布带有包头的消息
var header = new Dictionary<string, string>
{
["my.header.first"] = "first",
["my.header.second"] = "second"
};
_capPublisher.Publish("test.show.time", DateTime.Now, header);
}
[Route("adonet/transaction")]
[HttpGet]
public async Task AdonetWithTransaction()
{//ADO.NET 事务处理 操作数据库并且发送消息
using (var conn = new SqlConnection(_dbContext.Database.GetConnectionString()))
{
using (var trans = conn.BeginTransaction(_capPublisher))
{
await _capPublisher.PublishAsync("test.order", DateTime.Now);
trans.Commit();
}
}
}
[Route("efcore/transaction")]
[HttpGet]
public async Task EfcoreWithTransaction()
{//EFCore 事件处理 操作数据库并且发送消息
using (var trans = _dbContext.Database.BeginTransaction(_capPublisher))
{
Order order = new Order { Name = "赵六", Address = "湖南岳阳临湘" };
_dbContext.Add(order);
await _dbContext.SaveChangesAsync();
await _capPublisher.PublishAsync("test.order", order);
await trans.CommitAsync();
}
}
}
}
四、订阅消息
4.1 在控制器中订阅消息
using CAPWebApplication.Entities;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace CAPWebApplication.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ConsumerController : ControllerBase
{
//private readonly ILogger _logger;
//public ConsumerController(ILogger logger)
//{
// _logger = logger;
//}
//[NonAction]
//[CapSubscribe("test.show.time")]
//public async Task ReceiveMessage(DateTime time)
//{
// Console.WriteLine($"接收到的时间 :{time}");
//}
[NonAction]
[CapSubscribe("test.show.time")]
public async Task ReceiveHeaderMessage(DateTime time, [FromCap] CapHeader header)
{
Console.WriteLine($"接收到的时间:{time}");
Console.WriteLine($"第一个头部:{header["my.header.first"]}");
Console.WriteLine($"第二个头部:{header["my.header.second"]}");
}
[NonAction]
[CapSubscribe("test.order")]
public async Task CheckReceivedMessage(Order order)
{
//string msg= System.Text.Json.JsonSerializer.Serialize(order);
Console.WriteLine($"接受来自[test.order]的订单:{order}");
}
}
}
4.2 在业务逻辑订阅消息
如果你的订阅方法不在Controller中,那么你的订阅类需要实现ICapSubscribe接口:
using CAPWebApplication.Entities;
using DotNetCore.CAP;
namespace CAPWebApplication.BusinessCode
{
public interface ISubscriberService
{
void CheckReceivedMessage(Order order);
}
public class SubscriberService : ISubscriberService, ICapSubscribe
{
[CapSubscribe("test.order")]
public void CheckReceivedMessage(Order order)
{
Console.WriteLine($"来自业务层的订阅:{order}");
}
}
}
然后在Program.cs中注册ISubscriberService
builder.Services.AddTransient<ISubscriberService, SubscriberService>();
注意:注册必须放在配置CAP之前,即 builder.Services.AddCap() 之前
五、组订阅
同一消息在同一个组里面只有一订阅能收到消息,但在不同的组多个订阅都能收到消息。
订阅组的概念类似于 Kafka 中的消费者组。与消息队列中的广播方式相同,用于处理多个不同微服务实例之间的同一条消息。
CAP 启动时,会使用当前的程序集名称作为默认的组名,如果多个同组订阅者订阅同一个主题名,则只有一个订阅者可以接收到消息。相反,如果订阅者在不同的组中,他们都会收到消息。
在同一个应用程序中,您可以指定Group属性以将订阅保存在不同的订阅组中:
using CAPWebApplication.Entities;
using DotNetCore.CAP;
namespace CAPWebApplication.BusinessCode
{
public interface ISubscriberService
{
void CheckReceivedMessage(Order order);
void CheckReceivedMessage2(Order order);
void CheckReceivedMessage3(Order order);
}
public class SubscriberService : ISubscriberService, ICapSubscribe
{
[CapSubscribe("test.order")]
public void CheckReceivedMessage(Order order)
{
Console.WriteLine($"来自业务层的订阅:{order}");
}
[CapSubscribe("test.order",Group ="group1")]
public void CheckReceivedMessage2(Order order)
{
Console.WriteLine($"来自业务层group1的订阅:{order}");
}
[CapSubscribe("test.order",Group ="group2")]
public void CheckReceivedMessage3(Order order)
{
Console.WriteLine($"来自业务层group2的订阅:{order}");
}
}
}
三个订阅都能收到同一消息

六、主题订阅
将类上的订阅键+方法上的订阅键,最终组合在一起
实际的订阅键为:[CapSubscribe("test.order")]
[CapSubscribe("test")]
[Route("api/[controller]")]
[ApiController]
public class ConsumerController : ControllerBase
{
[NonAction]
[CapSubscribe("order")]
public async Task CheckReceivedMessage(Order order)
{
//string msg= System.Text.Json.JsonSerializer.Serialize(order);
Console.WriteLine($"来自控制器的订阅消息:{order}");
}
}
浙公网安备 33010602011771号