【开发技术】Abp.vnext中使用分布式事件总线

目录

一、目的

二、什么是事件总线

2.1 Abp.vnext提供两种事务总线。

2.2 如何选择自己想要使用的事件总线

2.3 为什么使用事件总线

三、Abp.vnext中配置分布式事务总线

3.1 安装事件总线Nuget包(Volo.Abp.EventBus.RabbitMQ)

3.2 在appsettings.json文件配置

3.3 应用层添加组件依赖

3.4 配置RabbitMQ的连接字符串和事件总线选项

3.5 创建事件对象Eto,用于Publish发布

3.6事件名称EventName

四、发布和订阅分布式事件总线

4.1 发布分布式事件

4.1.1 在聚合根方法内发布分布式事件

IGeneratesDomainEvents 接口

4.1.2 使用IDistributedEventBus发布事件

4.2 订阅事件

五、文章总结


一、目的

        当前公司需要与某公司提供AI分析服务,准备开发一个对外开放平台。整体架构不多赘述。需求大致是:
1、用户通过Api发送分析请求。
2、用户通过Api查询分析结果。
3、提供分析状态查询接口(分析服务消耗时间和资源过大,分析请求排队异步处理)。
        这里通过使用 分布式事务总线+Rabbitmq,实现 Api服务分析服务之间的通信.

二、什么是事件总线

        事件总线是一个调解人,可将消息从发送者传输到接收者。这样,它在对象,服务和应用程序之间提供了一种松散的耦合方式。

2.1 Abp.vnext提供两种事务总线。

        本地事件总线:同一进程中,不同方法 过程中的消息传递。
        例如:创建账号后,需要发送邮件给用户。如果是单体项目,此时可以把发送邮件功能,通过事务总线,传递给邮件服务进行处理,进行服务解耦。
        分布式事件 总线(Distributed Transaction Coordinator, DTC): ‌是一种用于协调分布式事务的中间件服务。它负责管理跨多个系统或服务的数据库事务,确保事务的一致性和完整性。分布式事务总线通过协调不同系统中的事务执行,确保所有参与的系统在事务提交或回滚时保持一致‌。 适用于 过程间消息传递,例如在分布式/微服务系统中发布和订阅分布式事件。

2.2 如何选择自己想要使用的事件总线

  1. 如果您要构建微服务系统,请使用分布式事件总线通信。
  2. 如果您要发布一个事件并始终以同一微服务进行处理,则可以将本地活动总线用于该事件。
  3. 如果您要构建模块化整体应用程序,请使用分布式事件总线模块间通信。由于默认情况下,分布式事务总线在过程中工作(除非您配置了真实的分布式事件总线提供商)。这样,如果您以后迁移到微服务系统,这些模块间通信可能会成为无需更改的无代码(仅安装分布式事件总线提供包装包)而成为一种微服务通信。如果您要发布一个事件并始终在同一模块中进行处理,则可以将本地事件总线用于该事件。
  4. 如果要构建整体(非模块化)应用程序,则可以随时使用本地事件总线
ps:下图为abp.io官网对于选择的解答。实际上官网强烈推荐的是使用 IDistributedEventBus 分布式事件总线模块间通信. 因为中间件会根据你的实际 配置 进行“本地”和“分布式”事务总线的选择

2.3 为什么使用事件总线

  1. 解耦合(轻松的实现系统间解耦)
  2. 高性能可扩展(每一个事件都是简单独立且不可更改的对象,只需要保存新增的事件,不涉及其他的变更删除操作)

三、Abp.vnext中配置分布式事务总线

3.1 安装事件总线Nuget包(Volo.Abp.EventBus.RabbitMQ)

3.2 在appsettings.json文件配置

示例:连接到具有默认配置的本地RabbitMQ服务器的最小配置
{
  "RabbitMQ": {
    "EventBus": {
      "ClientName": "MyClientName",//应用程序的名称,用作队列名称在消息队列上
      "ExchangeName": "MyExchangeName"//交换机名称
    }
  }
}
示例:指定主机名(作为IP地址)和
{
    "RabbitMQ": {
    "Connections": {
      "Default": {
        "HostName": "ip地址",
        "Port": "端口号",
        "UserName": "账号",
        "Password": "密码",
        "VirtualHost": "虚拟主机(服务隔离)"
      }
    },
    "EventBus": {
      "ClientName": "Cloud.Api.Result",
      "ExchangeName": "Cloud.Ultra.Service.Exchange"
    }
  },
}

示例:连接到消息队列集群
{
  "RabbitMQ": {
    "Connections": {
      "Default": {
        "HostName": "123.123.123.123;234.234.234.234"
      }
    },
    "EventBus": {
      "ClientName": "MyClientName",
      "ExchangeName": "MyExchangeName"
    }
  }
}

3.3 应用层添加组件依赖

3.4 配置RabbitMQ的连接字符串和事件总线选项

示例:配置连接
        // RabbitMQ连接配置
        Configure<AbpRabbitMqOptions>(options =>
        {
            options.Connections.Default.HostName = configuration["RabbitMQ:Connections:Default:HostName"];
            options.Connections.Default.Port = Convert.ToInt32(configuration["RabbitMQ:Connections:Default:Port"]);  // 默认端口
            options.Connections.Default.UserName = configuration["RabbitMQ:Connections:Default:UserName"];
            options.Connections.Default.Password = configuration["RabbitMQ:Connections:Default:Password"];
            options.Connections.Default.VirtualHost = configuration["RabbitMQ:Connections:Default:VirtualHost"];
        });

3.5 创建事件对象Eto,用于Publish发布

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.EventBus;

namespace AbpDemo.Application.Contracts.Etos
{
    /// <summary>
    /// 上传分析事件
    /// </summary>
    [EventName(MqttAnalyseServerEvents.AnalyseUploadClientEventName)]
    public class AnalyseUploadEto
    {
        /// <summary>
        /// 唯一编号
        /// </summary>
        public string Guid { get; set; }

        /// <summary>
        /// 脏器类型
        /// </summary>
        public string OrganType { get; set; }
        
        /// <summary>
        /// 请求数据
        /// </summary>
        public string Data { get; set; }
    }
}
        即使你不需要传输任何数据也需要创建一个类(在这种情况下为空类).
        Eto 是我们按照约定使用的 Event Transfer Objects(事件传输对象)的后缀. s虽然这不是必需的,但我们发现识别这样的事件类很有用(就像应用层上的 DTO 一样).
        ps:事件传输对象 必须是可序列化的,因为将其传输到进程外时,它们将被序列化/反序列化为JSON或其他格式.
        避免循环引用,多态,私有setter,并提供默认(空)构造函数,如果你有其他的构造函数.(虽然某些序列化器可能会正常工作),就像DTO一样.

3.6事件名称EventName

EventName attribute是可选的,但建议使用. 如果不声明,事件名将事件名称将是事件类的全名. 这里是 AbpDemo.AnalyseUpload.Server .EventName
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbpDemo.Application.Contracts.Etos
{
    public class MqttAnalyseServerEvents
    {

        #region 统一私有前缀
        private const string AnalyseUploadPrefix = "AbpDemo.AnalyseUpload.Server";
        private const string AnalyseResultPrefix = "AbpDemo.AnalyseResult.Server";
        #endregion

        /// <summary>
        /// 上传分析事件名
        /// </summary>
        public const string AnalyseUploadClientEventName = $"{AnalyseUploadPrefix}.EventName";

        /// <summary>
        /// 分析结果事件名
        /// </summary>
        public const string AnalyseResultClientEventName = $"{AnalyseResultPrefix}.EventName";
    }
}

四、发布和订阅分布式事件总线

4.1 发布分布式事件

4.1.1 在聚合根方法内发布分布式事件

using System;
using Volo.Abp.Domain.Entities;

namespace AbpDemo
{
    public class ProductDemo : AggregateRoot<Guid>
    {
        public string Name { get; set; }
        
        public int StockCount { get; private set; }

        private Product() { }

        public Product(Guid id, string name)
            : base(id)
        {
            Name = name;
        }

        public void ChangeStockCount(int newCount)
        {
            StockCount = newCount;
            
            //ADD an EVENT TO BE PUBLISHED
            AddDistributedEvent(
                new StockCountChangedEto
                {
                    ProductId = Id,
                    NewCount = newCount
                }
            );
        }
    }
}
        AggregateRoot 类定义了 AddDistributedEvent 来添加一个新的分布式事件,事件在聚合根对象保存(创建,更新或删除)到数据库时发布.
        如果实体发布这样的事件,以可控的方式更改相关属性是一个好的实践,就像上面的示例一样 - StockCount只能由保证发布事件的 ChangeStockCount 方法来更改.
IGeneratesDomainEvents 接口
        实际上添加分布式事件并不是 AggregateRoot 类独有的. 你可以为任何实体类实现 IGeneratesDomainEvents. 但是 AggregateRoot 默认实现了它简化你的工作.
        不建议为不是聚合根的实体实现此接口,因为它可能不适用于此类实体的某些数据库提供程序. 例如它适用于EF Core,但不适用于MongoDB.
4.1.2 使用IDistributedEventBus发布事件

using System;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectMapping;

namespace AbpDemo.BaseService.Application;

public class UploadAppService : ApplicationService, IUploadAppService
{
    private readonly IDistributedEventBus _distributedEventBus;
    private readonly ILogger<UploadAppService> _logger;

    public UploadAppService(IDistributedEventBus distributedEventBus,
        ILogger<UploadAppService> logger)
    {
        _logger = logger;
        _distributedEventBus = distributedEventBus;
    }

    /// <summary>
    /// 分布式事件发布
    /// </summary>
    /// <returns></returns>
    public async Task<string> PublishMQAsync(AnalyseUploadEto analyseUploadEto)
    {
        var result = string.Empty;
        try
        {
            await _distributedEventBus.PublishAsync(analyseUploadEto);
        }
        catch (Exception ex)
        {
            _logger.LogError($"异常!Message:{ex.Message} \r\n stack:{ex.StackTrace}");
            result = ex.Message;
        }
        return result;
    }

}

4.2 订阅事件

订阅自动事件与订阅常规分布式事件相同.
using AbpDemo.BaseService.Application.Contracts;
using AbpDemo.BaseService.Application.Contracts.Etos;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;

namespace AbpDemo.BaseService.Application.Services
{
    public class AnalyseResultEventHandler : IDistributedEventHandler<AnalyseResultEto>, ITransientDependency
    {
        private readonly ILogger<AnalyseResultEventHandler> _logger;
        private readonly AnalysisAppService _analysisAppService;

        public AnalyseResultEventHandler(AnalysisAppService analysis_THAppService, ILogger<AnalyseResultEventHandler> logger)
        {
            _analysisAppService = analysisAppService;
            _logger = logger;
        }

        /// <summary>
        /// 处理逻辑
        /// </summary>
        /// <param name="eventData"></param>
        /// <returns></returns>
        public async Task HandleEventAsync(AnalyseResultEto eventData)
        {
            try
            {
                var clientId = eventData.Guid;
                //TODO:业务逻辑处理
                var res = await _analysisAppService.SaveAnalysis(eventData);
                if (!string.IsNullOrEmpty(res))
                {
                    _logger.LogError(res);
                }
                _logger.LogInformation($"Client:{clientId} has connected the broker");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "处理分析结果事件异常");
            }
        }
    }
}
  • MyHandler 实现了 IDistributedEventHandler>.

五、文章总结

        事件总线 能够简化 各组件或进程间 的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题。
把之所学,以文载之。欢迎大家多多交流学习
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勿芮介

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值