简介
当我们构建大规模的分布式应用集群时,我们利用所有的努力将单体分解成小的容器化工作负载,这些容器之间相互通信并分享信息以执行各种操作。
我们没有花太多时间去设计一个 消息传递系统.
消息传递通常被视为任何_大规模分布式系统_的_中枢神经系统_。通常情况下,单体内部的内存通信被转化为线上通信。
如果我们对_集群_内的所有通信进行布线,就会形成类似网状的模块,每个服务以同步的方式调用另一个服务,由于在请求-响应的生命周期中有大量的等待时间,这并不理想。
这一点 混乱的网状结构可以通过引入一个 异步消息传递集群而不是同步的。
在两个微服务之间没有_点对点的通信_,我们可以把它们的消息委托给一种 枢纽和辐条拓扑结构.因此,消息传递是连接整个系统的_胶水_。
在本指南中,我们将使用 NATS JetStream来执行异步消息传递,通过 发布/订阅模式.
那么,我们如何为我们的应用程序选择一个消息代理或消息传递架构呢?
选择一个消息传递系统可能会让人感觉很吃力,已经有大量的选择,而且每天都有新的选择出现,每个选择都有不同的优势。
选择一个分布式的消息传递系统
最值得注意的是,我们已经有了广泛流行和相当频繁使用的Apache Kafka,它通常被称为_分布式日志存储_。
在Kafka中发布到主题的消息会持续一段时间,而.NET的概念允许消息在多个实例中均匀分布。 消费者群体允许消息在同一服务的多个实例中均匀分布。它的功能非常强大,但伴随着强大的功能而来的是巨大的责任和维护。Kafka明显难以维护,对于任何希望掌握该技术的团队来说,都有一个陡峭的学习曲线。
另一个独特的选择是RabbitMQ。RabbitMQ使用高级消息队列协议进行消息传递。它也明显是轻量级的。
RabbitMQ 没有使用独特的消费者组的概念,而是采取了更简单的方法,即让客户端消费_队列_。如果一个客户端不承认一个消息,它将回到队列中,由另一个客户端处理。
所有这些产品都有一些甜头,并在它们的用例中大放异彩。
那么,如果有人想真正接受拥有一个简单而又超高性能的系统的想法,而不需要维护它的额外开销呢?如果有人想做传统的pub/sub,但也想做request/reply,甚至想做scatter-gather,同时又想保持简单和轻便呢?
这就是 NATS信息传递系统可能是最适合你的解决方案的地方。
介绍一下NATS
NATS是一个经过生产验证的、云原生的消息传递系统,是为那些想花更多时间实现业务逻辑,而少花时间担心_如何做消息传递的_开发者或运营商而设计的。
它是一个令人难以置信的快速、开源的消息传递系统,建立在一个简单而强大的核心之上。服务器使用基于文本的协议,所以虽然有一些特定语言的客户端库,但你完全可以通过_telnet_进入NATS服务器来发送和接收信息。
NATS被设计成永远在线,连接,并准备接受命令。如果你足够老,知道什么是_拨号音_,那么值得一提的是,NATS团队喜欢用这个比喻来设计。
NATS的一些突出特点包括。
- _超高_的性能
- 低配置
- 客户端只需要一个URL和凭证
- 服务器自动发现自己
- 能够在不影响运行服务的情况下扩展架构
- 自愈且始终可用
- 支持多种交付模式。
- 最多一次(NATS核心)
- 至少一次(NATS流或JetStream)
- 将消息存储到持久性存储,并按时间或顺序重放
- 支持通配符
- 在REST加密的数据
- 净化特定的消息(GDPR
- 横向可扩展性
- 完整的TLS支持。CA证书,双向支持
- 支持标准的用户/密码认证/JWT的使用
- 权限限制
- 具有数据隔离的安全多租户
- 账户之间共享数据
- 拥有30多个用不同语言编写的客户端库
信息传递模式
NATS支持4种主要的通信模式。它们是
- 基于主题
- 发布-订阅
- 请求-回复/分散-收集
- 队列组
每一个都是不同的模式,都有其使用案例,有一些重叠。允许所有这四种模式给了NATS极大的灵活性和功能,以应对多个应用程序之间的各种不同情况,或一个大型单体。
基于主题的消息传递
A 主题在NATS中是一个简单的字符串,代表对数据的兴趣。它被分_层标记_以支持_通配符订阅_。
- foo.* 匹配 _foo.bar_和 foo.baz
- foo.*.bar匹配 _foo.a.bar_和 foo.b.bar
- _foo.>_匹配上述任何一个
- _>_匹配NATS中的所有内容
这种消息传递模式允许发布者使用一个_Subject_来共享数据,而消费者可以通过使用通配符来监听这些Subject来接收这些消息。
从某种意义上说,这种模式是基于观察者设计模式的,它通常有一个_主题_和_观察者_。
例如,如果有人向_'audit.us.east'_发送消息,那么所有监听该确切主题或通配符主题的订阅者都会收到这个消息。
发布-订阅消息
这是传统的消息传递模式之一,其中 _发布者_将一个消息发布到一个 _订阅者_列表,其中每个订阅者都单独订阅了它。
这类似于通讯,这种模式被_广泛_用于各种系统中。从通知/警报系统到VoD平台,如YouTube。
这就是我们在本指南中要使用的模式。
请求-回复消息/分散-收集模式
当我们进行REST API调用时,我们发出一个HTTP请求并收到一个响应,我们使用的是传统的同步请求-响应模式。_请求-回应模式通常很困难,或者有时需要复杂的解决方案或妥协。这种模式在使用NATS实现时相当简单,因为它只需要你在发布消息时提供一个"回复到 "_主题。
这种模式也可以被称为 _分散-聚集_模式,发布者向未知数量的订阅者同时发布一个主题的消息。然后所有监听这个主题的听众都会活跃起来并开始处理。然后,发布者将等待积累来自部分或全部订阅者的所有回复。
队列组
有时在一个分布式集群中,你必须对多个应用程序或同一应用程序的多个实例进行_负载平衡_。这种模式将是一个完美的解决方案,可以在订阅了同一主题的多个订阅者之间实现消息的_负载平衡_。
这个解决方案最好的部分是,与其他消息系统不同,它不需要在NATS服务器进行任何配置。队列组是由应用程序和他们的队列订阅者定义的,并在他们之间进行管理。
为了创建一个队列订阅,所有的订阅者都注册一个队列名称。当注册的主题上的消息被发布时,组中的一个成员被随机选择来接收消息。尽管队列组有多个订阅者,但每个消息只被一个人消费。
所有这些模式在NATS服务器上需要零配置。
它完全由应用程序或客户端库驱动。因此,让我们研究一下jnatsJava客户端库,看看我们如何定义其中的一些模式并执行异步消息传递。
基本的NATS服务器、NATS流和NATS JetStream
第一个 _NATS云原生_信息传递生态系统是以 NATS服务器基于 _'最多一次'(At-most once_交付模式--消息最多交付一次。它曾经以难以置信的速度将发布的消息转发给消费者,为该行业设定了新的性能界限。对于一些应用来说,基本的NATS提供的性能超过了丢失消息的潜在损失。
但是,在'最多一次'的交付模式下,如果任何一个订户出现故障,发送到的消息将永远不会到达,因此,没有保证数据的交付。
这类似于大多数流媒体服务所使用的超高速UDP协议,数据的速度比数据的完整性更重要。你宁愿在视频中损失几个像素或分辨率较低,也不愿意长时间等待听到某人的声音。
但这并不是你想在金融交易中发生的事情。在这里和那里损失一点可能会改变某人的账单或收件人的地址。
作为对这个问题的回应 NATS流媒体引入了NATS,它用一些性能来换取消息的持久性。没有牺牲太多的性能,NATS流是一个轻量级和高性能的平台,在引擎盖下使用基本的NATS。它的构建方式是 _'至少一次_交付模型,具有为发布者和订阅者发送ACK 消息的能力。
这类似于TCP,它保证数据的完整性,如果没有收到
ACK,就会重新发送包,表示客户端可能没有收到包。
当消息被发布后,它们会被持久化一段时间(可定制),这样如果消费者没有收到它,它就可以被重新播放给消费者。虽然这个组件的性能非常好,而且是轻量级的,但在能力和成熟度方面,它和Kafka等分布式流媒体系统一样强大。
开发人员提出了一些要求,如分布式安全、分散式管理、多租户、超级集群的全球扩展以及数据的安全共享,这些要求在NATS 2.0时代催生了下一代NATS流,被称为 NATS JetStream.
对于具有分布式集群的现代流媒体系统,建议使用最新的 NATS JetStream提供。JetStream_的创建是为了解决当今流媒体技术所发现的问题--复杂性、脆弱性和缺乏扩展性。我们将在本文中进一步讨论_JetStream。
用NATS JetStream实现Java中的异步Pub/Sub消息传递
项目设置
运行或安装一个_NATS JetStream_服务器是非常容易的。无论你想在Windows、Mac或Linux机器上托管这个集群,Docker引擎都能使设置变得非常简单。
我们将使用一个Docker容器来托管JetStream服务器。为了运行Docker镜像,我们可以简单地运行。
$ docker run -ti -p 4222:4222 --name jetstream synadia/jsm:latest server
一旦你运行该程序,你会看到一些类似的内容。
NATS有一个庞大的不同语言的客户端库列表,有一个由1000多个贡献者组成的活跃社区。它在2018年加入了 CNCF(云原生计算基金会)。作为一个孵化项目在2018年加入。
我们将使用NATS的Java客户端,即jnats。为了连接到NATS JetStream,我们只需要在pom.xml 中定义一个依赖项。
<dependency>
<groupId>io.nats</groupId>
<artifactId>jnats</artifactId>
<version>${version}</version>
</dependency>
就这样了!我们已经准备好了。现在让我们来看看我们的一些使用情况。如果你遇到困难,你可以在GitHub上找到完整的源代码。
发布者/订阅者流
让我们尝试通过创建一个新的Stream 和一个主题来定义一个传统的_发布者/订阅_者模型。Stream,在NATS JetStream中代表两个端点之间的任何数据流,是API的核心构建块。
我们将创建一个单一的类,首先发布一些消息,然后订阅读取这些消息并发送确认。
public class PubSubAsync {
// Proceeding code goes here
}
让我们继续定义一些全局静态设置,如流名称、主题、默认消息和服务器。
private static final String defaultStream = "pubsubasync-stream";
private static final String defaultSubject = "pubsubasync-subject";
private static final String defaultMessage = "Hello User";
private static final int defaultMessageCount = 2;
private static final String defaultServer = "nats://localhost:4222";
我们将在以后逐步设置流的时候使用这些设置,以避免在其中硬编码变量。
让我们首先设置一个Connection 到NATS JetStream服务器,实例化一个JetStreamManagement 实例,用来添加Stream 实例,以及一个StreamConnfiguration 实例--通过生成器设计模式建立,以便在定义设置时有灵活性。
与NATS服务器的连接可能会失败,所以你要把*所有的程序代码包在一个try-catch 块中。我们将使用一个 try-with-resources块,因为这是一个可关闭的连接。
try (Connection nc = Nats.connect(defaultServer)) {
// Creating streams, managers, sending messages, subscribing, etc.
} catch (Exception e) {
e.printStackTrace();
}
在try 块中,我们将首先创建一个JetStreamManagement 实例,以及一个StreamConfiguration 和JetStream 上下文。
JetStream类是该框架的中心API。JetStream通过将消息推送到订阅者正在收听的_主题_,间接地将消息_发布_给_订阅_者。它还将用户_订阅_到主题上。
_主题_是在构建StreamConfiguration 时定义的,而JetStreamManagement 实例让我们将具有该配置的Streams 添加到我们的管道中。我们将在后面的章节中更详细地介绍JetStreamManagement 。让我们创建一个单一的流来发布消息给一个主题,并创建JetStream 上下文来管理发布和订阅发送给该主题的消息。
JetStreamManagement jsm = nc.jetStreamManagement();
// Create a stream, here will use an in-memory storage type, and one subject
StreamConfiguration sc = StreamConfiguration.builder()
.name(defaultStream)
.storageType(StorageType.Memory)
.subjects(defaultSubject)
.build();
// Add a stream via the `JetStreamManagement` instance and capture its info in a `StreamInfo` object
StreamInfo streamInfo = jsm.addStream(sc);
JsonUtils.printFormatted(streamInfo);
// Create a JetStream context. This hangs off the original connection
// allowing us to produce data to publish into streams and consume data from
// JetStream consumers.
JetStream js = nc.jetStream();
现在,我们可以继续创建一个Future列表来保存我们的消息结果,因为我们正在处理异步消息,不知道它们_何时_会返回。当通过JetStream 实例的publishAsync() 方法发布消息时,会返回一个PublishAck ,表示未来客户端对接收的确认。
如果你想阅读更多关于
Future接口的信息,请阅读我们的《Java中的未来接口指南》。
此外,对于每个消息,我们将创建一个Message 实例,它接受一个_主题_和_数据_。我们要向谁发送消息以及消息是什么。使用NatsMessage.builder() 方法,我们可以很容易地建立一个我们想发送的消息,并省略某些我们没有任何用途的参数。
一旦建立了一个Message ,我们就可以通过JetStream'的publishAsync() 方法异步发布它。
// Create a future for asynchronous message processing
List<CompletableFuture<PublishAck>> futures = new ArrayList<>();
int stop = defaultMessageCount + 1;
for (int x = 1; x < stop; x++) {
String data = defaultMessage + "-" + x;
// Create a typical NATS message
Message msg = NatsMessage.builder()
.subject(defaultSubject)
.data(data, StandardCharsets.UTF_8)
.build();
System.out.printf("Publishing message %s on subject %s.\n", data, defaultSubject);
// Publish a message and add the result to our `CompletableFuture` list
futures.add(js.publishAsync(msg));
}
一旦我们发送了消息,我们很可能想知道它们发生了什么,是否有任何问题被提出。通过迭代我们的futures 列表,我们可以检查CompletableFuture 实例是否_完成了_,如果完成了就打印它们的内容,如果没有完成就重新排队,以后再检查。
// Get Acknowledgement for the messages
while (futures.size() > 0) {
CompletableFuture<PublishAck> f = futures.remove(0);
if (f.isDone()) {
try {
PublishAck pa = f.get();
System.out.printf("Publish Succeeded on subject %s, stream %s, seqno %d.\n",
defaultSubject, pa.getStream(), pa.getSeqno());
}
catch (ExecutionException ee) {
System.out.println("Publish Failed " + ee);
}
}
else {
// Re-queue it and try again
futures.add(f);
}
}
对于一个_发布者_来说,我们需要一个订阅_者_来发布(合理的),以免信息在没有什么意义的情况下悬空。一个_订阅者_被创建为一个JetStreamSubscription 实例,由JetStream 上下文的subscribe() 方法返回。
// Subscribe to the messages that have been published to the subject
JetStreamSubscription sub = js.subscribe(defaultSubject);
List<Message> messages = new ArrayList<>();
// Retrieve the next message and kick off an iteration of all the messages
Message msg = sub.nextMessage(Duration.ofSeconds(1));
boolean first = true;
while (msg != null) {
if (first) {
first = false;
System.out.print("Read/Ack ->");
}
messages.add(msg);
if (msg.isJetStream()) {
msg.ack();
System.out.print(" " + new String(msg.getData()) + "\n");
}
else if (msg.isStatusMessage()) {
System.out.print(" !" + msg.getStatus().getCode() + "!");
}
JsonUtils.printFormatted(msg.metaData());
msg = sub.nextMessage(Duration.ofSeconds(1));
}
// Make sure the message goes through before we close
nc.flush(Duration.ZERO);
nc.close();
将所有这些联系起来,当我们运行代码时--我们应该看到这样的信息。
我们已经成功地建立了一个数据的Stream ,它将消息传递给一个_主题_,而我们的订阅者正在观察它们的异步到达。但有时,我们的主题名称在我们想要订阅它们之前是不知道的。例如,你可能会_生成_主题名称,并想在新主题创建时订阅它们。或者,有一整个具有共同前缀的主题列表,你想订阅。
在这两种情况下--而不是复杂的循环和生成-订阅逻辑--你可以使用_通配符_来针对不止一个主题。
通配符发布者/订阅者流
NATS支持_层次化_的标记,以支持通配符订阅。作为本指南开始时的复习。
A 主题在NATS中是一个简单的字符串,代表对数据的兴趣。它被_分层标记_以支持_通配符订阅_。
- foo.* 匹配 _foo.bar_和 foo.baz
- foo.*.bar匹配 _foo.a.bar_和 foo.b.bar
- _foo.>_匹配上述任何一个
- _>_匹配NATS中的所有内容
这些通配符可以在发布者或订阅者中配置,也可以在两者中配置。我们稍后会看一下这个典型的例子。我们现在要使用的方法背后的逻辑与我们之前看到的大致相同。
public class PubWildcardSubWildcard {
private static final String defaultStream = "pubsubwildcardasync-stream";
private static final String defaultSubjectWildcard = "audit.us.*";
private static final String defaultSubjectSpecific = "audit.us.east";
private static final String defaultMessage = "Audit User";
private static final int defaultMessageCount = 2;
private static final String defaultServer = "nats://localhost:4222";
public static void main( String[] args ) {
System.out.printf("\nPublishing to %s. Server is %s\n\n", defaultSubjectWildcard, defaultServer);
try (Connection nc = Nats.connect(defaultServer)) {
JetStreamManagement jsm = nc.jetStreamManagement();
StreamConfiguration sc = StreamConfiguration.builder()
.name(defaultStream)
.storageType(StorageType.Memory)
.subjects(defaultSubjectWildcard)
.build();
StreamInfo streamInfo = jsm.addStream(sc);
JsonUtils.printFormatted(streamInfo);
JetStream js = nc.jetStream();
List<CompletableFuture<PublishAck>> futures = new ArrayList<>();
int stop = defaultMessageCount + 1;
for (int x = 1; x < stop; x++) {
String data = defaultMessage + "-" + x;
Message msg = NatsMessage.builder()
.subject(defaultSubjectSpecific)
.data(data, StandardCharsets.UTF_8)
.build();
System.out.printf("Publishing message %s on subject %s.\n", data, defaultSubjectSpecific);
futures.add(js.publishAsync(msg));
}
while (futures.size() > 0) {
CompletableFuture<PublishAck> f = futures.remove(0);
if (f.isDone()) {
try {
PublishAck pa = f.get();
System.out.printf("Publish Succeeded on subject %s, stream %s, seqno %d.\n",
defaultSubjectSpecific, pa.getStream(), pa.getSeqno());
}
catch (ExecutionException ee) {
System.out.println("Publish Failed " + ee);
}
}
else {
futures.add(f);
}
}
JetStreamSubscription sub = js.subscribe(defaultSubjectWildcard);
List<Message> messages = new ArrayList<>();
Message msg = sub.nextMessage(Duration.ofSeconds(1));
boolean first = true;
while (msg != null) {
if (first) {
first = false;
System.out.print("Read/Ack ->");
}
messages.add(msg);
if (msg.isJetStream()) {
msg.ack();
System.out.print(" " + new String(msg.getData()) + "\n");
}
else if (msg.isStatusMessage()) {
System.out.print(" !" + msg.getStatus().getCode() + "!");
}
JsonUtils.printFormatted(msg.metaData());
msg = sub.nextMessage(Duration.ofSeconds(1));
}
nc.flush(Duration.ZERO)
nc.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
当我们运行这段代码时,我们会看到。
作为Pub/Sub模式的替代品,使用msg.getReplyTo() ,我们可以开始构建一个_Request-Reply_模式的实现,通过构建队列组和通道来订阅和取消订阅--我们可以构建一个_队列组_模式的实现。
这是可能的,因为我们根本没有为NATS做任何特定模式的配置--你想使用的特定模式只取决于你如何使用这个库。
JetStream管理
在某一点上,你可能想观察或管理你的流。为了做到这一点,我们将研究NATS JetStream中的流的生命周期。
- 创建或添加一个带有主题的流
- 通过添加一个主题来更新一个流
- 获取流的信息
- 清除一个流中的信息
- 删除一个流
为了演示这些,让我们创建一个有几个静态字段和只有一个main() 方法的类。在这个类中,我们将测试其中的一些操作,但根据你的架构和这些操作的触发器,你会想相应地附加上接下来的代码段。
public class NatsJsManageStreams {
private static final String STREAM1 = "manage-stream1";
private static final String STREAM2 = "manage-stream2";
private static final String SUBJECT1 = "manage-subject1";
private static final String SUBJECT2 = "manage-subject2";
private static final String SUBJECT3 = "manage-subject3";
private static final String SUBJECT4 = "manage-subject4";
private static final String defaultServer = "nats://localhost:4222";
public static void main(String[] args) {
try (Connection nc = Nats.connect(defaultServer)) {
JetStreamManagement jsm = nc.jetStreamManagement();
// Management code
// ...
// Make sure the message goes through before we close
nc.flush(Duration.ZERO);
nc.close();
} catch (Exception exp) {
exp.printStackTrace();
}
}
}
在剩下的示例中,我们将使用同一个JetStreamManagement 实例,因为我们在一个单一的类中使用它们。不过,请记住,在真实世界的场景中,你永远不会/很少会创建一个多流设置。相反,你通常会将主题添加到现有的流中以重新利用资源。
**注意:**在整个例子中,我们将使用一个自定义的_实用程序类来_处理流的创建或更新,异步发布而无需等待,或读取有无确认的消息 -
NatsJsUtils。这个实用类可以在GitHub上找到。
创建或添加一个带有主题的流
我们第一次创建一个Stream ,我们只是设置了它的名字、主题和存储策略。还有其他各种设置,我们可以通过构建器的方法进行调整。
// 1. Create (add) a stream with a subject
System.out.println("\n----------\n1. Configure And Add Stream 1");
StreamConfiguration streamConfig = StreamConfiguration.builder()
.name(STREAM1)
.subjects(SUBJECT1)
// .retentionPolicy()
// .maxConsumers(...)
// .maxBytes(...)
// .maxAge(...)
// .maxMsgSize(...)
.storageType(StorageType.Memory)
// .replicas(...)
// .noAck(...)
// .template(...)
// .discardPolicy(...)
.build();
StreamInfo streamInfo = jsm.addStream(streamConfig);
NatsJsUtils.printStreamInfo(streamInfo);
RetentionPolicy 设置消息被删除的时间--当对它们没有兴趣时(没有消费者会消费它),当它们被消费时,等等。你可以限制消费者的数量,消息可以有多长的字节,它可以被持久化多长时间,是否需要一个ACK 响应--等等。
在最简单的形式下--你提供一个名称、主题和存储类型,然后build() 。我们可以在一个Stream ,作为JetStreamManagement 实例的addStream() 方法的返回类型,通过NatsJsUtils 类漂亮地打印出信息。
用一个主题更新一个流
你可以通过JetStreamManagement 实例的updateStream() 方法来更新现有的流。我们将重新使用streamConfig 参考变量,并根据从现有的StreamInfo 实例中提取的配置,为我们想要更新的流建立一个新的配置build() 。
// 2. Update stream, in this case, adding a new subject
// - StreamConfiguration is immutable once created
// - but the builder can help with that.
System.out.println("----------\n2. Update Stream 1");
streamConfig = StreamConfiguration.builder(streamInfo.getConfiguration())
.addSubjects(SUBJECT2).build();
streamInfo = jsm.updateStream(streamConfig);
NatsJsUtils.printStreamInfo(streamInfo);
// 3. Create (add) another stream with 2 subjects
System.out.println("----------\n3. Configure And Add Stream 2");
streamConfig = StreamConfiguration.builder()
.name(STREAM2)
.storageType(StorageType.Memory)
.subjects(SUBJECT3, SUBJECT4)
.build();
streamInfo = jsm.addStream(streamConfig);
NatsJsUtils.printStreamInfo(streamInfo);
这样做的结果是。
获取流的信息
// 4. Get information on streams
// 4.0 publish some message for more interesting stream state information
// - SUBJECT1 is associated with STREAM1
// 4.1 getStreamInfo on a specific stream
// 4.2 get a list of all streams
// 4.3 get a list of StreamInfo's for all streams
System.out.println("----------\n4.1 getStreamInfo");
NatsJsUtils.publish(nc, SUBJECT1, 5);
streamInfo = jsm.getStreamInfo(STREAM1);
NatsJsUtils.printStreamInfo(streamInfo);
System.out.println("----------\n4.2 getStreamNames");
List<String> streamNames = jsm.getStreamNames();
NatsJsUtils.printObject(streamNames);
System.out.println("----------\n4.2 getStreamNames");
List<StreamInfo> streamInfos = jsm.getStreams();
NatsJsUtils.printStreamInfoList(streamInfos);
清除一个流
你可以很容易地清除一个流中的所有信息,把它完全清空。
// 5. Purge a stream of it's messages
System.out.println("----------\n5. Purge stream");
PurgeResponse purgeResponse = jsm.purgeStream(STREAM1);
NatsJsUtils.printObject(purgeResponse);
删除一个数据流
或者,如果你肯定已经用完了一个信息流--你可以轻松地删除它。
// 6. Delete a stream
System.out.println("----------\n6. Delete stream");
jsm.deleteStream(STREAM2);
System.out.println("----------\n");
处理安全问题
NATS JetStream支持用TLS对连接进行加密。TLS可以用来加密/解密客户/服务器连接之间的流量,并检查服务器的身份。当在TLS模式下启用时,NATS将要求所有客户端用TLS连接。
你可以通过加载所有的Keystores和Truststores来定义一个SSLContext ,然后在连接到NATS时将SSLContext作为一个选项来重载。让我们定义一个SSLUtils 类,我们可以用它来加载一个钥匙库,创建钥匙管理器和一个SSL上下文。
class SSLUtils {
public static String KEYSTORE_PATH = "keystore.jks";
public static String TRUSTSTORE_PATH = "truststore.jks";
public static String STORE_PASSWORD = "password";
public static String KEY_PASSWORD = "password";
public static String ALGORITHM = "SunX509";
public static KeyStore loadKeystore(String path) throws Exception {
KeyStore store = KeyStore.getInstance("JKS");
BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));
try {
store.load(in, STORE_PASSWORD.toCharArray());
} finally {
if (in != null) {
in.close();
}
}
return store;
}
public static KeyManager[] createTestKeyManagers() throws Exception {
KeyStore store = loadKeystore(KEYSTORE_PATH);
KeyManagerFactory factory = KeyManagerFactory.getInstance(ALGORITHM);
factory.init(store, KEY_PASSWORD.toCharArray());
return factory.getKeyManagers();
}
public static TrustManager[] createTestTrustManagers() throws Exception {
KeyStore store = loadKeystore(TRUSTSTORE_PATH);
TrustManagerFactory factory = TrustManagerFactory.getInstance(ALGORITHM);
factory.init(store);
return factory.getTrustManagers();
}
public static SSLContext createSSLContext() throws Exception {
SSLContext ctx = SSLContext.getInstance(Options.DEFAULT_SSL_PROTOCOL);
ctx.init(createTestKeyManagers(), createTestTrustManagers(), new SecureRandom());
return ctx;
}
}
然后,我们的实用类都准备好了--我们可以在创建NATS连接时向sslContext() builder方法提供由它创建的SSLContext 。
public class NatsConnectTLS {
public static void main(String[] args) {
try {
SSLContext ctx = SSLUtils.createSSLContext();
Options options = new Options.Builder()
.server("nats://localhost:4222")
.sslContext(ctx) // Set the SSL context
.build();
Connection nc = Nats.connect(options);
// Do something with the connection
nc.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们还可以定义一个认证机制来限制对NATS系统的访问。客户端没有对访问控制的控制权,但客户端提供了与系统进行身份验证所需的配置,绑定到一个账户,并要求TLS。
在设置Options 时,可以通过userInfo() 方法设置一个简单的配置,用一个_用户名_和_密码_来连接。
Options options = new Options.Builder().
.server("nats://localhost:4222")
.userInfo("myname","password") // Set a user and plain text password
.build();
Connection nc = Nats.connect(options);
然后,在创建一个连接时,我们可以通过在URL中提供用户名和密码来连接到NATS服务器。
Connection nc = Nats.connect("nats://myname:password@localhost:4222");
同样,我们也可以通过认证令牌,如JWTs,或秘密作为以下配置的一部分。
Options options = new Options.Builder()
.server("nats://localhost:4222")
.token("mytoken") // Set a token
.build();
Connection nc = Nats.connect(options);
我们现在可以像下面这样连接到NATS Url。
Connection nc = Nats.connect("nats://mytoken@localhost:4222"); // Token in URL
结论
当你考虑使用分布式流媒体系统作为构建分布式微服务集群、基于物联网的系统、下一代边缘系统的神经系统时,你可以考虑使用NATS JetStream,与其他流行的、强大的框架(如Apache Kafka)相比,这是一个轻量级的选择。在一个数据驱动的世界里,处理大量的事件和消息流正变得越来越普遍。NATS JetStream提供了分布式安全、多租户和横向扩展的能力。
一如既往,你可以在GitHub上找到完整的源代码。