迁移到 Brighter V10:Kafka 配置更新与突破性变更

在之前的文章中,我介绍了 Brighter 与 Kafka 的集成 和 Brighter V10 RC1 的特性。本文将重点指导如何迁移到 Brighter V10,强调 Kafka 配置的变更和突破性更新。

环境要求

Brighter 基础回顾

请求(命令/事件)

通过 IRequest 定义消息:

[PublicationTopic("greeting.topic")]
public class Greeting() : Event(Guid.NewGuid())
{
    public string Name { get; set; } = string.Empty;
}
  • 命令(Command):单接收者操作(如 SendEmail)。
  • 事件(Event):广播通知(如 OrderShipped)。

消息映射器(可选)

在异步工作流中,消息映射器需实现 IAmAMessageMapperAsync 接口。

请求处理器

处理传入消息:

public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
    public override Greeting Handle(Greeting command)
    {
        logger.LogInformation("Hello {Name}", command.Name);
        return base.Handle(command);
    }
}

配置 Brighter 与 Kafka

1. 连接配置

定义 Kafka 连接参数:

var connection = new KafkaMessagingGatewayConfiguration
{
    Name = "sample", // 应用名称
    BootStrapServers = ["localhost:9092"], // 代理地址
    SecurityProtocol = SecurityProtocol.Plaintext, // 生产环境使用 SSL
    SaslMechanisms = SaslMechanism.Plain,
};

2. 订阅 Kafka 主题

订阅主题并配置消费者组:

.AddServiceActivator(opt =>
{
    opt.Subscriptions =
    [
        new KafkaSubscription<Greeting>(
            new SubscriptionName("kafka.greeting.subscription"), // 内部使用的订阅名称
            new ChannelName("greeting.topic"), // 主题名称
            new RoutingKey("greeting.topic"),  // 路由键
            groupId: "some-consumer-group", // Kafka 消费者组 ID
            makeChannels: OnMissingChannel.Create, // 主题不存在时自动创建
            numOfPartitions: 2, // 主题分区数(仅代码创建时有效)
            noOfPerformers: 2, // 并发消费者数(不应超过分区数)
            messagePumpType: MessagePumpType.Proactor // 消息泵类型
        ),
    ];
    opt.DefaultChannelFactory = new ChannelFactory(new KafkaMessageConsumerFactory(connection));
})

3. 发布事件到 Kafka

配置生产者:

.UseExternalBus(opt =>
{
    opt.ProducerRegistry = new KafkaProducerRegistryFactory(connection,
    [
        new KafkaPublication
        {
            MakeChannels = OnMissingChannel.Create,
            Source = new Uri("test-app", UriKind.RelativeOrAbsolute), // CloudEvents 属性
            Topic = new RoutingKey("greeting.topic")
        }
    ]).Create();
})

新功能

CloudEvents 支持

Brighter 现在原生支持 CloudEvents,可通过 Publication 配置源(Source)、主题(Subject)、类型(Type)等属性。默认使用二进制模式,若需 JSON 模式:
 

// 全局使用 CloudEvent JSON
.MapperRegistry(registry => registry.SetCloudEventJsonAsDefaultMessageMapper())

// 或按类型注册
.MapperRegistry(registry => registry.Register<Greeting, CloudEventJsonMessageMapper<Greeting>>())

PublicationTopic 特性

通过特性标记默认发布主题:

[PublicationTopic("greeting.topic")]
public class Greeting() : Event(Guid.NewGuid()) { ... }

// 或显式声明
new KafkaPublication<Greeting> { ... }

Brighter V10 突破性变更

默认值调整

为避免 "Local queue is full" 错误,默认值大幅优化:

  • KafkaPublication.BatchNumberMessages 从 10 提升至 10000
  • KafkaPublication.QueueBufferingMaxMessages 从 10 提升至 100000

消息映射器重构

  • 内置 JSON 序列化:V10 移除强制自定义映射器,除非需要特殊逻辑。
  • 默认序列化配置:可通过 SetCloudEventJsonAsDefaultMessageMapper 或 Register<T> 修改。

订阅配置变更

显式消息泵类型

isAsync 布尔值改为枚举 MessagePumpTypeReactor, Proactor, Unknown)。

属性重命名

AddServiceActivator 中的 ChannelFactory 重命名为 DefaultChannelFactory。### 生产者配置变更
使用 ExternalBusConfiguration 统一配置生产者和发件箱模式:

// V10
.UseExternalBus(opt => { ... })

// V9
.UseExternalBus(new RmqProducerRegistryFactory(...))

Kafka 最佳实践

优先使用 Post 而非 PostAsync:减少异步开销(吞吐量提升可达 100 倍以上)。

总结

Brighter V10 通过 CloudEvents 支持、优化默认值和简化配置提升了 Kafka 集成体验。突破性变更聚焦代码清晰性,但需更新订阅、生产者和序列化逻辑。完整示例代码请参考 GitHub 仓库

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessagingGateway.Kafka;
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.ServiceActivator.Extensions.Hosting;
using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateLogger();

var host = new HostBuilder()
    .UseSerilog()
    .ConfigureServices(
        (_, services) =>
        {
            var connection = new KafkaMessagingGatewayConfiguration
            {
                Name = "sample",
                BootStrapServers = ["localhost:9092"],
                SaslUsername = "admin",
                SaslPassword = "admin-secret",
                SecurityProtocol = SecurityProtocol.Plaintext,
                SaslMechanisms = SaslMechanism.Plain,
            };

            services
                .AddHostedService<ServiceActivatorHostedService>()
                .AddServiceActivator(opt =>
                {
                    opt.Subscriptions =
                    [
                        new KafkaSubscription<Greeting>(
                            new SubscriptionName("kafka.greeting.subscription"),
                            new ChannelName("greeting.topic"),
                            new RoutingKey("greeting.topic"),
                            groupId: "some-consumer-group",
                            makeChannels: OnMissingChannel.Create
                        )
                    ];

                    opt.DefaultChannelFactory = new ChannelFactory(
                        new KafkaMessageConsumerFactory(connection)
                    );
                })
                .AutoFromAssemblies()
                .MapperRegistry(registry => registry.SetCloudEventJsonAsDefaultMessageMapper())
                .UseExternalBus(opt =>
                {
                    opt.ProducerRegistry = new KafkaProducerRegistryFactory(
                        connection,
                        [
                            new KafkaPublication
                            {
                                MakeChannels = OnMissingChannel.Create,
                                Source = new Uri("test-app", UriKind.RelativeOrAbsolute),
                                Topic = new RoutingKey("greeting.topic")
                            }
                        ]
                    ).Create();
                });
        }
    )
    .Build();

_ = host.RunAsync();

while (true)
{
    await Task.Delay(TimeSpan.FromSeconds(10));
    Console.Write("Say your name (or q to quit): ");
    var name = Console.ReadLine();

    if (string.IsNullOrEmpty(name))
    {
        continue;
    }

    if (name == "q")
    {
        break;
    }

    var process = host.Services.GetRequiredService<IAmACommandProcessor>();
    process.Post(new Greeting { Name = name });
}

await host.StopAsync();

[PublicationTopic("greeting.topic")]
public class Greeting() : Event(Guid.NewGuid())
{
    public string Name { get; set; } = string.Empty;
}

public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
    public override Greeting Handle(Greeting command)
    {
        logger.LogInformation("Hello {Name}", command.Name);
        return base.Handle(command);
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值