.Net Core 代码中使用Azure Service Bus 去实现消息的发送和消费
前言
最近在项目中使用了Azure的
Service Bus来实现一些异步下载的功能。因为下载文件前我们会去做一些大数据量的查询和文件的生成,所以用户可能会要长时间的等待,体验不好,所以想到了用消息队列的方式来异步下载这些文件。
主要的思路和步骤是:
- 当用户下载的时候我们先去
创建一个任务的编号,这个编号保存在一个任务表中,任务表中还应该有任务的状态,创建时间等一些必要的信息的记录。- 获取到
任务编号后我们去给消息队列中发送一条消息,这个消息中必须带上我们的任务编号和一些必要的业务逻辑数据- 一旦消息发送成功后我们就将任务编号返回,用户端展示这个
任务编号,并且提示可以通过这个任务编号稍后去任务列表中心查询(任务列表中心就是将任务表中的状态和数据在某个页面展示出来)- 上面的步骤完成后,消息队列中的消息会被一个服务给消费掉,拿到消息中的任务编号和业务数据去处理对应的业务并且处理完成
更新任务表中的状态。- 任务列表中心回去轮询任务的状态(可以是通过
定时请求或者websocket的方式),一旦任务状态为完成,就提供一个下载的按钮,供用户去下载,这时候用户下载的文件就是已经查询完成并且生成好的文件了。
因为我们最近所有的项目都要上
Azure(微软的云),所以我这里用到的时候Azure的Service Bus(服务总线),所以我这里简单的记录下如何去使用Service Bus的代码。
Service Bus的使用
添加Azure.Messaging.SerivceBus的包
PM> Install-Package Azure.Messaging.ServiceBus -Version 7.2.1
在appsetting.json中添加AzureServiceBus的配置项,这里是连接字符串。具体的获取这个字符串的方式可以去看下这个官方文档
"ConnectionStrings": {
"AzureServiceBus": "Endpoint=sb://sb-sne2-its-das-acrp-dev.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=riczsDd4FmMul/6O2WesWTYrJ9gRD6uwOXJHOtV5Ulg="
},
创建一个client的类,我这边命名为
QueueService.cs
using Azure.Messaging.ServiceBus;
using Azure.Messaging.ServiceBus.Administration;
using Microsoft.Extensions.Configuration;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Dtt.DDP.App_Utilities.ServiceBus
{
public class QueueService : IQueueService
{
private readonly IConfiguration config;
public QueueService(IConfiguration config)
{
this.config = config;
}
public async Task SendMessagesAsync<T>(T serviceBusMessage, string queueName)
{
try
{
ServiceBusClient client = new ServiceBusClient(config.GetConnectionString("AzureServiceBus"));//创建一个servicebus的客户端
var options = new CreateQueueOptions(queueName)
{
EnableBatchedOperations = false//是否批量去处理消息
};
var administrationClient = new ServiceBusAdministrationClient(config.GetConnectionString("AzureServiceBus"));//这个类是用来管理(CRUD)队列和主题的
if (!(await administrationClient.QueueExistsAsync(queueName)))
{
await administrationClient.CreateQueueAsync(options);
}
ServiceBusSender sender = client.CreateSender(queueName);//生成发送器
var messageBody = JsonSerializer.Serialize(serviceBusMessage);//构建消息
// create a message that we can send
ServiceBusMessage message = new ServiceBusMessage(Encoding.UTF8.GetBytes(messageBody));
await sender.SendMessageAsync(message);
}
catch (System.Exception ex)
{
throw ex;
}
}
}
}
ctrl+enter为QueueService生成一个接口IQueueService解耦我们的消息队列的类的使用,也就是我们这里可以用其他的消息队列来实现,比如RabbitMQ,MSMQ等等
Startup.cs文件
ConfigureServices方法中配置服务注入
services.AddSingleton<IQueueService, QueueService>();
添加一个消费者的基类
Queuelistener,需要实现IHostedService的接口
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using Microsoft.Extensions.Configuration;
using System.Threading.Tasks;
using log4net;
using Azure.Messaging.ServiceBus;
namespace Dtt.DDP.App_Utilities.ServiceBus
{
public class QueueListener : IHostedService//这里因为我需要开启一个host服务所以需要实现这个接口
{
private static ILog Logger;
public string queueName;
protected ServiceBusClient serviceBusClient;
protected ServiceBusProcessor processor;
public QueueListener(IConfiguration configuration)
{
Logger = LogManager.GetLogger(typeof(QueueListener));
try
{
serviceBusClient = new ServiceBusClient(configuration.GetConnectionString("AzureServiceBus"));
}
catch (Exception ex)
{
Logger.Error($"Service Bus 初始化出错, error info : [ {ex.Message} ] ", ex);
}
}
public virtual Task ProcessMessagesAsync(ProcessMessageEventArgs arg1)//这里是处理消息的任务监听方法,我这里用了一个虚方法,具体的实现需要在业务类中去重写它。
{
throw new NotImplementedException();
}
// handle any errors when receiving messages
static Task ErrorHandler(ProcessErrorEventArgs args)
{
return Task.CompletedTask;
}
public async Task Register()
{
try
{
processor = serviceBusClient.CreateProcessor(queueName, new ServiceBusProcessorOptions());//创建消息处理器
processor.ProcessMessageAsync += ProcessMessagesAsync;//绑定消息处理任务
processor.ProcessErrorAsync += ErrorHandler;//绑定错误处理任务
await processor.StartProcessingAsync();//开启处理
}
catch (Exception ex)
{
Logger.Error($"Service Bus 注册消费者监听出错!Error: [ {ex.Message} ]", ex);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await Register();
//Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await processor.StopProcessingAsync();
await processor.CloseAsync();
await this.serviceBusClient.DisposeAsync();
}
}
}
新建一个类继承
QueueListener去重写虚方法ProcessMessagesAsync实现不同业务逻辑的处理。我这里是导出报表
using Azure.Messaging.ServiceBus;
using Dtt.DDP.App_Utilities.ServiceBus;
using Dtt.DDP.Areas.DDP1000_DeclationMaintenance.BLL;
using Dtt.DDP.Areas.DDP1000_DeclationMaintenance.Models;
using Dtt.DDP.Areas.ZZZ0000_Common.Enums;
using Dtt.DDP.Areas.ZZZ0000_Common.Utils;
using Dtt.DDP.Models;
using Dtt.Framework.App_Utilities.DI;
using Microsoft.Extensions.DependencyInjection;
using Dtt.Framework.Repository;
using log4net;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace Dtt.DDP.Areas.DDP1000_DeclationMaintenance.MQ
{
public class DeclarationReportListener : QueueListener
{
private static ILog _logger;
private readonly IServiceProvider _services;
private DeclarationListBLL bll;
private CommonRepository taskRepository;
public DeclarationReportListener(IServiceProvider services, IConfiguration configuration) : base(configuration)
{
_logger = LogManager.GetLogger(typeof(DeclarationReportListener));
base.queueName = EnumUtil.GetEnumDes(ServiceBusQueueEnum.ExportDeclaraationReport);
taskRepository = services.GetService<CommonRepository>();
bll = services.GetService<DeclarationListBLL>();
}
public override async Task ProcessMessagesAsync(ProcessMessageEventArgs args)
{
decimal taskID = 0;
try
{
_logger.Info($"{typeof(DeclarationReportListener).FullName} 开始消费, args: [ {args} ]");
//处理 message
// MQMessageModel
var jsonStr = Encoding.UTF8.GetString(args.Message.Body.ToArray());
MQMessageModel mQMessageModel = JsonSerializer.Deserialize<MQMessageModel>(jsonStr);
taskID = mQMessageModel.TaskID;
DeclarationReportSearchModel bizPara = JsonSerializer.Deserialize<DeclarationReportSearchModel>(mQMessageModel.BizMessageBody);
taskRepository.UpdateTaskToStart(taskID);
// do business
//string fileName =bll.ExportScheduleConflictDetail(bizPara, mQMessageModel.TaskCreatedBy);
string fileName = await bll.ExportDeclarationReport(bizPara, mQMessageModel.TaskCreatedBy);
taskRepository.UpdateTaskToSucceeded(taskID, fileName);
await args.CompleteMessageAsync(args.Message);
}
catch (Exception ex)
{
taskRepository.UpdateTaskToFailed(taskID, $"异步导出 Excel 出错!Error: [ {ex.Message} ]" + ex.StackTrace + ex.InnerException?.Message + ex.InnerException?.StackTrace);
_logger.Error($"异步导出 Excel 出错!Error: [ {ex.Message} ]", ex);
// 消息已经接受,处理完成
await Task.CompletedTask;
}
}
}
}
EnumUtil.GetEnumDes(ServiceBusQueueEnum.ExportDeclaraationReport)这段代码是为了通过枚举去定义不同的业务场景,具体的QueueName可以按照自己的业务去实现。
在Startup.cs中的
ConfigureServices方法中去启动一个host服务
services.AddHostedService<DeclarationReportListener>();
到这里,我们的Service bus基本就配置完成了,剩下就是在具体的业务中去调用发消息的方法。当然我上面的代码只是在我项目中的,可能具体到你们的项目中需要做一些修改和引用的添加,这里仅供参考~

浙公网安备 33010602011771号