学海无涯

导航

统计

用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}");
       }
   }

  

 

posted on   宁静致远.  阅读(355)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示