簡介
BufferQueue 是一個用 .NET 編寫的高效能的緩衝佇列實現,支援多執行緒併發操作。
原始碼網站:https://github.com/eventhorizon-cli/BufferQueue
功能設計
-
支援建立多個 Topic,每個 Topic 可以有多種資料型別。每一對 Topic 和資料型別對應一個獨立的緩衝區。
-
支援建立多個 Consumer Group,每個 Consumer Group 的消費進度都是獨立的。支援多個 Consumer Group 併發消費同一個 Topic。
-
支援同一個 Consumer Group 建立多個 Consumer,以負載均衡的方式消費資料。
-
支援資料的批次消費,可以一次性獲取多條資料。
-
支援 pull 模式和 push 模式兩種消費模式。
-
pull 模式下和 push 模式下都支援 auto commit 和 manual commit 兩種提交方式。auto commit 模式下,消費者在收到資料後自動提交消費進度,如果消費失敗不會重試。manual commit 模式下,消費者需要手動提交消費進度,如果消費失敗只要不提交進度就可以重試。
安裝
Install-Package BufferQueue
示例程式碼
BufferQueue 支援兩種消費模式:pull 模式和 push 模式。
builder.Services.AddBufferQueue(options =>
{
options.UseMemory(bufferOptions =>
{
// 每一對 Topic 和資料型別對應一個獨立的緩衝區,可以設定 partitionNumber
bufferOptions.AddTopic<Foo>("topic-foo1", partitionNumber: 6);
bufferOptions.AddTopic<Foo>("topic-foo2", partitionNumber: 4);
bufferOptions.AddTopic<Bar>("topic-bar", partitionNumber: 8);
})
// 新增 push 模式的消費者
// 掃描指定程式集中的標記了 BufferPushCustomerAttribute 的類,
// 註冊為 push 模式的消費者
.AddPushCustomers(typeof(Program).Assembly);
});
// 在 HostedService 中使用 pull模式 消費資料
builder.Services.AddHostedService<Foo1PullConsumerHostService>();
pull 模式的消費者示例:
public class Foo1PullConsumerHostService(
IBufferQueue bufferQueue,
ILogger<Foo1PullConsumerHostService> logger) : IHostedService
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
public Task StartAsync(CancellationToken cancellationToken)
{
var token = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token)
.Token;
var consumers = bufferQueue.CreatePullConsumers<Foo>(
new BufferPullConsumerOptions
{
TopicName = "topic-foo1", GroupName = "group-foo1", AutoCommit = true, BatchSize = 100,
}, consumerNumber: 4);
foreach (var consumer in consumers)
{
_ = ConsumeAsync(consumer, token);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_cancellationTokenSource.Cancel();
return Task.CompletedTask;
}
private async Task ConsumeAsync(IBufferPullConsumer<Foo> consumer, CancellationToken cancellationToken)
{
await foreach (var buffer in consumer.ConsumeAsync(cancellationToken))
{
foreach (var foo in buffer)
{
// Process the foo
logger.LogInformation("Foo1PullConsumerHostService.ConsumeAsync: {Foo}", foo);
}
}
}
}
push 模式的消費者示例:
透過 BufferPushCustomer 特性註冊 push 模式的消費者。
push consumer 會被註冊到 DI 容器中,可以透過建構函式注入其他服務,可以透過設定 ServiceLifetime 來控制 consumer 的生命週期。
BufferPushCustomerAttribute 中的 concurrency 引數用於設定 push consumer 的消費併發數,對應 pull consumer 的 consumerNumber。
[BufferPushCustomer(
topicName: "topic-foo2",
groupName: "group-foo2",
batchSize: 100,
serviceLifetime: ServiceLifetime.Singleton,
concurrency: 2)]
public class Foo2PushConsumer(ILogger<Foo2PushConsumer> logger) : IBufferAutoCommitPushConsumer<Foo>
{
public Task ConsumeAsync(IEnumerable<Foo> buffer, CancellationToken cancellationToken)
{
foreach (var foo in buffer)
{
logger.LogInformation("Foo2PushConsumer.ConsumeAsync: {Foo}", foo);
}
return Task.CompletedTask;
}
}
[BufferPushCustomer(
"topic-bar",
"group-bar",
100,
ServiceLifetime.Scoped,
2)]
public class BarPushConsumer(ILogger<BarPushConsumer> logger) : IBufferManualCommitPushConsumer<Bar>
{
public async Task ConsumeAsync(IEnumerable<Bar> buffer, IBufferConsumerCommitter committer,
CancellationToken cancellationToken)
{
foreach (var bar in buffer)
{
logger.LogInformation("BarPushConsumer.ConsumeAsync: {Bar}", bar);
}
var commitTask = committer.CommitAsync();
if (!commitTask.IsCompletedSuccessfully)
{
await commitTask.AsTask();
}
}
}
Producer 示例:
透過 IBufferQueue 獲取到指定的 Producer,然後呼叫 ProduceAsync 方法傳送資料。
[ApiController]
[Route("/api/[controller]")]
public class TestController(IBufferQueue bufferQueue) : ControllerBase
{
[HttpPost("foo1")]
public async Task<IActionResult> PostFoo1([FromBody] Foo foo)
{
var producer = bufferQueue.GetProducer<Foo>("topic-foo1");
await producer.ProduceAsync(foo);
return Ok();
}
[HttpPost("foo2")]
public async Task<IActionResult> PostFoo2([FromBody] Foo foo)
{
var producer = bufferQueue.GetProducer<Foo>("topic-foo2");
await producer.ProduceAsync(foo);
return Ok();
}
[HttpPost("bar")]
public async Task<IActionResult> PostBar([FromBody] Bar bar)
{
var producer = bufferQueue.GetProducer<Bar>("topic-bar");
await producer.ProduceAsync(bar);
return Ok();
}
}