用CAP操作RabbitMQ 处理分布式事务的解决方案
一、在Nuget中引用以下包:
1 2 3 4 | dotnetcore.cap DotNetCore.CAP.Dashboard DotNetCore.CAP.RabbitMQ DotNetCore.CAP.SqlServer |
二、在Program.cs中注册服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //配置CAP builder.Services.AddCap(x => { x.UseEntityFramework<AppDbContext>(); //从AppDbContext获取数据库连接字符串 x.UseRabbitMQ( "localhost" );<br> 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" ; }); }); |
三、发布消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | 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 在控制器中订阅消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
1 | builder.Services.AddTransient<ISubscriberService, SubscriberService>(); |
注意:注册必须放在配置CAP之前,即 builder.Services.AddCap() 之前
五、组订阅
同一消息在同一个组里面只有一订阅能收到消息,但在不同的组多个订阅都能收到消息。
订阅组的概念类似于 Kafka 中的消费者组。与消息队列中的广播方式相同,用于处理多个不同微服务实例之间的同一条消息。
CAP 启动时,会使用当前的程序集名称作为默认的组名,如果多个同组订阅者订阅同一个主题名,则只有一个订阅者可以接收到消息。相反,如果订阅者在不同的组中,他们都会收到消息。
在同一个应用程序中,您可以指定Group
属性以将订阅保存在不同的订阅组中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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")]
1 2 3 4 5 6 7 8 9 10 11 12 13 | [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}" ); } } |
分类:
RabbitMQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!