NATS Server 2.10+ & JetStream ์ต์ ๊ธฐ์ค ์ด๋ณด์ ์๋ฒฝ ๊ฐ์ด๋: Docker 1์ด ์ค์น, Core NATS(Pub/Sub, Request-Reply, Queue Groups)์ JetStream(์์ ์คํธ๋ฆผ, Key-Value Store, Object Store) ํต์ฌ ๋ฉํ ๋ชจ๋ธ, ์์ผ๋์นด๋ ๊ท์น, nats CLI ์์ ์ ๋ณต, Go/Python/Node.js ์ฆ์ ์คํ ์ฝ๋ ๋ฐ ๊ณ ๊ฐ์ฉ์ฑ ํ๋ก๋์
์ฒดํฌ๋ฆฌ์คํธ
## 1. ํ์ ์ฌ์ ์ค์น ์๊ฑด & ํ๊ฒฝ ๊ตฌ์ฑ (Prerequisites)
NATS.io๋ CNCF(Cloud Native Computing Foundation)๋ฅผ ์กธ์
ํ ์ด๊ฒฝ๋ยท์ด๊ณ ์ ๋ถ์ฐ ๋ฉ์์ง ๋ฐ ์ด๋ฒคํธ ์คํธ๋ฆฌ๋ฐ ์์คํ
์
๋๋ค. ๋จ์ผ Go ๋ฐ์ด๋๋ฆฌ๋ก ์คํ๋๋ฉฐ, ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๊ทนํ ์ ๊ณ (์์ญ MB ์์ค), ๋จ์ผ ์๋ฒ์์๋ ์ด๋น ์๋ฐฑ๋ง ๊ฑด์ ๋ฉ์์ง๋ฅผ ์๋ธ๋ฐ๋ฆฌ์ด(Microsecond ๋จ์) ๋๊ธฐ์๊ฐ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
์ด๋ณด์๋ 1๋ถ ์์ ์์ ์ ๋ก์ปฌ ํ๊ฒฝ์์ ์ง์ ๋์์ ๊ฒ์ฆํ ์ ์๋๋ก **JetStream(์ง์์ฑ ์์ง)๊ณผ ๋ชจ๋ํฐ๋ง ์๋ํฌ์ธํธ๊ฐ ํ์ฑํ๋ Docker ์คํ**๋ถํฐ ์์ํฉ๋๋ค.
---
### ๐ณ 1. Docker๋ก 1์ด ๋ง์ ์คํํ๊ธฐ (๊ฐ์ฅ ์ถ์ฒ)
JetStream ์์ ์คํ ๋ฆฌ์ง(`-js`)์ HTTP ๋ชจ๋ํฐ๋ง ํฌํธ(`-m 8222`)๋ฅผ ์ผ ์ํ๋ก ๊ณต์ NATS ์ปจํ
์ด๋๋ฅผ ๊ตฌ๋ํฉ๋๋ค:
```bash
# NATS 2.10+ ์ต์ ์๋ฒ ๊ตฌ๋ (JetStream ๋ฐ ์น ๋ชจ๋ํฐ๋ง ํฌํธ ํ์ฑํ)
docker run -d --name nats \
-p 4222:4222 \
-p 8222:8222 \
-p 6222:6222 \
nats:latest -js -m 8222
```
- **4222 ํฌํธ**: ํด๋ผ์ด์ธํธ(Go, Python, Java, Node.js) ๋ฐ `nats` CLI ๊ธฐ๋ณธ ํต์ ํฌํธ
- **8222 ํฌํธ**: HTTP ๊ธฐ๋ฐ ๋ด์ฅ ๋ชจ๋ํฐ๋ง ์น ์๋ํฌ์ธํธ (`/varz`, `/connz`, `/jsz` ๋ฑ)
- **6222 ํฌํธ**: NATS ํด๋ฌ์คํฐ ๋
ธ๋ ๊ฐ ๋ผ์ฐํ
(Cluster Routing) ํฌํธ
#### โ
์คํ ์ํ ํ์ธ
์น ๋ธ๋ผ์ฐ์ ๋๋ ํฐ๋ฏธ๋์์ NATS ์๋ฒ ์ํ๋ฅผ ์ฆ์ ํ์ธํ ์ ์์ต๋๋ค:
```bash
# ์๋ฒ ๋ฉํธ๋ฆญ ์์ฝ ํ์ธ (JSON ์ถ๋ ฅ)
curl http://localhost:8222/varz
# JetStream ํ์ฑํ ์ํ ํ์ธ
curl http://localhost:8222/jsz
```
---
### ๐ป 2. NATS CLI ๋๊ตฌ ๋ฐ ์๋ฒ ๋ก์ปฌ ์ค์น (์ ํ)
NATS๋ ๊ณต์ CLI ์ ํธ๋ฆฌํฐ(`nats`)๋ฅผ ํตํด ํ ํฝ ๋ฐํ/๊ตฌ๋
, ์คํธ๋ฆผ ์์ฑ, KV ๊ด๋ฆฌ, ๋ฒค์น๋งํฌ๋ฅผ ์์ฝ๊ฒ ์กฐ์ํ ์ ์์ต๋๋ค.
| ์ด์์ฒด์ (OS) | ์ค์น ๋ช
๋ น์ด |
| :--- | :--- |
| **macOS** | `brew install nats-io/nats-tools/nats nats-server` |
| **Linux (Ubuntu/Debian)** | `curl -sf https://binaries.nats.dev/nats-io/natscli/nats@latest \| sh`<br>`sudo mv nats /usr/local/bin/` |
| **Windows** | `choco install natscli nats-server` ๋๋ `scoop install nats nats-server` |
| **Go ํด์ฒด์ธ ์ฌ์ฉ ์** | `go install github.com/nats-io/natscli/nats@latest`<br>`go install github.com/nats-io/nats-server/v2@latest` |
์ค์น ํ ๋ก์ปฌ ์ฐ๊ฒฐ ์ง๋จ:
```bash
nats server check
# ์ถ๋ ฅ ์์: OK: nats://localhost:4222 connected in 1.2ms (Server: NATS Server 2.10.x)
```
---
### ๐ฆ 3. ํด๋ผ์ด์ธํธ ๋๋ผ์ด๋ฒ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
์ ํธํ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๊ณต์ NATS ํด๋ผ์ด์ธํธ๋ฅผ ์ค์นํฉ๋๋ค:
```bash
# Go (๊ณต์ ํ์ค ๋๋ผ์ด๋ฒ)
go get github.com/nats-io/nats.go
# Python (๋น๋๊ธฐ asyncio ๊ธฐ๋ฐ ๊ณต์ ๋๋ผ์ด๋ฒ)
pip install nats-py
# Node.js / TypeScript (๊ณต์ ๋๋ผ์ด๋ฒ)
npm install nats
```
---
## 2. ์ง๊ด์ ์ธ ๋ฉํ ๋ชจ๋ธ & ์ํคํ
์ฒ (Mental Model)
NATS๋ ํฌ๊ฒ **Core NATS(์์ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ ์ธ๋ฉ๋ชจ๋ฆฌ ํต์ )**์ **JetStream(Raft ๊ธฐ๋ฐ ์์์ฑ ๋ฐ ๋ถ์ฐ ์คํธ๋ฆฌ๋ฐ ์์ง)** ๋ ๊ฐ์ง ๊ณ์ธต์ผ๋ก ๋๋ฉ๋๋ค.
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NATS SERVER (ํฌํธ 4222) โ
โ โ
โ [ Core NATS ๊ณ์ธต ] - ์ด๊ณ ์ ๋ฉ๋ชจ๋ฆฌ ํต์ (At-Most-Once / Fire & Forget)โ
โ 1. Publish / Subscribe : 1:N ๋ธ๋ก๋์บ์คํธ (๊ตฌ๋
์ ์์ผ๋ฉด ์ฆ์ ํ๊ธฐ) โ
โ 2. Request / Reply : 1:1 ๋๊ธฐ RPC ํจํด (_INBOX.<์์ID> ๊ธฐ๋ฐ) โ
โ 3. Queue Groups : 1:N ๋ถ์ฐ ๋ถํ ๋ถ์ฐ (๋์ผ ํ๊ทธ๋ฃน ์ค 1๊ฐ๋ง ์๋น)โ
โ โ
โ [ JetStream ๊ณ์ธต ] - ๋์คํฌ/๋ฉ๋ชจ๋ฆฌ ์์ํ (At-Least-Once / Exactly-Once) โ
โ 1. Streams : ํ ํฝ ๋ฉ์์ง ์์ฐจ ๋ก๊ทธ ์ ์ฅ (Write-Ahead-Log)โ
โ 2. Consumers : Durable/Ephemeral ์คํ์
์ถ์ & ACK/์ฌ์๋ โ
โ 3. Key-Value Store : ๋ถ์ฐ ํค-๊ฐ ์ ์ฅ์ (์คํธ๋ฆผ ๊ธฐ๋ฐ ์ถ์ํ) โ
โ 4. Object Store : ๋์ฉ๋ ํ์ผ ์ฒญํฌ ๋ถ์ฐ ์ ์ฅ์ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโ
โผ โผ
[ Publisher / Client ] [ Subscriber / Consumer ]
nats.publish("orders.created", data) nats.subscribe("orders.*")
```
### Core NATS vs JetStream ํต์ฌ ์ฐจ์ด ๋น๊ต
| ๊ตฌ๋ถ | Core NATS | JetStream |
| :--- | :--- | :--- |
| **๋ฉ์์ง ์ ๋ฌ ๋ณด์ฅ** | **At-most-once (์ต๋ 1ํ)**<br>์์ ์๊ฐ ์์ผ๋ฉด ์ฆ์ ๋ฉ์์ง ์๋ฉธ | **At-least-once (์ต์ 1ํ)** ๋๋ **Exactly-once**<br>๋์คํฌ์ ์ ์ฅ ํ ACK ํ์ธ ์๊น์ง ์ฌ์๋ |
| **์ ์ฅ์ (Storage)** | ๋ฉ๋ชจ๋ฆฌ ์์ (Zero-allocation ๋ฒํผ) | ํ์ผ(File) ๋๋ ์ธ๋ฉ๋ชจ๋ฆฌ(Memory) WAL |
| **์๋น์ ์คํ์
** | ์์ (์ค์๊ฐ ์์ ์ ์ฉ) | ์ปจ์๋จธ(Consumer)๋ณ ์คํ์
๋ฐ ์ํ์ค ๋ฒํธ ์ถ์ |
| **์ฃผ์ ํ์ฉ ์ฌ๋ก** | ์ค์๊ฐ ๋ฉํธ๋ฆญ ์์ง, ์ฑํ
, ๋ด๋ถ RPC, ์๋ฆผ | ๊ฒฐ์ ์ด๋ฒคํธ, ์ฃผ๋ฌธ ํธ๋์ญ์
, ๊ฐ์ฌ ๋ก๊ทธ, ์์
ํ |
| **๋๊ธฐ ์๊ฐ (Latency)** | ์์ญ ๋ง์ดํฌ๋ก์ด (Microseconds, ๊ทน์ด์ ์ง์ฐ) | ์๋ฐฑ ๋ง์ดํฌ๋ก์ด ~ 1๋ฐ๋ฆฌ์ด ๋ด์ธ |
---
## 3. ๋จ๊ณ๋ณ ์ ์ง์ ์ค์ต (Step-by-Step Walkthrough)
### ๐ [์ด๋ณด์ 1๋จ๊ณ] ํฌ๋ก์๋: CLI๋ก ๊ฒฝํํ๋ Pub/Sub & Subject ์์ผ๋์นด๋
NATS๋ URL ๊ฒฝ๋ก์ ์ ์ฌํ๊ฒ ์ (`.`)์ ๊ตฌ๋ถ์๋ก ์ฌ์ฉํ๋ **Subject(์ฃผ์ )** ๊ธฐ๋ฐ ๋ผ์ฐํ
์ ์ง์ํฉ๋๋ค.
#### 1) Subject ๋ช
๋ช
๊ท์น & ์์ผ๋์นด๋
- **๊ตฌ๋ถ์**: `.` (์ , ํ ํฐ ๊ตฌ๋ถ)
- `*` (Single Token): ์ ํํ **1๊ฐ์ ํ ํฐ**์ ๋งค์นญ
- `sensor.*.temperature` โ `sensor.kitchen.temperature` (๋งค์นญ O)
- `sensor.kitchen.room1.temperature` (๋งค์นญ X)
- `>` (Multi Token / Wildcard): **1๊ฐ ์ด์์ ๋ชจ๋ ํ์ ํ ํฐ**์ ๋งค์นญ (๋ฐ๋์ ๋งจ ๋์ ์์น)
- `orders.>` โ `orders.created`, `orders.us.express.delivered` ๋ชจ๋ ๋งค์นญ
#### 2) CLI๋ก ์ค์๊ฐ ์์ ๋ฐ ๋ฐํ ์ค์ต
์๋ก ๋ค๋ฅธ ๋ ๊ฐ์ ํฐ๋ฏธ๋์ ์ด๊ณ ์๋ ๋ช
๋ น์ด๋ฅผ ์์๋๋ก ์คํํฉ๋๋ค:
**[ํฐ๋ฏธ๋ 1: ๊ตฌ๋
์(Subscriber)]**
```bash
# notifications๋ก ์์ํ๋ ๋ชจ๋ ์๋ฆผ ์์ ๋๊ธฐ
nats sub "notifications.*"
```
**[ํฐ๋ฏธ๋ 2: ๋ฐํ์(Publisher)]**
```bash
# ๋ฉ์์ง ๋ฐํ
nats pub "notifications.email" '{"user": "alice", "title": "๋ก๊ทธ์ธ ์๋ฆผ"}'
nats pub "notifications.sms" '{"user": "bob", "phone": "010-1234-5678"}'
```
**[ํฐ๋ฏธ๋ 1์ ์ถ๋ ฅ ๊ฒฐ๊ณผ (์ค์๊ฐ ์์ )]**
```text
[#1] Received on "notifications.email":
{"user": "alice", "title": "๋ก๊ทธ์ธ ์๋ฆผ"}
[#2] Received on "notifications.sms":
{"user": "bob", "phone": "010-1234-5678"}
```
---
### ๐ [2๋จ๊ณ] Request-Reply (๋๊ธฐ์ RPC) & Queue Groups (๋ถํ ๋ถ์ฐ)
#### 1) Request-Reply ํจํด (์ธ๋ฐ์ค ๊ธฐ๋ฐ RPC)
๋ฐํ์๊ฐ ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ๋ ์์ ์์ฑ๋ ์๋ต ์ฃผ์(`_INBOX.xxx`)๋ฅผ ํจ๊ป ์ ์กํ๋ฉฐ, ์์ ์๊ฐ ํด๋น ์ธ๋ฐ์ค๋ก ๋ต์ฅ์ ๋ณด๋ด๋ 1:1 ๋๊ธฐ ํจํด์
๋๋ค.
**[ํฐ๋ฏธ๋ 1: ์๋ต ์๋ฒ (Responder)]**
```bash
# service.user.info ์์ฒญ์ด ์ค๋ฉด JSON ๋ต์ฅ ์ ์ก
nats reply "service.user.info" '{"status": 200, "name": "Lucky", "role": "ADMIN"}'
```
**[ํฐ๋ฏธ๋ 2: ์์ฒญ์ (Requester)]**
```bash
# ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต ์์ ๋๊ธฐ (๊ธฐ๋ณธ 0.5์ด ํ์์์)
nats req "service.user.info" '{"userId": 1001}'
```
**[ํฐ๋ฏธ๋ 2 ์ถ๋ ฅ ๊ฒฐ๊ณผ]**
```text
17:42:01.321 >>> {"userId": 1001}
17:42:01.323 <<< {"status": 200, "name": "Lucky", "role": "ADMIN"}
Received in 2.1ms
```
---
#### 2) Queue Groups (๋์ผ ํ ๊ทธ๋ฃน ๋ด ์์
์ ๊ฐ ๋ถํ ๋ถ์ฐ)
์ผ๋ฐ Pub/Sub๋ ๋ชจ๋ ๊ตฌ๋
์์๊ฒ ๋ฉ์์ง๋ฅผ ๋ณต์ (Broadcast)ํ์ง๋ง, `--queue` ํ๋๊ทธ๋ฅผ ์ง์ ํ๋ฉด **๋์ผ ๊ทธ๋ฃน์ ์ํ ์์ปค๋ค ์ค ๋จ ํ๋๋ง ๋ฉ์์ง๋ฅผ ํ ๋น**๋ฐ์ ์ฒ๋ฆฌํฉ๋๋ค (Worker Pool / ๋ก๋๋ฐธ๋ฐ์ฑ).
```bash
# ํฐ๋ฏธ๋ A (Worker 1)
nats sub "jobs.image.resize" --queue "workers"
# ํฐ๋ฏธ๋ B (Worker 2)
nats sub "jobs.image.resize" --queue "workers"
# ํฐ๋ฏธ๋ C (๋ฐํ์) - ์ฌ๋ฌ ๊ฑด ์ ์ก
nats pub "jobs.image.resize" "์ด๋ฏธ์ง ์์
#1"
nats pub "jobs.image.resize" "์ด๋ฏธ์ง ์์
#2"
nats pub "jobs.image.resize" "์ด๋ฏธ์ง ์์
#3"
```
โ ์์
#1์ Worker 1, ์์
#2๋ Worker 2 ๋ฑ์ผ๋ก ๊ณตํํ๊ฒ ๋ถ๋ฐฐ๋ฉ๋๋ค.
---
### ๐ [3๋จ๊ณ] JetStream ์์ ์คํธ๋ฆผ & Key-Value Store ์ค๋ฌด ์์ฉ
๋ฉ์์ง๊ฐ ์ ์ค๋๋ฉด ์ ๋๋ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง(๊ฒฐ์ , ์ฃผ๋ฌธ)์๋ JetStream์ ์ฌ์ฉํฉ๋๋ค.
#### 1) ์์ ์คํธ๋ฆผ(Stream) ์์ฑ
```bash
# ORDERS ์คํธ๋ฆผ ์์ฑ: orders.* ์ฃผ์ ์ ๋ฉ์์ง๋ฅผ ํ์ผ์ ์์ ๋ณด๊ด
nats stream add ORDERS \
--subjects "orders.*" \
--storage file \
--retention limits \
--max-msgs 100000 \
--max-age 7d \
--discard old \
--dupe-window 2m
```
#### 2) ์คํธ๋ฆผ์ ๋ฉ์์ง ๋ฐํ & ์ค๋ณต ๋ฐฉ์ง (Idempotency)
`Nats-Msg-Id` ํค๋๋ฅผ ์ ๋ฌํ๋ฉด ์ค๋ณต ์๋์ฐ(`dupe-window`) ๋ด ๋์ผ ID์ ๋ฉ์์ง ์ค๋ณต ๋ฐํ์ NATS๊ฐ ์๋์ผ๋ก ์ฐจ๋จํฉ๋๋ค:
```bash
# ์ฃผ๋ฌธ ์์ฑ ๋ฉ์์ง ๋ฐํ (๋ฉ์์ง ID ํค๋ ์ง์ )
nats pub orders.created '{"orderId": "ORD-2026", "amount": 54000}' -H "Nats-Msg-Id: ORDER_REQ_001"
# ๋์ผํ ID๋ก ์ฌ๋ฐํ ์ ์ค๋ณต ๊ฐ์ง๋์ด ์คํ ๋ฆฌ์ง์ 1๊ฑด๋ง ๊ธฐ๋ก๋จ
nats pub orders.created '{"orderId": "ORD-2026", "amount": 54000}' -H "Nats-Msg-Id: ORDER_REQ_001"
```
#### 3) ์ง์์ฑ ์ปจ์๋จธ(Durable Consumer) ์์ฑ ๋ฐ ๋ฉ์์ง ๊ฐ์ ธ์ค๊ธฐ (Pull)
```bash
# ORDERS ์คํธ๋ฆผ์ ORDER_WORKER ์ปจ์๋จธ ๋ฑ๋ก (๋ช
์์ ์๋ ACK ์๊ตฌ)
nats consumer add ORDERS ORDER_WORKER \
--pull \
--ack explicit \
--deliver all \
--replay instant
# ๋ค์ ๋๊ธฐ ์ค์ธ ๋ฉ์์ง 1๊ฑด ๊ฐ์ ธ์ค๊ธฐ ๋ฐ ACK ์ฒ๋ฆฌ
nats consumer next ORDERS ORDER_WORKER
```
---
#### 4) NATS Key-Value (KV) Store ํ์ฉ
JetStream ์คํธ๋ฆผ์ ๊ธฐ๋ฐ์ผ๋ก ์ด๊ณ ์ ๋ถ์ฐ K/V ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ธฐ๋ณธ ์ ๊ณตํฉ๋๋ค:
```bash
# 1. 'app-config' ๋ฒํท ์์ฑ (ํ์คํ ๋ฆฌ ์ต๋ 5๊ฐ ๋ณด๊ด)
nats kv add app-config --history 5
# 2. ํค/๊ฐ ์ ์ฅ
nats kv put app-config db_host "postgres.cluster.local"
nats kv put app-config max_connections "50"
# 3. ํค ์กฐํ
nats kv get app-config db_host
# 4. ์ค์๊ฐ ๋ณ๊ฒฝ ๊ฐ์ง (Watch ๋ชจ๋ - ์ค์ ํซ ๋ฆฌ๋ก๋ฉ์ ์ ์ฉ)
nats kv watch app-config
```
---
## 4. ๋ณต์ฌํด์ ๋ฐ๋ก ์คํ ๊ฐ๋ฅํ ์ฝ๋ (Self-contained & Runnable)
### ๐น 1. Go (๊ณต์ nats.go ํ์ค ์์ )
๋ค์ ์ฝ๋๋ **Core NATS Pub/Sub**, **Request-Reply**, **JetStream ์คํธ๋ฆผ ์์ฑ ๋ฐ ๋ฐํ/์๋น**๋ฅผ ํ ๋ฒ์ ๊ฒ์ฆํ ์ ์๋ ์์ ํ ๋จ์ผ ํ์ผ์
๋๋ค.
```go
// main.go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
)
func main() {
// 1. NATS ์๋ฒ ์ฐ๊ฒฐ
nc, err := nats.Connect("nats://127.0.0.1:4222", nats.Timeout(3*time.Second))
if err != nil {
log.Fatalf("NATS ์ฐ๊ฒฐ ์คํจ: %v", err)
}
defer nc.Close()
fmt.Println("โ
[Core NATS] ์๋ฒ ์ฐ๊ฒฐ ์ฑ๊ณต!")
// 2. Core NATS: ๋น๋๊ธฐ ๊ตฌ๋
(Pub/Sub)
sub, err := nc.Subscribe("service.logs", func(m *nats.Msg) {
fmt.Printf("๐ฉ [Subscriber] ์์ : %s\n", string(m.Data))
})
if err != nil {
log.Fatal(err)
}
defer sub.Unsubscribe()
// 3. ๋ฉ์์ง ๋ฐํ
_ = nc.Publish("service.logs", []byte("์๋ฒ ํฌ์ค์ฒดํฌ ์ ์"))
_ = nc.Flush()
// 4. Core NATS: Request-Reply (1:1 RPC)
nc.Subscribe("user.lookup", func(m *nats.Msg) {
// ์์ฒญ ์์ ํ ์๋ต ๋ฐํ
m.Respond([]byte(`{"status": "OK", "user": "lucky"}`))
})
_ = nc.Flush()
// ์์ฒญ ๋ณด๋ด๊ธฐ (1์ด ๋๊ธฐ)
replyMsg, err := nc.Request("user.lookup", []byte(`{"id": 42}`), 1*time.Second)
if err == nil {
fmt.Printf("๐ [Request-Reply] ์๋ต ์์ : %s\n", string(replyMsg.Data))
}
// 5. JetStream ์ปจํ
์คํธ ์์ฑ (์์ ์คํธ๋ฆฌ๋ฐ)
ctx := context.Background()
js, err := jetstream.New(nc)
if err != nil {
log.Fatalf("JetStream ์ด๊ธฐํ ์คํจ: %v", err)
}
// ์คํธ๋ฆผ ์ ์ ๋ฐ ์์ฑ (์กด์ฌํ์ง ์์ ๋๋ง ์์ฑ)
streamName := "EVENTS"
stream, err := js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
Name: streamName,
Subjects: []string{"events.*"},
Storage: jetstream.FileStorage,
Retention: jetstream.LimitsPolicy,
})
if err != nil {
log.Fatalf("์คํธ๋ฆผ ์์ฑ ์คํจ: %v", err)
}
fmt.Printf("๐ [JetStream] ์คํธ๋ฆผ '%s' ํ์ฑํ ์๋ฃ\n", stream.CachedInfo().Config.Name)
// JetStream ๋ฉ์์ง ๋ฐํ
ack, err := js.Publish(ctx, "events.login", []byte(`{"event": "USER_LOGIN", "timestamp": "2026-10-07"}`))
if err != nil {
log.Fatalf("๋ฐํ ์คํจ: %v", err)
}
fmt.Printf("๐ฆ [JetStream] ๋ฐํ ์๋ฃ (Stream: %s, Seq: %d)\n", ack.Stream, ack.Sequence)
time.Sleep(500 * time.Millisecond)
fmt.Println("๐ NATS ๋ชจ๋ ๋์ ํ
์คํธ ์๋ฃ!")
}
```
#### ์คํ ๋ฐฉ๋ฒ ๋ฐ ์ถ๋ ฅ:
```bash
go mod init nats-test
go get github.com/nats-io/nats.go
go run main.go
```
```text
โ
[Core NATS] ์๋ฒ ์ฐ๊ฒฐ ์ฑ๊ณต!
๐ฉ [Subscriber] ์์ : ์๋ฒ ํฌ์ค์ฒดํฌ ์ ์
๐ [Request-Reply] ์๋ต ์์ : {"status": "OK", "user": "lucky"}
๐ [JetStream] ์คํธ๋ฆผ 'EVENTS' ํ์ฑํ ์๋ฃ
๐ฆ [JetStream] ๋ฐํ ์๋ฃ (Stream: EVENTS, Seq: 1)
๐ NATS ๋ชจ๋ ๋์ ํ
์คํธ ์๋ฃ!
```
---
### ๐ 2. Python (๋น๋๊ธฐ nats-py ์์ )
```python
# test_nats.py
import asyncio
import json
import nats
async def main():
# 1. NATS ์๋ฒ ๋น๋๊ธฐ ์ฐ๊ฒฐ
nc = await nats.connect("nats://127.0.0.1:4222")
print("โ
[Python] NATS ์ฐ๊ฒฐ ์ฑ๊ณต!")
# 2. ๋ฉ์์ง ์์ ์ฝ๋ฐฑ ํธ๋ค๋ฌ ๋ฑ๋ก
async def message_handler(msg):
data = json.loads(msg.data.decode())
print(f"๐ฉ [Python ์์ ] ์ฃผ์ : {msg.subject}, ๋ฐ์ดํฐ: {data}")
# 3. ์์ผ๋์นด๋ ๊ตฌ๋
sub = await nc.subscribe("orders.*", cb=message_handler)
# 4. ๋ฉ์์ง ๋ฐํ
payload = {"order_id": "ORD-999", "status": "PAID", "items": 3}
await nc.publish("orders.checkout", json.dumps(payload).encode())
# 5. Request-Reply ํ
์คํธ
async def reply_handler(msg):
await msg.respond(b'{"result": "PONG"}')
await nc.subscribe("service.ping", cb=reply_handler)
response = await nc.request("service.ping", b'PING', timeout=1.0)
print(f"๐ [Python RPC] ์๋ต: {response.data.decode()}")
# ์ ์ ๋๊ธฐ ํ ์ ๋ฆฌ
await asyncio.sleep(0.5)
await sub.unsubscribe()
await nc.drain()
print("๐ [Python] ํ
์คํธ ์ข
๋ฃ ์๋ฃ")
if __name__ == '__main__':
asyncio.run(main())
```
---
### ๐ฉ 3. Node.js / TypeScript (๊ณต์ nats ํจํค์ง)
```typescript
// app.ts
import { connect, StringCodec } from "nats";
async function run() {
const sc = StringCodec();
// 1. ์๋ฒ ์ฐ๊ฒฐ
const nc = await connect({ servers: "nats://127.0.0.1:4222" });
console.log(`โ
[Node.js] NATS ์ฐ๊ฒฐ๋จ: ${nc.getServer()}`);
// 2. ๋น๋๊ธฐ ์ดํฐ๋ ์ดํฐ ๋ฐฉ์ ๊ตฌ๋
(Core NATS)
const sub = nc.subscribe("telemetry.>");
(async () => {
for await (const m of sub) {
console.log(`๐ฉ [Node.js ์์ ] [${m.subject}]: ${sc.decode(m.data)}`);
}
})();
// 3. ๋ฉ์์ง ๋ฐํ
nc.publish("telemetry.server.cpu", sc.encode(JSON.stringify({ usage: "42%" })));
nc.publish("telemetry.server.memory", sc.encode(JSON.stringify({ free_mb: 2048 })));
// 4. Request-Reply
nc.subscribe("math.add", {
callback: (_err, msg) => {
const numbers: number[] = JSON.parse(sc.decode(msg.data));
const sum = numbers.reduce((a, b) => a + b, 0);
msg.respond(sc.encode(JSON.stringify({ sum })));
},
});
const rep = await nc.request("math.add", sc.encode(JSON.stringify([10, 20, 30])), { timeout: 1000 });
console.log(`โ [Node.js RPC] ํฉ๊ณ ๊ฒฐ๊ณผ: ${sc.decode(rep.data)}`);
// ์ฐ๊ฒฐ ์ข
๋ฃ ๋๊ธฐ
await nc.drain();
}
run().catch(console.error);
```
---
## 5. nats CLI ํต์ฌ ๋ช
๋ น์ด ์ข
ํฉ ์นํธ์ํธ
### 1) ๊ธฐ๋ณธ Core NATS ๋ฉ์์ง
```bash
# ๋ฉ์์ง ์ค์๊ฐ ๊ตฌ๋
(ํค๋ ํ์, ํ์์คํฌํ)
nats sub "sensors.*" --headers
# ๋ฉ์์ง ๋ฐํ (ํค๋ ํฌํจ)
nats pub "sensors.temp" "24.5" -H "Device-ID: DEV-01"
# 1:1 ๋๊ธฐ ์์ฒญ (ํ์์์ 2์ด)
nats req "auth.verify" '{"token": "xyz"}' --timeout 2s
# ํ ๊ทธ๋ฃน ๋ถ์ฐ ๊ตฌ๋
nats sub "tasks.process" --queue "workers"
```
### 2) JetStream ์คํธ๋ฆผ ๊ด๋ฆฌ
```bash
# ์คํธ๋ฆผ ๋ชฉ๋ก ํ์ธ
nats stream ls
# ์คํธ๋ฆผ ์์ธ ์ ๋ณด ์กฐํ (๋ฉ์์ง ์, ์ฉ๋, ์ปจ์๋จธ ๋ชฉ๋ก)
nats stream info ORDERS
# ํน์ ์คํธ๋ฆผ์ ์ ์ฅ๋ ๋ฉ์์ง ๋ธ๋ผ์ฐ์ง (์กฐํ)
nats stream view ORDERS
# ์คํธ๋ฆผ ๋ด ๋ชจ๋ ๋ฉ์์ง ์์ ํ๊ฒ ๋น์ฐ๊ธฐ (Purge)
nats stream purge ORDERS
# ์คํธ๋ฆผ ์ญ์
nats stream rm ORDERS --force
```
### 3) JetStream ์ปจ์๋จธ ๊ด๋ฆฌ
```bash
# ํน์ ์คํธ๋ฆผ์ ์ปจ์๋จธ ๋ชฉ๋ก ์กฐํ
nats consumer ls ORDERS
# ์ปจ์๋จธ ์ํ ๋ฐ ๋ฐ๋ฆฐ ๋ฉ์์ง ์(Lag) ํ์ธ
nats consumer info ORDERS ORDER_WORKER
# Pull ์ปจ์๋จธ๋ก๋ถํฐ ๋ค์ ๋ฉ์์ง ์์ ๋ฐ ํ์ธ
nats consumer next ORDERS ORDER_WORKER --ack
```
### 4) ์ฑ๋ฅ ์ธก์ (๋ด์ฅ ๋ฒค์น๋งํฌ)
๋ณ๋์ ๋ถํ ๋๊ตฌ ์ค์น ์์ด ์ด๋น ์ฒ๋ฆฌ๋์ ์ฆ์ ์ธก์ ํ ์ ์์ต๋๋ค:
```bash
# 100๋ง ๊ฑด ๋ฉ์์ง Pub/Sub ๋ฒค์น๋งํฌ (๋ฉ์์ง ํฌ๊ธฐ 128B)
nats bench "benchmark.test" --pub 1 --sub 1 --msgs 1000000 --size 128
```
โ ์ด๋น 1,500,000+ msgs/sec ์ฒ๋ฆฌ๋๊ณผ p99 ์ง์ฐ ์๊ฐ ํต๊ณ๊ฐ ์ฆ์ ์ถ๋ ฅ๋ฉ๋๋ค.
---
## 6. ์ค๋ฌด ์ฃผ์์ฌํญ & ํ๋ก๋์
์ฅ์ ์๋ฐฉ ์ฒดํฌ๋ฆฌ์คํธ
### โ ๏ธ 1. ์ด๋ณด์๊ฐ ๊ฐ์ฅ ๋ง์ด ๊ฒช๋ ์ค์ Top 4
1. **๊ตฌ๋
์๊ฐ ์์ ๋ Core NATS ๋ฉ์์ง ์ ์ค**:
- `nc.Publish()`๋ ๊ธฐ๋ณธ์ ์ผ๋ก **Fire-and-forget**์
๋๋ค. ํด๋น ์์ ์ ํ ํฝ์ ์ฒญ์ทจ ์ค์ธ ํ์ฑ ๊ตฌ๋
์๊ฐ ์๋ค๋ฉด ๋ฉ์์ง๋ ๋ฒ๋ ค์ง๋๋ค.
- โ ์ฃผ๋ฌธ, ๊ฒฐ์ ๋ฑ ์์์ฑ์ด ํ์ํ ์
๋ฌด์๋ ๋ฐ๋์ **JetStream Stream**์ ์์ฑํ์ฌ ๋ฐํํด์ผ ํฉ๋๋ค.
2. **๋์ปค ์คํ ์ `-js` ํ๋๊ทธ ๋๋ฝ**:
- `nats:latest` ์ปจํ
์ด๋ ๊ตฌ๋ ์ `-js` ์ต์
์ ์ฃผ์ง ์์ผ๋ฉด JetStream API ํธ์ถ ์ `nats: JetStream not enabled for account` ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
3. **Queue Group๊ณผ JetStream Consumer์ ํผ๋**:
- **Queue Group**: ์ธ๋ฉ๋ชจ๋ฆฌ ์ค์๊ฐ ๋ก๋๋ฐธ๋ฐ์ฑ (์์
์ ๋ค์ด ์ ์ฒ๋ฆฌ ์ค์ด๋ ๋ฉ์์ง ๋ณต๊ตฌ ๋ถ๊ฐ).
- **JetStream Durable Pull Consumer**: ์๋ ACK(`ack explicit`) ๊ธฐ๋ฐ์ผ๋ก ์์
์๊ฐ ๋ค์ด๋์ด ACK๋ฅผ ๋ชป ์ฃผ๋ฉด ํ์์์(`AckWait`) ํ ๋ค๋ฅธ ์์
์์๊ฒ ์๋ ์ฌ์ ์ก(At-least-once ๋ณด์ฅ).
4. **Subject์ ๊ณต๋ฐฑ ๋๋ ์๋ชป๋ ํน์๋ฌธ์ ์ฌ์ฉ**:
- Subject๋ ์ค์ง ์๋ฌธ์, ์ซ์, ํ์ดํ(`-`), ์ธ๋์ค์ฝ์ด(`_`), ์ (`.`)๋ง ์ฌ์ฉํ๋ ๊ฒ์ด ํ์ค ๊ถ์ฅ์ฌํญ์
๋๋ค. ๊ณต๋ฐฑ์ด๋ ์ฌ๋์(`/`)๋ ์ฌ์ฉํ์ง ๋ง์ธ์.
---
### ๐ก๏ธ 2. ํ๋ก๋์
์ด์ ์ฒดํฌ๋ฆฌ์คํธ
- [ ] **๊ณ ๊ฐ์ฉ์ฑ ํด๋ฌ์คํฐ๋ง (High Availability)**:
- JetStream์ **3๋ ๋๋ 5๋์ ํ์ ๋
ธ๋**๋ก ํด๋ฌ์คํฐ๋ฅผ ๊ตฌ์ฑํด์ผ Raft ํฉ์ ์ฟผ๋ผ(Quorum)์ ์ ์งํ๋ฉฐ ๋ฆฌ๋ ์ฅ์ ์ ๋ฌด์ค๋จ ์๋ ํ์ผ์ค๋ฒ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- [ ] **์คํ ๋ฆฌ์ง ์ฉ๋ ํ๋ (`max_bytes`, `discard`)**:
- ๋์คํฌ ๊ณ ๊ฐ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์คํธ๋ฆผ๋ง๋ค `max_bytes` ํ๋์ `discard: old`(์ค๋๋ ๋ฉ์์ง ์์ฐจ ์ญ์ ) ์ ์ฑ
์ ์ค์ ํด์ผ ํฉ๋๋ค.
- [ ] **๋ฉํธ๋ฆญ ๋ชจ๋ํฐ๋ง**:
- `http://<nats-ip>:8222/varz`: ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋, CPU, ์ฐ๊ฒฐ ํด๋ผ์ด์ธํธ ์
- `http://<nats-ip>:8222/jsz`: JetStream ๊ณ์ ๋ณ ์คํธ๋ฆผ ๋ฐ ์ปจ์๋จธ ์, ๋์คํฌ ์ฌ์ฉ๋
- ๊ณต์ ํ๋ก๋ฉํ
์ฐ์ค ์ต์คํฌํฐ(`nats-exporter`)๋ฅผ ํ๋/ํธ์คํธ์ ๋ฐฐํฌํ์ฌ Grafana ๋์๋ณด๋์ ์ฐ๋ ๊ถ์ฅ
- [ ] **TLS ์ํธํ ๋ฐ ๊ณ์ /๊ถํ ๋ถ๋ฆฌ (NKeys / JWT)**:
- ํ๋ก๋์
์์๋ ํ๋ฌธ ํต์ ๋์ TLS 1.3์ ํ์ฑํํ๊ณ , ํ์ค์ํ๋ NKeys(ED25519) ๋ฐ JWT ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ์ผ๋ก Subject๋ณ ๋ฐํ/๊ตฌ๋
๊ถํ์ ํ์ดํธ๋ฆฌ์คํธ๋ก ์ ํํด์ผ ํฉ๋๋ค.
- [ ] **Leaf Node (์ฃ์ง ์ปดํจํ
์ฐ๋)**:
- ๊ณต์ฅ ์ค๋น, IoT ๊ฒ์ดํธ์จ์ด, ๋ฆฌ๋ชจํธ ์ง์ฌ ์๋ฒ์ ๋
๋ฆฝ๋ NATS Leaf Node๋ฅผ ์ค์นํ๊ณ ๋ณธ์ฌ ์ค์ NATS ํด๋ฌ์คํฐ๋ก ๋ธ๋ฆฌ์ง ์ฐ๊ฒฐํ๋ฉด ๋คํธ์ํฌ ๋จ์ ์์๋ ๋ก์ปฌ ๋ฒํผ๋ง ํ ๋ณต๊ตฌ ์ ์๋ ๋๊ธฐํ๋ฉ๋๋ค.
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์