NATSとは
イベント駆動アーキテクチャの基盤となる、メッセージングミドルウェアにあたるソフトウェアです。
現在CNCFではincubating
プロジェクトとなっています。
また、thoughtworks社のTechnology Radarでも、volume24時点でAssess
となっています。
メッセージングの保証
At most once
(最大で一回)とAt-least/exactly once
(最低もしくは確実に一回)があります。
https://docs.nats.io/nats-concepts/what-is-nats#nats-quality-of-service-qos
NATSを使い始める場合、まずはCore NATSという基本のサーバから導入しますが、
それだけだとAt most onceになり、
加えてJetStreamというシステムを使うと、外部ストレージへの永続化が可能になり、
At-least/exactly onceが可能になります。
他プロダクトとの比較
競合にあたるソフトウェアとしては、KafkaやRabbit MQなどが該当します。
プロダクトの比較表
CNCFプロジェクトということもあってか、Prometheus exporterやGrafana dashboardなどがサポートされてますね。
また、これらのソフトウェアに共通することですが、メッセージングを登録する側(Publisher/Producer)と、消費する側(Subscriber/Consumer)を、独立したコンポーネントとして設計することで、互いの状態に依存せずに非同期の仕組みにでき、各コンポーネントを疎結合にすることができます。
実践
公式にあるDocker imageで実践します
Docker composeでクラスタを構築する
ポート4222がクライアントになるクラスタリング構成です。
ポート6222がクラスターのルーティングポートで、
各サーバはリクエストを--cluster
で指定し、--routes
に接続情報を記述します。
version: "3.5"
services:
nats:
image: nats
ports:
- "8222:8222"
command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --http_port 8222 "
networks: ["nats"]
nats-1:
image: nats
command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222"
networks: ["nats"]
depends_on: ["nats"]
nats-2:
image: nats
command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222"
networks: ["nats"]
depends_on: ["nats"]
networks:
nats:
name: nats
インスタンスを作成します。
$ docker compose -f nats-cluster.yaml up
[+] Running 3/0
⠿ Container nats-nats-1 Created 0.0s
⠿ Container nats-nats-1-1 Created 0.0s
⠿ Container nats-nats-2-1 Created 0.0s
Attaching to nats-nats-1, nats-nats-1-1, nats-nats-2-1
nats-nats-1 | [1] 2022/01/23 15:03:27.133998 [INF] Starting nats-server
nats-nats-1 | [1] 2022/01/23 15:03:27.134083 [INF] Version: 2.7.0
nats-nats-1 | [1] 2022/01/23 15:03:27.134097 [INF] Git: [not set]
nats-nats-1 | [1] 2022/01/23 15:03:27.134121 [INF] Name: NDLRORYKRIS7AFKF2P3FTNR32ZVQDZ7JKM375AE6Z64UO6DJ7HZ7AZJJ
nats-nats-1 | [1] 2022/01/23 15:03:27.134140 [INF] ID: NDLRORYKRIS7AFKF2P3FTNR32ZVQDZ7JKM375AE6Z64UO6DJ7HZ7AZJJ
nats-nats-1 | [1] 2022/01/23 15:03:27.135757 [INF] Starting http monitor on 0.0.0.0:8222
nats-nats-1 | [1] 2022/01/23 15:03:27.135919 [INF] Listening for client connections on 0.0.0.0:4222
nats-nats-1 | [1] 2022/01/23 15:03:27.136514 [INF] Server is ready
nats-nats-1 | [1] 2022/01/23 15:03:27.136572 [INF] Cluster name is NATS
nats-nats-1 | [1] 2022/01/23 15:03:27.136879 [INF] Listening for route connections on 0.0.0.0:6222
nats-nats-2-1 | [1] 2022/01/23 15:03:28.014862 [INF] Starting nats-server
nats-nats-2-1 | [1] 2022/01/23 15:03:28.014934 [INF] Version: 2.7.0
nats-nats-2-1 | [1] 2022/01/23 15:03:28.014946 [INF] Git: [not set]
nats-nats-2-1 | [1] 2022/01/23 15:03:28.015124 [INF] Name: NA764AGYL3FA7VZEBN6B6MYMQB3XL3FAGACLCYPILTZBJDNLIJAAQ47A
nats-nats-2-1 | [1] 2022/01/23 15:03:28.015147 [INF] ID: NA764AGYL3FA7VZEBN6B6MYMQB3XL3FAGACLCYPILTZBJDNLIJAAQ47A
nats-nats-2-1 | [1] 2022/01/23 15:03:28.016274 [INF] Listening for client connections on 0.0.0.0:4222
nats-nats-2-1 | [1] 2022/01/23 15:03:28.016522 [INF] Server is ready
nats-nats-2-1 | [1] 2022/01/23 15:03:28.016577 [INF] Cluster name is NATS
nats-nats-2-1 | [1] 2022/01/23 15:03:28.016626 [INF] Listening for route connections on 0.0.0.0:6222
nats-nats-2-1 | [1] 2022/01/23 15:03:28.023049 [INF] 172.18.0.2:6222 - rid:4 - Route connection created
nats-nats-1 | [1] 2022/01/23 15:03:28.022990 [INF] 172.18.0.3:56610 - rid:4 - Route connection created
nats-nats-1-1 | [1] 2022/01/23 15:03:28.087152 [INF] Starting nats-server
nats-nats-1-1 | [1] 2022/01/23 15:03:28.087370 [INF] Version: 2.7.0
nats-nats-1-1 | [1] 2022/01/23 15:03:28.087383 [INF] Git: [not set]
nats-nats-1-1 | [1] 2022/01/23 15:03:28.087432 [INF] Name: NADV6SOGHIG7MRQFH5K3K5NPOJIY7Q6G5WGQ2IRY4KJDXNFED6BUMXST
nats-nats-1-1 | [1] 2022/01/23 15:03:28.087508 [INF] ID: NADV6SOGHIG7MRQFH5K3K5NPOJIY7Q6G5WGQ2IRY4KJDXNFED6BUMXST
nats-nats-1-1 | [1] 2022/01/23 15:03:28.089795 [INF] Listening for client connections on 0.0.0.0:4222
nats-nats-1-1 | [1] 2022/01/23 15:03:28.090691 [INF] Server is ready
nats-nats-1-1 | [1] 2022/01/23 15:03:28.090762 [INF] Cluster name is NATS
nats-nats-1-1 | [1] 2022/01/23 15:03:28.090994 [INF] Listening for route connections on 0.0.0.0:6222
nats-nats-1 | [1] 2022/01/23 15:03:28.094351 [INF] 172.18.0.4:41872 - rid:5 - Route connection created
nats-nats-1-1 | [1] 2022/01/23 15:03:28.094383 [INF] 172.18.0.2:6222 - rid:4 - Route connection created
nats-nats-2-1 | [1] 2022/01/23 15:03:28.096072 [INF] 172.18.0.4:6222 - rid:5 - Route connection created
nats-nats-1-1 | [1] 2022/01/23 15:03:28.096196 [INF] 172.18.0.3:57406 - rid:5 - Route connection created
Publish/Subscribe
pub/subクライアント用に、nats-boxというイメージを実行します。
$ docker run --network nats --rm -it synadia/nats-box
_ _
_ __ __ _| |_ ___ | |__ _____ __
| '_ \ / _` | __/ __|_____| '_ \ / _ \ \/ /
| | | | (_| | |_\__ \_____| |_) | (_) > <
|_| |_|\__,_|\__|___/ |_.__/ \___/_/\_\
nats-box v0.5.0
NATSは通常のメッセージングであるようなtopicベースpub/subではなく、
Subject-Based Mappingという仕組みでデータマッピングします。
以下のようにhello
でsubscribeしている場合に、
2028c118ec83:~# nats sub -s nats://nats:4222 hello &
2028c118ec83:~# 15:28:37 Subscribing on hello
hello
でpublishすると、正常に受信できます。
2028c118ec83:~# nats pub -s "nats://nats-1:4222" hello first
15:28:46 Published 5 bytes to "hello"
[#1] Received on "hello"
first
そしてworld
とpublishしたものは、上記では受信されません。
2028c118ec83:~# nats pub -s "nats://nats-1:4222" world second
15:28:54 Published 6 bytes to "world"
クラスタリングしている別ノードでも同様です。
2028c118ec83:~# nats pub -s "nats://nats-2:4222" hello third
15:29:03 Published 5 bytes to "hello"
[#2] Received on "hello"
third
2028c118ec83:~# nats pub -s "nats://nats-2:4222" world fourth
15:29:33 Published 6 bytes to "world"
2028c118ec83:~#
最後に
最近はKafkaもAmazon MSKなどあり、
zookeeperの管理も不要になるなど使いやすくなってきていますが、
NATSも軽量でレイテンシーも少なく非常に使いやすいと感じました。
KafkaはJavaのエコシステムが揃っている印象でしたが、
NATS自体がGoで作られていることもあり、Goのクライアントなどのサポートも充実しているようなので、
今後試してみたいと思っています。