BufferQueue的試用筆記

wzwyc發表於2024-08-22

簡介

BufferQueue 是一個用 .NET 編寫的高效能的緩衝佇列實現,支援多執行緒併發操作。

原始碼網站:https://github.com/eventhorizon-cli/BufferQueue

功能設計

  1. 支援建立多個 Topic,每個 Topic 可以有多種資料型別。每一對 Topic 和資料型別對應一個獨立的緩衝區。

  2. 支援建立多個 Consumer Group,每個 Consumer Group 的消費進度都是獨立的。支援多個 Consumer Group 併發消費同一個 Topic。

  3. 支援同一個 Consumer Group 建立多個 Consumer,以負載均衡的方式消費資料。

  4. 支援資料的批次消費,可以一次性獲取多條資料。

  5. 支援 pull 模式和 push 模式兩種消費模式。

  6. 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();
    }
}

相關文章