使用Nats Java Client发布和接收消息

Publish and Receive Messages with Nats Java Client

1.概述

在本教程中,我们将使用Java Client for NAT连接到NATS Server并发布和接收消息。

NATS提供了三种主要的消息交换模式。 发布/订阅语义将消息传递给主题的所有订阅者。 请求/回复消息传递通过主题发送请求,并将响应路由回请求者。

订阅者还可以在订阅主题时加入消息队列组。 发送到关联主题的消息仅传递给队列组中的一个订户。

2.设定

2.1。 Maven依赖

首先,我们需要将NATS库添加到我们的pom.xml中:

1
2
3
4
5
<dependency>
    <groupId>io.nats</groupId>
    <artifactId>jnats</artifactId>
    <version>1.0</version>
</dependency>

该库的最新版本可以在这里找到,Github项目在这里。

2.2。 NATS服务器
<

其次,我们需要一个NATS服务器来交换消息。 这里有所有主要平台的说明。

我们假设有一个在localhost:4222上运行的服务器。

3.连接和交换消息

3.1。 连接到NATS

静态NATS类中的connect()方法创建Connections。

如果要使用具有默认选项的连接并在端口4222上的localhost侦听,则可以使用默认方法:

1
Connection natsConnection = Nats.connect();

但是Connections有许多可配置的选项,我们要覆盖其中的一些。

我们将创建一个Options对象,并将其传递给Nats:

1
2
3
4
5
6
7
8
9
private Connection initConnection() {
    Options options = new Options.Builder()
      .errorCb(ex -> log.error("Connection Exception:", ex))
      .disconnectedCb(event -> log.error("Channel disconnected: {}", event.getConnection()))
      .reconnectedCb(event -> log.error("Reconnected to server: {}", event.getConnection()))
      .build();

    return Nats.connect(uri, options);
}

NATS连接是耐用的。 API将尝试重新连接丢失的连接。

我们已经安装了回调函数,以通知我们何时发生断开连接以及何时恢复连接。 在此示例中,我们使用的是lambda,但是对于需要做更多事情而不是简单地记录事件的应用程序,我们可以安装实现所需接口的对象。

我们可以进行快速测试。 创建一个连接并添加60秒的睡眠以保持进程运行:

1
2
Connection natsConnection = initConnection();
Thread.sleep(60000);

运行这个。 然后停止并启动您的NATS服务器:

1
2
3
4
5
6
[jnats-callbacks] ERROR com.baeldung.nats.NatsClient
  - Channel disconnected: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c7aea8e9a9a6b3b4e9a4abaea2a9b3e984a8a9a9a2a4b3aea8a98eaab7ab87f0fef3f5ffa3a4f6">[emailprotected]</a>
[reconnect] WARN io.nats.client.ConnectionImpl
  - couldn't connect to nats://localhost:4222 (nats: connection read error)
[jnats-callbacks] ERROR com.baeldung.nats.NatsClient
  - Reconnected to server: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="026b6d2c6c6376712c616e6b676c762c416d6c6c6761766b6d6c4b6f726e42353b36303a666133">[emailprotected]</a>

我们可以看到回调记录了断开连接并重新连接。

3.2。 订阅消息

现在我们有了连接,我们可以进行消息处理了。

NATS消息是字节数组[]的容器。 除了预期的setData(byte [])和byte [] getData()方法外,还有设置和获取消息目标并回复主题的方法。

我们订阅主题,即字符串。

NATS支持同步和异步订阅。

让我们看一下异步订阅:

1
2
AsyncSubscription subscription = natsConnection
  .subscribe( topic, msg -> log.info("Received message on {}", msg.getSubject()));

API在其线程中将消息传递到我们的MessageHandler()。

某些应用程序可能希望控制处理消息的线程:

1
2
SyncSubscription subscription = natsConnection.subscribeSync("foo.bar");
Message message = subscription.nextMessage(1000);

SyncSubscription具有阻塞的nextMessage()方法,该方法将阻塞指定的毫秒数。 我们将对测试使用同步订阅,以使测试用例保持简单。

AsyncSubscription和SyncSubscription都有一个unsubscribe()方法,我们可以使用该方法来关闭订阅。

1
subscription.unsubscribe();

3.3。 发布消息

发布消息可以通过多种方式完成。

最简单的方法只需要一个主题字符串和消息字节:

1
natsConnection.publish("foo.bar","Hi there!".getBytes());

如果发布者希望回复或提供有关消息来源的特定信息,它还可以发送带有回复主题的消息:

1
natsConnection.publish("foo.bar","bar.foo","Hi there!".getBytes());

其他一些组合也有重载,例如传递消息而不是字节。

3.4。 简单的消息交换

给定一个有效的连接,我们可以编写一个测试来验证消息交换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SyncSubscription fooSubscription = natsConnection.subscribe("foo.bar");
SyncSubscription barSubscription = natsConnection.subscribe("bar.foo");
natsConnection.publish("foo.bar","bar.foo","hello there".getBytes());

Message message = fooSubscription.nextMessage();
assertNotNull("No message!", message);
assertEquals("hello there", new String(message.getData()));

natsConnection
  .publish(message.getReplyTo(), message.getSubject(),"hello back".getBytes());

message = barSubscription.nextMessage();
assertNotNull("No message!", message);
assertEquals("hello back", new String(message.getData()));

我们首先订阅带有同步订阅的两个主题,因为它们在JUnit测试中工作得更好。 然后,我们向其中一个发送一条消息,将另一个指定为replyTo地址。

阅读来自第一个目的地的消息后,我们"翻转"主题以发送响应。

3.5。 通配符订阅

NATS服务器支持主题通配符。

通配符对以"。"分隔的主题令牌进行操作。 字符。 星号字符" *"与单个标记匹配。 大于符号">"是主题其余部分的通配符匹配,可能超过一个标记。

例如:

  • foo。*匹配foo.bar,foo.requests,但不匹配foo.bar.requests

  • foo。>匹配foo.bar,foo.requests,foo.bar.requests,foo.bar.baeldung等。

  • 让我们尝试一些测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    SyncSubscription fooSubscription = client.subscribeSync("foo.*");

    client.publishMessage("foo.bar","bar.foo","hello there");

    Message message = fooSubscription.nextMessage(200);
    assertNotNull("No message!", message);
    assertEquals("hello there", new String(message.getData()));

    client.publishMessage("foo.bar.plop","bar.foo","hello there");
    message = fooSubscription.nextMessage(200);
    assertNull("Got message!", message);

    SyncSubscription barSubscription = client.subscribeSync("foo.>");

    client.publishMessage("foo.bar.plop","bar.foo","hello there");

    message = barSubscription.nextMessage(200);
    assertNotNull("No message!", message);
    assertEquals("hello there", new String(message.getData()));

    4.请求/回复消息

    我们的消息交换测试类似于pub / sub消息传递系统上的常见习语; 请求/回复。 NATS对此请求/答复消息传递有明确的支持。

    发布者可以使用我们上面使用的异步订阅方法为请求安装处理程序:

    1
    2
    3
    4
    5
    6
    7
    AsyncSubscription subscription = natsConnection
      .subscribe("foo.bar.requests", new MessageHandler() {
        @Override
        public void onMessage(Message msg) {
            natsConnection.publish(message.getReplyTo(), reply.getBytes());
        }
    });

    或者,他们可以在到达时响应请求。

    API提供了request()方法:

    1
    Message reply = natsConnection.request("foo.bar.requests", request.getBytes(), 100);

    此方法为响应创建一个临时邮箱,并为我们编写回复地址。

    Request()返回响应,如果请求超时,则返回null。 最后一个参数是要等待的毫秒数。

    我们可以修改测试以进行请求/回复:

    1
    2
    3
    4
    5
    6
    natsConnection.subscribe(salary.requests", message -> {
        natsConnection.publish(message.getReplyTo(),"
    denied!".getBytes());
    });
    Message reply = natsConnection.request("
    salary.requests","I need a raise.", 100);
    assertNotNull("
    No message!", reply);
    assertEquals("
    denied!", new String(reply.getData()));

    5.消息队列

    订阅者可以在订阅时指定队列组。 当消息发布到该组时,NATS会将其传递给一个唯一的订户。

    队列组不保留消息。 如果没有可用的侦听器,则该消息将被丢弃。

    5.1。 订阅队列

    订阅服务器将队列组名称指定为字符串:

    1
    SyncSubscription subscription = natsConnection.subscribe("topic","queue name");

    当然,还有一个异步版本:

    1
    2
    3
    4
    5
    6
    7
    SyncSubscription subscription = natsConnection
      .subscribe("topic","queue name", new MessageHandler() {
        @Override
        public void onMessage(Message msg) {
            log.info("Received message on {}", msg.getSubject());
        }
    });

    订阅将在NATS服务器上创建队列。

    5.2。 发布到队列

    将消息发布到队列组仅需要发布到关联的主题:

    1
    natsConnection.publish("foo", "queue message".getBytes());

    NATS服务器会将消息路由到队列并选择消息接收者。

    我们可以通过测试验证这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    SyncSubscription queue1 = natsConnection.subscribe("foo","queue name");
    SyncSubscription queue2 = natsConnection.subscribe("foo","queue name");

    natsConnection.publish("foo","foobar".getBytes());

    List<Message> messages = new ArrayList<>();

    Message message = queue1.nextMessage(200);
    if (message != null) messages.add(message);

    message = queue2.nextMessage(200);
    if (message != null) messages.add(message);

    assertEquals(1, messages.size());

    我们只收到一条消息。

    如果我们将前两行更改为普通订阅:

    1
    2
    SyncSubscription queue1 = natsConnection.subscribe("foo");
    SyncSubscription queue2 = natsConnection.subscribe("foo");

    测试失败,因为该消息被传递给两个订户。

    六,结论

    在此简要介绍中,我们连接到NATS服务器,并发送了发布/订阅消息和负载平衡的队列消息。 我们研究了NATS对通配符订阅的支持。 我们还使用了请求/回复消息。

    与往常一样,可以在GitHub上找到代码示例。