EasyCore.EventBus 是面向 .NET 8 的轻量级事件总线库。统一本地(进程内)与分布式(跨进程)发布-订阅模型,通过可插拔适配器对接 RabbitMQ / Kafka / Pulsar / Redis Streams,帮助你快速落地事件驱动架构(EDA)。
仓库:github.com/RockyWang0521/EasyCore.EventBus
- 中文(当前文档)
- English: README.en.md
EasyCore.EventBus 解决「在 .NET 中用同一套 API 做进程内解耦与跨服务消息」的问题:
| 痛点 | EasyCore.EventBus 做法 |
|---|---|
| 本地与分布式两套写法 | 统一 IEvent + PublishAsync,本地 / 分布式仅 Handler 接口不同 |
| 消息中间件绑定深 | 核心抽象 + EventBus.* 适配器,按需引用 |
| Handler 手写注册繁琐 | EventTypeScanner 自动发现并注册 Handler |
| 消费失败难重试 | RetryCount / RetryInterval / FailureCallback + 消息头覆盖 |
| 想单独用 MQ 客户端 | EasyCore.RabbitMQ 等 infra 包可独立使用 |
| 原则 | 说明 |
|---|---|
| 低摩擦接入 | services.EasyCoreEventBus(...) 一条扩展方法即可 |
| 本地优先 | 不传 action 时仅注册 ILocalEventBus |
| 传输可插拔 | RabbitMQ / Kafka / Pulsar / Redis Streams 独立适配器 |
| 失败可感知 | 重试耗尽触发 FailureCallback,并写日志 |
| 扫描自动化 | 实现 ILocalEventHandler<T> / IDistributedEventHandler<T> 即自动入 DI |
EasyCore.EventBus/
├── src/
│ ├── EasyCore.EventBus/ # 核心:本地/分布式抽象、Dispatcher、HostedService
│ ├── EasyCore.EventBus.RabbitMQ/ # EventBus → RabbitMQ 适配器
│ ├── EasyCore.EventBus.Kafka/ # EventBus → Kafka 适配器
│ ├── EasyCore.EventBus.Pulsar/ # EventBus → Pulsar 适配器
│ ├── EasyCore.EventBus.RedisStreams/ # EventBus → Redis Streams 适配器
│ ├── EasyCore.RabbitMQ/ # RabbitMQ 基础设施(可独立使用)
│ ├── EasyCore.Kafka/
│ ├── EasyCore.Pulsar/
│ └── EasyCore.RedisStreams/
├── demo/
│ ├── Infra/ # 基础设施独立客户端 Demo(无 EventBus)
│ ├── RabbitMq/ # EventBus Publish + Subscribe
│ ├── Kafka/
│ ├── Pulsar/
│ ├── Redis/
│ └── Winform/ # 本地 / 分布式 WinForms 示例
├── tests/EasyCore.EventBus.Tests/
├── docs/svg/ # README 架构图 / 时序图
└── png/EasyCoreLogo.png
Publisher
│
├─ ILocalEventBus.PublishAsync ──────────► ILocalEventHandler<T>[]
│
└─ IDistributedEventBus.PublishAsync ────► IEventMessageQueueClient (MQ)
│
▼
EventBusHostedService
│
▼
DistributedEventDispatcher
│
▼
IDistributedEventHandler<T>[]
(重试 / FailureCallback)
三层结构:
| 层 | 包 | 职责 |
|---|---|---|
| 核心 | EasyCore.EventBus |
抽象、扫描、调度、HostedService |
| 适配器 | EasyCore.EventBus.* |
把 EventBus 接到具体中间件 |
| 基础设施 | EasyCore.* |
纯 MQ 客户端,可脱离 EventBus 使用 |
| 包名 | 职责 | 是否必须 |
|---|---|---|
EasyCore.EventBus |
核心:本地 / 分布式抽象、Dispatcher、自动扫描 | ✅ |
EasyCore.EventBus.RabbitMQ |
RabbitMQ 适配器 | 按需 |
EasyCore.EventBus.Kafka |
Kafka 适配器 | 按需 |
EasyCore.EventBus.Pulsar |
Pulsar 适配器 | 按需 |
EasyCore.EventBus.RedisStreams |
Redis Streams 适配器 | 按需 |
EasyCore.RabbitMQ |
RabbitMQ 基础设施客户端 | 可选(适配器会间接依赖) |
EasyCore.Kafka |
Kafka 基础设施客户端 | 可选 |
EasyCore.Pulsar |
Pulsar 基础设施客户端 | 可选 |
EasyCore.RedisStreams |
Redis Streams 基础设施客户端 | 可选 |
引用某个
EasyCore.EventBus.*适配器即可获得对应传输能力;若只需裸客户端 API,可单独引用EasyCore.RabbitMQ等 infra 包。
| 能力 | 本地 | RabbitMQ | Kafka | Pulsar | Redis Streams |
|---|---|---|---|---|---|
| 包 | 核心即可 | .RabbitMQ |
.Kafka |
.Pulsar |
.RedisStreams |
| 跨进程 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 典型场景 | 模块解耦 | 企业集成 / AMQP | 高吞吐日志流 | 云原生消息 | 轻量队列 |
| 配置入口 | — | options.RabbitMQ(...) |
options.Kafka(...) |
options.Pulsar(...) |
options.RedisStreams(...) |
是否需要跨进程 / 跨服务?
├── 否 → 仅 services.EasyCoreEventBus() + ILocalEventBus
└── 是 → 选已有中间件
├── RabbitMQ → EasyCore.EventBus.RabbitMQ
├── Apache Kafka → EasyCore.EventBus.Kafka
├── Apache Pulsar → EasyCore.EventBus.Pulsar
└── Redis Streams → EasyCore.EventBus.RedisStreams
| 项 | 要求 |
|---|---|
| .NET | 8.0+ |
| 宿主 | ASP.NET Core / Generic Host / WinForms + Hosting |
| 中间件 | 分布式场景需对应 Broker 可达 |
| DI | Microsoft.Extensions.DependencyInjection |
# 核心(本地 EventBus 必需)
dotnet add package EasyCore.EventBus
# 按需选择其一(或组合,视场景而定)
dotnet add package EasyCore.EventBus.RabbitMQ
dotnet add package EasyCore.EventBus.Kafka
dotnet add package EasyCore.EventBus.Pulsar
dotnet add package EasyCore.EventBus.RedisStreams定义事件(实现 IEvent):
using EasyCore.EventBus.Event;
public class OrderCreatedEvent : IEvent
{
public string OrderId { get; set; } = "";
}定义处理器(实现 ILocalEventHandler<T>,自动扫描注册):
using EasyCore.EventBus.Event;
public class OrderCreatedLocalHandler : ILocalEventHandler<OrderCreatedEvent>
{
public Task HandleAsync(OrderCreatedEvent eventMessage)
{
Console.WriteLine($"Local: {eventMessage.OrderId}");
return Task.CompletedTask;
}
}注册并发布:
using EasyCore.EventBus;
using EasyCore.EventBus.Local;
// 仅本地:不传 action 即可
services.EasyCoreEventBus();
// 注入后发布
public class OrderService(ILocalEventBus localEventBus)
{
public Task CreateAsync(string orderId) =>
localEventBus.PublishAsync(new OrderCreatedEvent { OrderId = orderId });
}订阅端 Program.cs:
using EasyCore.EventBus;
using EasyCore.EventBus.RabbitMQ;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.EasyCoreEventBus(options =>
{
options.RabbitMQ(opt =>
{
opt.HostName = "localhost";
opt.UserName = "guest";
opt.Password = "guest";
opt.Port = 5672;
});
options.RetryCount = 3;
options.RetryInterval = 3;
options.FailureCallback = (eventName, payload) =>
{
// 重试耗尽后的兜底(写日志 / 告警 / 入库)
Console.WriteLine($"Failed: {eventName}, payload={payload}");
};
});
var app = builder.Build();
app.MapControllers();
app.Run();也可简写:options.RabbitMQ("localhost");
事件 + 分布式处理器:
using EasyCore.EventBus.Event;
public class WebEventMessage : IEvent
{
public string? Message { get; set; }
}
public class MyEventHandler : IDistributedEventHandler<WebEventMessage>
{
private readonly ILogger<MyEventHandler> _logger;
public MyEventHandler(ILogger<MyEventHandler> logger) => _logger = logger;
public Task HandleAsync(WebEventMessage eventMessage)
{
_logger.LogInformation("Received: {Message}", eventMessage.Message);
return Task.CompletedTask;
}
}发布端:
using EasyCore.EventBus.Distributed;
[ApiController]
[Route("api/[controller]")]
public class PublishController(IDistributedEventBus distributedEventBus) : ControllerBase
{
[HttpPost]
public async Task Publish()
{
await distributedEventBus.PublishAsync(new WebEventMessage
{
Message = "Hello, world!"
});
}
}配置了传输扩展后,框架会注册
IDistributedEventBus与EventBusHostedService,在 Host 启动时连接并订阅。
| 概念 | 类型 | 说明 |
|---|---|---|
| 事件标记 | IEvent |
所有事件消息必须实现 |
| 本地总线 | ILocalEventBus |
进程内 PublishAsync |
| 本地处理器 | ILocalEventHandler<T> |
T : IEvent,同进程消费 |
| 分布式总线 | IDistributedEventBus |
PublishAsync / Publish 发往 MQ |
| 分布式处理器 | IDistributedEventHandler<T> |
跨进程消费 |
| 调度器 | DistributedEventDispatcher |
反序列化、多 Handler、重试 |
同一事件类型可注册多个 Handler;本地按顺序调用,分布式由 DistributedEventDispatcher 统一调度。
| 属性 | 默认 | 说明 |
|---|---|---|
RetryCount |
3 |
首次失败后的额外重试次数 |
RetryInterval |
3 |
重试间隔(秒) |
FailureCallback |
null |
重试耗尽(或反序列化失败)时回调 (eventTypeName, payloadJson?) |
总尝试次数 ≈ max(1, RetryCount + 1)。
| Header | 常量 | 含义 |
|---|---|---|
x-retry |
EventMessageHeaders.Retry |
覆盖本条消息的最大重试次数 |
x-retry-time |
EventMessageHeaders.RetryInterval |
覆盖重试间隔(秒) |
由 DistributedEventDispatcher.ParseRetryHeaders(...) 解析;缺失或非法时回退到 EventBusOptions 默认值。
options.RabbitMQ("localhost");
// 或
options.RabbitMQ(opt =>
{
opt.HostName = "localhost";
opt.UserName = "guest";
opt.Password = "guest";
opt.Port = 5672;
opt.ExchangeName = "EasyCore.EventBus";
opt.QueueName = "EasyCore.Queue";
opt.ExchangeType = "topic";
opt.VirtualHost = "/";
});options.Kafka("localhost:9092");
// 或
options.Kafka(opt =>
{
opt.BootstrapServers = "localhost:9092";
opt.TopicName = "EasyCore.Topic";
opt.GroupId = "EasyCore.GroupId";
});options.Pulsar("pulsar://localhost:6650");
// 或
options.Pulsar(opt =>
{
opt.ServiceUrl = "pulsar://localhost:6650";
});options.RedisStreams(new List<string> { "localhost:6379" });
// 或
options.RedisStreams(opt =>
{
opt.EndPoints = new List<string> { "localhost:6379" };
opt.Password = null;
opt.ConsumerGroup = "RedisGroup";
});| 目录 | 说明 | 命令示例 |
|---|---|---|
demo/RabbitMq |
EventBus:Web.RabbitMQ 订阅 + Web.RabbitMQ.Publish 发布 |
dotnet run --project demo/RabbitMq/Web.RabbitMQ |
demo/Kafka |
EventBus:Kafka 发布 / 订阅成对 | dotnet run --project demo/Kafka/Web.Kafka |
demo/Pulsar |
EventBus:Pulsar 发布 / 订阅成对 | dotnet run --project demo/Pulsar/Web.Pulsar |
demo/Redis |
EventBus:Redis Streams 发布 / 订阅 | dotnet run --project demo/Redis/Web.Redis |
demo/Winform |
本地 / 分布式 WinForms + Web | 打开对应 .csproj 运行 |
demo/Infra |
仅基础设施包:IRabbitMQClient / IKafkaClient / IPulsarClient / IRedisStreamsClient |
dotnet run --project demo/Infra/Web.Infra.RabbitMQ |
完整说明见 demo/README.md。
建议:
- EventBus:分别启动 Subscribe 与 Publish,对 Publish 的
POST /api/Publish(或/one、/batch)发请求。 - Infra:单进程即可,Swagger →
POST /api/messages;后台 HostedService 自动订阅并打日志。
- 凭证放入配置中心 / 密钥库,勿硬编码
- 按业务评估
RetryCount/RetryInterval,并实现FailureCallback(告警或死信落库) - 订阅端与发布端使用同一套事件类型与序列化约定
- 多实例消费时确认中间件的竞争消费 / Consumer Group 语义符合预期
- 监控 Broker 积压、连接断开与 Handler 异常日志
- 上线前用对应
demo/*在目标环境打通连通性 - 仅本地解耦时不要挂载传输扩展,避免无意义的 HostedService
Q: 入口方法是 AddAppEventBus 吗?
A: 不是。正确入口是 services.EasyCoreEventBus(options => { ... })。
Q: 只想用本地事件,还要装 RabbitMQ 包吗?
A: 不需要。仅引用 EasyCore.EventBus 并调用 services.EasyCoreEventBus() 即可。
Q: Handler 需要手动 AddTransient 吗?
A: 一般不需要。实现 ILocalEventHandler<T> / IDistributedEventHandler<T> 后,由 EventTypeScanner 在注册时自动扫描入 DI。
Q: 分布式 Handler 什么时候开始消费?
A: 配置了传输扩展后会注册 EventBusHostedService;Host 启动时 ConnectAsync + SubscribeAsync。
Q: RetryCount = 3 一共试几次?
A: 约 4 次(1 次首次 + 3 次额外重试)。消息头 x-retry 可覆盖该值。
Q: 可以同时配置多个传输吗?
A: 扩展点支持注册多个 IEventOptionsExtension,但实际以当前适配器实现为准;生产环境建议一个宿主绑定一种主传输,避免拓扑混乱。
Q: infra 包和适配器包有何区别?
A: EasyCore.EventBus.* 面向事件总线场景;EasyCore.RabbitMQ 等是底层客户端,可单独用于非 EventBus 场景。
MIT OR Apache-2.0 — 详见 LICENSE 与各包 PackageLicenseExpression。
你可以在 MIT 或 Apache-2.0 任一许可下使用本软件。
- Fork 并创建特性分支
- 在
tests/EasyCore.EventBus.Tests补充测试 - 执行
dotnet test与dotnet build EasyCore.EventBus.sln - 提交 Pull Request
欢迎 Issue / PR 🚀
