冠军

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

NATS: Pull Consumers in JetStream

原文地址:https://natsbyexample.com/examples/jetstream/pull-consumer/csharp

对 Consumer 使用拉取模式,支持订阅到 Consumer 的应用程序根据需要来提取一个或者多个消息。这样应用程序可以自己来控制消息到达的流程,应用程序可以在自己控制的合适的时间范围来处理它。

Consumer 本身可以是持久的或者暂时的。持久的 Consumer 将在 NATS 服务器上跟踪自己的状态,特别重要的是,来自客户端的最新的确认消息。

对于一次性使用的场景来说,暂时的 Consumer 更加方便,对于资源使用和管理来说也更加轻量。不过,在订阅者取消订阅之后,暂时的 Consumer 不会持久化。NATS 服务器将会在一个时间间隔之后,自动清理 (删除) 该 Consumer.

因为每个订阅者根据需要来提取消息,多个订阅者可以创建绑定到相同的拉取 Consumer,而不需要任何其它的配置。每个订阅者都可以提取一批消息,然后并发的处理它们。

特别要提醒的是,在一批中的消息相对其它消息来说是有序的,但是,每个订阅者会独立的处理成批的消息,如果需要确定性分区以实现可扩展的订单处理,请在此处了解更多信息

代码示例

1. 安装 NATS.Net 包

using System.Diagnostics;
using NATS.Client.JetStream;
using NATS.Client.JetStream.Models;
using NATS.Net;

2. 通过环境变量获得 NATS 连接串

这里尝试使用环境变量 NATS_URL 来获得 NATS 连接串。

var url = Environment.GetEnvironmentVariable("NATS_URL") ?? "nats://127.0.0.1:4222";

3. 连接到 NATS

连接到 NATS 服务器。因为连接在我们指定的 Scope 之外是 Disposable 可丢弃的,我们应该清理缓存并清楚关闭连接。

await using var nc = new NatsClient(url);

4. 获得 JetStream

底层是 JetStream, 通过 JetStream 来管理 Stream,使用基于 JetStream 的 Consumer 发布消息和消息消息。

var js = nc.CreateJetStreamContext();

5. 定义流

定义一个简单的基于限制的流

var streamName = "EVENTS";
var stream = await js.CreateStreamAsync(new StreamConfig(streamName, subjects: ["events.>"]));

6. 发布一些消息

通过流发布消息

await js.PublishAsync(subject: "events.1", data: "event-data-1");
await js.PublishAsync(subject: "events.2", data: "event-data-2");
await js.PublishAsync(subject: "events.3", data: "event-data-3");

7. 创建基于前述创建的流的 Consumer

基于前面创建的 Stream 创建 Consumer。如果没有提供持久化的名称,当没有活动的消息的时候,Consumer 将在 InactiveThreshold (默认是 5 秒钟) 时间到达之后之后被删除掉。

Consumer 的名称是可选的,如果没有提供,Consumer 的名字将被自动生成。对于本示例来说,我们使用了没有提供的方式,这样它将是临时的,使用了自动生成的名称。

var consumer = await stream.CreateOrUpdateConsumerAsync(new ConsumerConfig());

8. 通过 Consumer 消费消息

使用 Consume() 方法,可以在循环中持续消息消息。Consume() 方法支持多种形式的选项,不过在本例中,我们使用默认的 ones.break 作为本例来确保在处理了 3 个消息之后停止处理 (这样它不会影响其它的示例)

var count = 0;
await foreach (var msg in consumer.ConsumeAsync<string>())
{
    await msg.AckAsync();
    Console.WriteLine($"received msg on {msg.Subject} with data {msg.Data}");
    if (++count == 3)
        break;
}

此时前面发布的 3 个消息应该被处理掉了。

9. 再发布一些消息

await js.PublishAsync(subject: "events.1", data: "event-data-1");
await js.PublishAsync(subject: "events.2", data: "event-data-2");
await js.PublishAsync(subject: "events.3", data: "event-data-3");

10. 批量提取消息

可以批量提取消息。第一个参数是批量的尺寸,它指定了最多可以提取的消息数量。对第一次提取,我们使用了 2 个消息,我们会得到 2 个消息,因为前面我们已经准备了 3 个消息。

var fetchCount = 0;
await foreach (var msg in consumer.FetchAsync<string>(opts: new NatsJSFetchOpts { MaxMsgs = 2 }))
{
    await msg.AckAsync();
    fetchCount++;
}

Console.WriteLine($"Got {fetchCount} messages");

11. 使用 FetchNoWait() 方法提取消息

通过返回的 Messages() 通道来获取发布到其中的消息。

该通道仅仅在请求数量的消息被接收到之后,或者超时之后才会被关闭。如果我们并不希望等待剩下的消息,并且希望在获得当前存在的消息之后快速返回 (基于提供的批的尺寸),我们应该使用 FetchNoWait() 方法来替代 Fetch() 方法。

所以,因为我们已经接收了 2 个消息,我们会仅仅得到 1 个剩下的消息。

注意:FetchNoWait() 方法并不鼓励使用,因为没有正确使用的话,它会导致额外的负担。例如,在没有规避空队列处理的循环中使用这个方法,会导致在 Stream 中没有消息的情况下,还持续尝试提取消息。

fetchCount = 0;
await foreach (var msg in ((NatsJSConsumer)consumer).FetchNoWaitAsync<string>(opts: new NatsJSFetchOpts { MaxMsgs = 100 }))
{
    await msg.AckAsync();
    fetchCount++;
}
Console.WriteLine($"Got {fetchCount} messages");

12. Fetch() 导致阻塞

最后,如果流已经为空,我们到达了流的末尾,并且调用了 Fetch() 方法,该调用将会被阻塞直到 "max wait" 时间,默认是 30s 秒钟,不过,这个时间可以作为选项显式配置。

var fetchStopwatch = Stopwatch.StartNew();
fetchCount = 0;
await foreach (var msg in consumer.FetchAsync<string>(opts: new NatsJSFetchOpts { MaxMsgs = 100, Expires = TimeSpan.FromSeconds(1) }))
{
    await msg.AckAsync();
    fetchCount++;
}
Console.WriteLine($"Got {fetchCount} messages in {fetchStopwatch.Elapsed}");

13. 持久化的 Consumer

持久化的 Consumer 需要通过提供持久化的名称来创建。持久化的 Consumer 不会因为 InactiveThreshold 时间被自动删除。它需要通过调用 DeleteConsumer() 来删除。

var durable = await stream.CreateOrUpdateConsumerAsync(new ConsumerConfig("processor"));

14. 通过持久化 Consumer 提取消息是不变的

从使用端来说,使用持久化的 Consumer 和暂时的 Consumer 没有区别。

await foreach (var msg in durable.FetchAsync<string>(opts: new NatsJSFetchOpts { MaxMsgs = 1 }))
{
    Console.WriteLine($"Received {msg.Subject} from durable consumer");
}

15. 删除持久 Consumer

对于暂时的 Consumer 来时,在 InactiveThreshold 之后会自动删除,在不需要之后,持久的 Consumer 必须显式删除。

await stream.DeleteConsumerAsync("processor");

16. 确认不存在的持久化 Consumer

使用 try/catch 来确认持久化的 Consumer 已经被删除了。

try
{
    await stream.GetConsumerAsync("processor");
}
catch (NatsJSApiException e)
{
    if (e.Error.Code == 404)
    {
        Console.WriteLine("Consumer is gone");
    }
}

17. 完成

就这样了。

Console.WriteLine("Bye!");

posted on   冠军  阅读(21)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2011-02-10 svn 的一些资料
点击右上角即可分享
微信分享提示