在之前的文章中,我介绍了 Brighter 与 Kafka 的集成 和 Brighter V10 RC1 的特性。本文将重点指导如何迁移到 Brighter V10,强调 Kafka 配置的变更和突破性更新。
环境要求
- .NET 8 或更高版本
- .NET 项目需包含以下 NuGet 包:
- Paramore.Brighter.MessagingGateway.Kafka:启用 Kafka 集成。
- Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection:通过 Microsoft DI 注册 Brighter。
- Paramore.Brighter.ServiceActivator.Extensions.Hosting:将 Brighter 作为后台服务运行。
- Serilog.AspNetCore:结构化日志支持(可选但推荐)。
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 布尔值改为枚举 MessagePumpType(Reactor, 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);
}
}
1027

被折叠的 条评论
为什么被折叠?



