14.分布式事件总线MassTransit的简单使用
简介:
MassTransit,直译公共交通, 是由 Chris Patterson 开发的基于消息驱动的.NET 分布式应用框架,其核心思想是借助消息来实现服务之间的松耦合异步通信,进而确保应用更高的可用性、可靠性和可扩展性。通过对消息模型的高度抽象,以及对主流的消息代理(包括RabbitMQ、ActiveMQ、Kafaka、Azure Service Bus、Amazon SQS等)的集成,大大简化了基于消息驱动的开发门槛,同时内置了连接管理、消息序列化和消费者生命周期管理,以及诸如重试、限流、断路器等异常处理机制,让开发者更好的专注于业务实现。
简而言之,MassTransit实现了消息代理透明化。无需面向消息代理编程进行诸如连接管理、队列的申明和绑定等操作,即可轻松实现应用间消息的传递和消费。
新添加一个项目EventBus.MassTraint(http://localhost:5129)【发布】
安装包:
MassTransit
MassTransit.RabbitMQ
Microsoft.AspNetCore.OpenApi
Swashbuckle.AspNetCore
添加一个MQ配置类RabbitMQOptions
public class RabbitMQOptions { /// <summary> /// Default password (value: "guest"). /// </summary> /// <remarks>PLEASE KEEP THIS MATCHING THE DOC ABOVE.</remarks> public const string DefaultPass = "guest"; /// <summary> /// Default user name (value: "guest"). /// </summary> /// <remarks>PLEASE KEEP THIS MATCHING THE DOC ABOVE.</remarks> public const string DefaultUser = "guest"; /// <summary> /// Default virtual host (value: "/"). /// </summary> /// <remarks> PLEASE KEEP THIS MATCHING THE DOC ABOVE.</remarks> public const string DefaultVHost = "/"; /// <summary> /// The host to connect to. /// If you want connect to the cluster, you can assign like “192.168.1.111,192.168.1.112” /// </summary> public string HostName { get; set; } = "localhost"; /// <summary> /// Password to use when authenticating to the server. /// </summary> public string Password { get; set; } = DefaultPass; /// <summary> /// Username to use when authenticating to the server. /// </summary> public string UserName { get; set; } = DefaultUser; /// <summary> /// Virtual host to access during this connection. /// </summary> public string VirtualHost { get; set; } = DefaultVHost; /// <summary> /// The port to connect on. /// </summary> public ushort Port { get; set; } }
appsettings.Development.json配置
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "RabbitMQ": { "HostName": "127.0.0.1", "VirtualHost": "/", "UserName": "admin", "Password": "你的密码", "Port": "5672" } }
Program.cs注入
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddSwaggerGen(); var rabbitConfig = builder.Configuration.GetSection("RabbitMQ"); builder.Services.Configure<RabbitMQOptions>(rabbitConfig); var rabbitOptions = rabbitConfig.Get<RabbitMQOptions>(); builder.Services.AddMassTransit(p => { p.UsingRabbitMq((context, mqConfig) => { mqConfig.Host( host: rabbitOptions.HostName, // 配置主机地址 port: rabbitOptions.Port, virtualHost: rabbitOptions.VirtualHost, // 虚拟主机 configure: hostConfig => { hostConfig.Username(rabbitOptions.UserName); hostConfig.Password(rabbitOptions.Password); }); mqConfig.ConfigureEndpoints(context); }); }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
添加一个发布的控制器PublishController
[Route("[controller]/[action]")] [ApiController] public class PublishController:ControllerBase { private readonly IBus _bus; public PublishController(IBus bus) { _bus = bus; } // 生产者、发布。执行1.0 [HttpGet] public ActionResult PublishMsg() { _bus.Publish(new UserInfo {Id = 1, NickName = "张三"}); return Ok("发布成功"); }
//批量请求接口 [HttpGet] public ActionResult PublishBatchMsg() { _bus.Publish(new BatchMsg {Id = Thread.CurrentThread.ManagedThreadId}); return Ok("发布成功"); } }
新添加一个项目EventBus.MassTransit.Subscribe(http://localhost:5166)【订阅】
安装包:
MassTransit.RabbitMQ
Microsoft.AspNetCore.OpenApi
Swashbuckle.AspNetCore
MQ配置类RabbitMQOptions照抄下来
public class RabbitMQOptions { /// <summary> /// Default password (value: "guest"). /// </summary> /// <remarks>PLEASE KEEP THIS MATCHING THE DOC ABOVE.</remarks> public const string DefaultPass = "guest"; /// <summary> /// Default user name (value: "guest"). /// </summary> /// <remarks>PLEASE KEEP THIS MATCHING THE DOC ABOVE.</remarks> public const string DefaultUser = "guest"; /// <summary> /// Default virtual host (value: "/"). /// </summary> /// <remarks> PLEASE KEEP THIS MATCHING THE DOC ABOVE.</remarks> public const string DefaultVHost = "/"; /// <summary> /// The host to connect to. /// If you want connect to the cluster, you can assign like “192.168.1.111,192.168.1.112” /// </summary> public string HostName { get; set; } = "localhost"; /// <summary> /// Password to use when authenticating to the server. /// </summary> public string Password { get; set; } = DefaultPass; /// <summary> /// Username to use when authenticating to the server. /// </summary> public string UserName { get; set; } = DefaultUser; /// <summary> /// Virtual host to access during this connection. /// </summary> public string VirtualHost { get; set; } = DefaultVHost; /// <summary> /// The port to connect on. /// </summary> public ushort Port { get; set; } }
appsettings.Development.json配置
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "RabbitMQ": { "HostName": "127.0.0.1", "VirtualHost": "/", "UserName": "admin", "Password": "你的密码", "Port": "5672" } }
Program.cs注入
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddSwaggerGen(); var rabbitConfig = builder.Configuration.GetSection("RabbitMQ"); builder.Services.Configure<RabbitMQOptions>(rabbitConfig); var rabbitOptions = rabbitConfig.Get<RabbitMQOptions>(); builder.Services.AddMassTransit(p => { // 批量注册消费者,类名以Consumer结尾和此类继承IConsumer接口,才能注册 p.AddConsumers(t=>t.Name.EndsWith("Consumer") && t.GetInterface("IConsumer")!=null ,Assembly.GetExecutingAssembly()); // 添加消费者与ConsumerDefinition // p.AddConsumer<SubscribeConsumer, SubscribeConsumerDefinition>(p => // {
// //里面可以做并发数,消息,过滤,限流,超时,事务,重试,熔断,并发限制,延迟 // p.UseMessageRetry(r=>r.Interval(5,TimeSpan.FromSeconds(3))); // // });
//批量请求处理数设置 // p.AddConsumer<BatchMsgConsumer>(b => // { // b.Options<BatchOptions>(opt => // { // opt.SetMessageLimit(30); // }); // }); p.UsingRabbitMq((context, mqConfig) => { mqConfig.Host( host: rabbitOptions.HostName, // 配置主机地址 port: rabbitOptions.Port, virtualHost: rabbitOptions.VirtualHost, // 虚拟主机 configure: hostConfig => { hostConfig.Username(rabbitOptions.UserName); hostConfig.Password(rabbitOptions.Password); }); mqConfig.ConfigureEndpoints(context); }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthorization(); app.MapControllers(); app.Run();
添加一个类SubscribeConsumer用于消费PublishController控制器里的PublishMsg方法。
// 消费者、订阅。执行1.1 // Message : 只能是接口,记录(record),类, 换句话说,只能是引用类型 public class SubscribeConsumer:IConsumer<UserInfo> { // 由MQ自动调用 public Task Consume(ConsumeContext<UserInfo> context) { UserInfo user = context.Message; Console.WriteLine(user.NickName); return Task.CompletedTask; } }
批量处理
添加一个类,用于批量处理类型。
/// <summary> /// MassTransit 批量处理消息的类型 /// </summary> [EntityName("batch-msg")] public class BatchMsg { public int Id { get; set; } }
消费、订阅
/// <summary> /// 批量处理消息 /// </summary> public class BatchMsgConsumer:IConsumer<Batch<BatchMsg>> { public Task Consume(ConsumeContext<Batch<BatchMsg>> context) { foreach (var consumeContext in context.Message) { Console.WriteLine(consumeContext.Message.Id); } Console.WriteLine($"{context.Message.Length}==================================="); return Task.CompletedTask; } }
打开jmeter工具做批量请求。
设置批量请求数,此方法内可以做并发数,消息,过滤,限流,超时,重试,事务,熔断,并发限制,延迟等。
public class BatchMsgConsumerDefinition:ConsumerDefinition<BatchMsgConsumer> { protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator , IConsumerConfigurator<BatchMsgConsumer> consumerConfigurator, IRegistrationContext context) { // endpointConfigurator.ConcurrentMessageLimit = 8;// 并发数据限制,默认是1 // endpointConfigurator.PrefetchCount = 5; // 每次同时可以执行多少条(取决于CPU的核数) consumerConfigurator.Options<BatchOptions>(opt => { opt.MessageLimit = 30; }); } }
重试
// 因为Program中AddConsumers 会将这个ConsumerDefinition一起添加至容器中来 public class SubscribeConsumerDefinition:ConsumerDefinition<SubscribeConsumer> { protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator<SubscribeConsumer> consumerConfigurator, IRegistrationContext context) { // 重试(可以解决数据库/网络异常 等 短暂性的异常),重试2次,一秒执行一次 endpointConfigurator.UseMessageRetry(r=>r.Interval(2,TimeSpan.FromSeconds(1))); /** * 如果执行失败,则会先进行重试,如果重试次数用完了,则会在设置的时间内进行消息重发 * 使用场景:为了防止服务器宕机的情况 */ // endpointConfigurator.UseDelayedRedelivery(r=>r.Interval(1,TimeSpan.FromSeconds(5))); } }
脏数据,补偿机制
// 如果消息重试之后,还是执行失败,则会进入此类来 public class SubscribeFaultConsumer:IConsumer<Fault<UserInfo>> { // 我们可以在这里做一些补偿,或者记录/存储这条失败的数据 public Task Consume(ConsumeContext<Fault<UserInfo>> context) { var user = context.Message.Message; Console.WriteLine(user.Id); return Task.CompletedTask; } }