学海无涯

导航

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

  

 

posted on 2022-10-05 20:32  宁静致远.  阅读(337)  评论(0编辑  收藏  举报