Ver3.5 NSQ: 분산형 실시간 메시징 플랫폼 완전 정복 가이드

NSQ: 분산형 실시간 메시징 플랫폼 완전 정복 가이드
⚡ Go 언어로 만들어진 초고속 메시지 큐, NSQ의 구조부터 실전 구현까지 친구처럼 쉽게 파헤쳐보자!
야, 솔직히 말해봐. 🤔 서버 개발하다가 이런 상황 한 번쯤 겪어봤지?
"주문이 들어오면 재고 시스템도 업데이트하고, 알림 서비스도 보내고, 로그도 남겨야 하는데... 이걸 다 동기로 처리하면 응답이 너무 느려지잖아!"
바로 이럴 때 필요한 게 메시지 큐(Message Queue)야. 그리고 오늘 우리가 파헤칠 주인공은 바로 NSQ! 🎯
NSQ는 Go 언어로 만들어진 분산형 실시간 메시징 플랫폼인데, 설정이 간단하고 성능이 미쳐있어서 스타트업부터 대기업까지 널리 쓰이고 있어.
Bitly가 처음 만들었고, 지금은 오픈소스로 공개되어 있지. 같이 처음부터 끝까지 제대로 파보자! 💪
1. NSQ가 뭔데? 기본 개념부터 잡자 🎓
NSQ(엔에스큐)는 Go 언어로 작성된 분산형 실시간 메시지 큐 시스템이야.
2012년 Bitly(URL 단축 서비스로 유명한 회사)에서 처음 개발했고, 현재는 오픈소스로 GitHub에 공개되어 있어.
공식 GitHub 주소는 github.com/nsqio/nsq야.
메시지 큐는 쉽게 말해 "중간 우체통"이야. A 서비스가 B 서비스한테 직접 말 걸지 않고, 우체통에 편지를 넣어두면 B가 나중에 꺼내 읽는 방식이지.
이렇게 하면 A와 B가 서로 독립적으로 동작할 수 있어서 시스템이 훨씬 유연해져!
NSQ가 탄생한 배경
Bitly는 하루에 수억 건의 URL 단축 요청을 처리해야 했어. 기존의 메시지 큐 시스템들(RabbitMQ, ActiveMQ 등)은 설정이 복잡하거나 단일 장애점(SPOF, Single Point of Failure) 문제가 있었지.
그래서 Bitly 팀이 직접 만든 게 NSQ야. "설정 간단하고, 빠르고, 분산되어 있고, 장애에 강한" 메시지 큐를 목표로 했어.
NSQ의 핵심 철학
📌 분산(Distributed): 중앙 브로커 없이 각 노드가 독립적으로 동작
📌 수평 확장(Horizontally Scalable): 노드를 추가하는 것만으로 성능 확장 가능
📌 낮은 지연(Low Latency): 메모리 기반 처리로 초고속 메시지 전달
📌 내결함성(Fault Tolerant): 일부 노드가 죽어도 시스템 전체는 살아있음
📌 최소 1회 전달 보장(At-least-once delivery): 메시지가 반드시 처리됨을 보장
2. NSQ의 핵심 컴포넌트 3총사 🔧
NSQ는 크게 3가지 핵심 컴포넌트로 구성돼. 이 3개만 제대로 이해하면 NSQ의 80%는 이해한 거야!
① nsqd - 메시지의 심장 💓
nsqd는 NSQ의 핵심 데몬이야. 실제로 메시지를 받고, 저장하고, 전달하는 역할을 해.
각 서버에 독립적으로 실행되며, 다른 nsqd와 직접 통신하지 않아. 이게 NSQ의 분산 철학이야!
🔹 TCP 포트 4150: Producer/Consumer가 연결하는 포트
🔹 HTTP 포트 4151: REST API, 메시지 발행, 통계 조회
🔹 Topic: 메시지의 카테고리 (예: "주문완료", "결제요청")
🔹 Channel: Topic의 구독 그룹 (같은 채널의 Consumer들은 메시지를 나눠서 처리)
② nsqlookupd - 서비스 디스커버리 🔍
nsqlookupd는 "어디에 어떤 nsqd가 있는지" 알려주는 서비스야.
Consumer가 특정 Topic을 구독하고 싶을 때, nsqlookupd에게 물어보면 해당 Topic을 가진 nsqd 목록을 알려줘.
이 덕분에 Consumer는 nsqd의 IP를 하드코딩할 필요가 없어! 🎉
그래서 nsqlookupd가 잠깐 죽어도 이미 연결된 Consumer들은 계속 메시지를 받을 수 있어!
③ nsqadmin - 눈으로 보는 관리 UI 🖥️
nsqadmin은 웹 기반 관리 인터페이스야. 브라우저에서 접속하면 현재 Topic, Channel, 메시지 수, Consumer 수 등을 한눈에 볼 수 있어.
기본 포트는 4171이고, 개발/운영 환경에서 모니터링할 때 정말 유용해!
3. Topic과 Channel: NSQ의 핵심 개념 🎯
NSQ를 이해하는 데 있어서 Topic과 Channel의 차이를 아는 게 정말 중요해.
이걸 헷갈리면 나중에 설계할 때 꼬여버리거든. 확실하게 정리해보자!
Topic: 메시지의 카테고리
Topic은 메시지를 분류하는 카테고리야. 예를 들어 이커머스 시스템이라면:
order_completed payment_failed user_signup inventory_update
이런 식으로 Topic을 만들 수 있어. Topic은 nsqd에 메시지가 처음 발행될 때 자동으로 생성돼.
Channel: 구독 그룹
Channel은 Topic을 구독하는 그룹이야. 중요한 건 같은 Topic의 메시지가 각 Channel에 복사된다는 거야.
예를 들어 order_completed Topic에 notification, inventory, analytics 채널이 있다면,
주문 완료 메시지 하나가 세 채널 모두에 복사되어 각각 처리돼!
같은 Channel 안에 Consumer가 여러 개 있으면, 메시지가 Consumer들에게 라운드로빈 방식으로 분산돼.
즉, Channel은 "Pub/Sub 패턴"과 "로드밸런싱"을 동시에 제공하는 마법 같은 개념이야! ✨
4. NSQ 설치하고 바로 써보자! 🚀
이론은 충분히 했으니 이제 직접 손을 움직여보자. NSQ 설치는 생각보다 훨씬 간단해!
방법 1: 바이너리 직접 다운로드
NSQ 공식 사이트(nsq.io)에서 OS에 맞는 바이너리를 다운받으면 돼.
# macOS (Homebrew)
brew install nsq
# Linux (직접 다운로드)
wget https://s3.amazonaws.com/bitly-downloads/nsq/nsq-1.3.0.linux-amd64.go1.21.5.tar.gz
tar -zxvf nsq-*.tar.gz
cd nsq-*/bin
방법 2: Docker로 실행 (추천! 🐳)
Docker를 쓰면 환경 걱정 없이 바로 실행할 수 있어.
# nsqlookupd 실행
docker run -d --name nsqlookupd \
-p 4160:4160 -p 4161:4161 \
nsqio/nsq /nsqlookupd
# nsqd 실행 (nsqlookupd에 등록)
docker run -d --name nsqd \
-p 4150:4150 -p 4151:4151 \
nsqio/nsq /nsqd \
--lookupd-tcp-address=nsqlookupd:4160
# nsqadmin 실행
docker run -d --name nsqadmin \
-p 4171:4171 \
nsqio/nsq /nsqadmin \
--lookupd-http-address=nsqlookupd:4161
Docker Compose로 한 번에 실행하기
실제 개발 환경에서는 Docker Compose를 쓰는 게 훨씬 편해!
# docker-compose.yml
version: '3'
services:
nsqlookupd:
image: nsqio/nsq
command: /nsqlookupd
ports:
- "4160:4160"
- "4161:4161"
nsqd:
image: nsqio/nsq
command: /nsqd --lookupd-tcp-address=nsqlookupd:4160
ports:
- "4150:4150"
- "4151:4151"
depends_on:
- nsqlookupd
nsqadmin:
image: nsqio/nsq
command: /nsqadmin --lookupd-http-address=nsqlookupd:4161
ports:
- "4171:4171"
depends_on:
- nsqlookupd
# 실행
docker-compose up -d
# 확인
docker-compose ps
기본 동작 테스트 (curl로 빠르게)
# Topic에 메시지 발행 (HTTP API)
curl -d 'hello NSQ!' 'http://127.0.0.1:4151/pub?topic=test'
# 여러 메시지 한 번에 발행 (MPUB)
curl -d $'msg1\nmsg2\nmsg3' 'http://127.0.0.1:4151/mpub?topic=test'
# Topic 목록 확인
curl http://127.0.0.1:4151/stats
# nsqlookupd에서 Topic 조회
curl http://127.0.0.1:4161/topics
5. Go로 Producer/Consumer 구현하기 🐹
NSQ는 Go로 만들어졌으니 Go 클라이언트가 가장 잘 지원돼. 공식 Go 클라이언트 라이브러리는 go-nsq야!
go-nsq 설치
go get github.com/nsqio/go-nsq
Producer 구현
package main
import (
"log"
"github.com/nsqio/go-nsq"
)
func main() {
// NSQ 설정
config := nsq.NewConfig()
// Producer 생성
producer, err := nsq.NewProducer("127.0.0.1:4150", config)
if err != nil {
log.Fatal("Producer 생성 실패:", err)
}
defer producer.Stop()
// 메시지 발행
topic := "order_completed"
message := []byte(`{"order_id": "12345", "user_id": "user_001", "amount": 50000}`)
err = producer.Publish(topic, message)
if err != nil {
log.Fatal("메시지 발행 실패:", err)
}
log.Println("✅ 메시지 발행 성공!")
// 여러 메시지 한 번에 발행
messages := [][]byte{
[]byte(`{"order_id": "12346"}`),
[]byte(`{"order_id": "12347"}`),
[]byte(`{"order_id": "12348"}`),
}
err = producer.MultiPublish(topic, messages)
if err != nil {
log.Fatal("멀티 발행 실패:", err)
}
log.Println("✅ 멀티 메시지 발행 성공!")
}
Consumer 구현
package main
import (
"encoding/json"
"log"
"os"
"os/signal"
"syscall"
"github.com/nsqio/go-nsq"
)
// 주문 데이터 구조체
type Order struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Amount float64 `json:"amount"`
}
// 메시지 핸들러 구현
type OrderHandler struct{}
func (h *OrderHandler) HandleMessage(msg *nsq.Message) error {
var order Order
// JSON 파싱
if err := json.Unmarshal(msg.Body, &order); err != nil {
log.Printf("❌ JSON 파싱 실패: %v", err)
// 에러 반환 시 메시지 재시도됨
return err
}
log.Printf("📦 주문 처리: ID=%s, 금액=%.0f원", order.OrderID, order.Amount)
// 비즈니스 로직 처리
// ... 알림 발송, DB 업데이트 등
// nil 반환 = 처리 성공 (FIN 전송)
return nil
}
func main() {
config := nsq.NewConfig()
// 동시 처리 수 설정 (기본값: 1)
config.MaxInFlight = 10
// Consumer 생성 (topic, channel)
consumer, err := nsq.NewConsumer("order_completed", "notification", config)
if err != nil {
log.Fatal("Consumer 생성 실패:", err)
}
// 핸들러 등록
consumer.AddHandler(&OrderHandler{})
// 동시 처리 핸들러 여러 개 등록
// consumer.AddConcurrentHandlers(&OrderHandler{}, 5)
// nsqlookupd를 통해 연결 (권장!)
err = consumer.ConnectToNSQLookupd("127.0.0.1:4161")
if err != nil {
log.Fatal("nsqlookupd 연결 실패:", err)
}
// 또는 nsqd에 직접 연결
// err = consumer.ConnectToNSQD("127.0.0.1:4150")
log.Println("🚀 Consumer 시작! 메시지 대기 중...")
// 종료 시그널 대기
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
consumer.Stop()
log.Println("👋 Consumer 종료")
}
Consumer의
HandleMessage에서 nil을 반환하면 NSQ에 FIN(처리 완료) 신호를 보내.에러를 반환하면 REQ(재시도 요청) 신호를 보내서 메시지가 다시 큐에 들어가.
이게 바로 "최소 1회 전달 보장"의 핵심 메커니즘이야!
6. Python으로도 NSQ 쓸 수 있어! 🐍
Go만 쓰는 건 아니야. Python, Node.js, Java 등 다양한 언어에서 NSQ를 사용할 수 있어.
Python 예제도 간단히 살펴보자!
pynsq 설치
pip install pynsq
Python Producer (HTTP API 사용)
import requests
import json
def publish_message(topic: str, data: dict) -> bool:
"""NSQ에 메시지 발행"""
url = f"http://127.0.0.1:4151/pub?topic={topic}"
try:
response = requests.post(
url,
data=json.dumps(data),
headers={'Content-Type': 'application/json'}
)
response.raise_for_status()
print(f"✅ 메시지 발행 성공: {topic}")
return True
except requests.RequestException as e:
print(f"❌ 발행 실패: {e}")
return False
# 사용 예시
order_data = {
"order_id": "12345",
"user_id": "user_001",
"amount": 50000,
"items": ["상품A", "상품B"]
}
publish_message("order_completed", order_data)
Python Consumer (pynsq 사용)
import nsq
import json
import tornado.ioloop
def handle_message(message):
"""메시지 처리 핸들러"""
try:
data = json.loads(message.body)
print(f"📦 주문 처리: {data.get('order_id')}")
# 비즈니스 로직
# ...
message.finish() # 처리 완료
except Exception as e:
print(f"❌ 처리 실패: {e}")
message.requeue() # 재시도 요청
# Consumer 설정
reader = nsq.Reader(
message_handler=handle_message,
lookupd_http_addresses=['http://127.0.0.1:4161'],
topic='order_completed',
channel='notification',
max_in_flight=10
)
print("🚀 Python Consumer 시작!")
nsq.run()
7. 실전 운영을 위한 고급 설정 ⚙️
개발 환경에서 테스트하는 건 쉬운데, 실제 프로덕션 환경에서 안정적으로 운영하려면 몇 가지 중요한 설정들을 알아야 해!
nsqd 주요 설정 옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
--mem-queue-size |
10000 | 메모리에 유지할 최대 메시지 수 |
--max-msg-size |
1048576 (1MB) | 최대 메시지 크기 |
--msg-timeout |
60s | 메시지 처리 타임아웃 |
--max-req-timeout |
1h | 최대 재시도 대기 시간 |
--data-path |
/tmp/nsqd | 디스크 저장 경로 |
--max-rdy-count |
2500 | Consumer당 최대 RDY 카운트 |
--statsd-address |
- | StatsD 서버 주소 (모니터링) |
메시지 재시도 전략 (Backoff)
Consumer에서 처리 실패 시 NSQ는 자동으로 재시도를 해. Go 클라이언트에서 재시도 설정을 커스터마이징할 수 있어:
config := nsq.NewConfig()
// 재시도 지연 설정
config.DefaultRequeueDelay = 90 * time.Second
config.MaxRequeueDelay = 15 * time.Minute
// 백오프 전략 설정
config.BackoffStrategy = &nsq.ExponentialBackoffStrategy{
MinDelay: 1 * time.Second,
MaxDelay: 10 * time.Minute,
}
// 최대 재시도 횟수 (기본: 5)
// 초과 시 메시지는 자동으로 FIN 처리됨
config.MaxAttempts = 10
메시지 지연 발행 (Deferred Publishing)
특정 시간 후에 메시지를 처리하고 싶을 때 사용해. 예를 들어 "30분 후에 쿠폰 만료 알림 보내기" 같은 경우!
// 30분 후에 처리될 메시지 발행
delay := 30 * time.Minute
err = producer.DeferredPublish(topic, delay, message)
if err != nil {
log.Fatal("지연 발행 실패:", err)
}
log.Println("⏰ 30분 후 처리될 메시지 발행 완료!")
메모리 vs 디스크 저장
NSQ는 기본적으로 메모리에 메시지를 저장해. 하지만 --mem-queue-size를 초과하면 자동으로 디스크에 저장해.
🔹 메모리 저장: 초고속, 하지만 nsqd 재시작 시 손실 가능
🔹 디스크 저장: 느리지만 영속성 보장
--mem-queue-size=0으로 설정하면 모든 메시지를 디스크에 저장해서 영속성을 완전히 보장할 수 있어!
NSQ는 기본적으로 메시지 순서를 보장하지 않아. 순서가 중요한 비즈니스 로직이라면 Consumer 쪽에서 별도로 처리해야 해.
또한 기본 설정에서 nsqd가 재시작되면 메모리의 메시지는 손실될 수 있으니 중요한 메시지는
--mem-queue-size=0 설정을 고려해봐!
8. 실전 아키텍처 패턴 🏗️
NSQ를 실제 서비스에 적용할 때 자주 쓰이는 패턴들을 알아보자!
패턴 1: 이벤트 드리븐 마이크로서비스
위 그림처럼 하나의 이벤트(주문 완료)를 여러 서비스가 독립적으로 처리하는 패턴이야.
각 서비스는 서로를 전혀 모르고, NSQ를 통해서만 통신해. 이게 바로 느슨한 결합(Loose Coupling)의 핵심이야!
패턴 2: 작업 큐 (Work Queue)
무거운 작업(이미지 처리, 동영상 인코딩 등)을 백그라운드에서 처리할 때 사용해.
같은 Channel에 여러 Worker를 붙이면 자동으로 로드밸런싱이 돼!
// 여러 Worker가 같은 채널 구독 → 자동 로드밸런싱
// Worker 1
consumer1, _ := nsq.NewConsumer("video_encode", "workers", config)
consumer1.AddHandler(&VideoEncoder{WorkerID: 1})
consumer1.ConnectToNSQLookupd("127.0.0.1:4161")
// Worker 2 (같은 채널!)
consumer2, _ := nsq.NewConsumer("video_encode", "workers", config)
consumer2.AddHandler(&VideoEncoder{WorkerID: 2})
consumer2.ConnectToNSQLookupd("127.0.0.1:4161")
// 메시지가 Worker 1, 2에 자동으로 분산됨 🎉
패턴 3: 고가용성 구성 (HA)
프로덕션 환경에서는 nsqd와 nsqlookupd를 여러 대 운영해야 해!
🔹 nsqlookupd: 최소 2대 이상 운영 (홀수 권장)
🔹 nsqd: 각 서버에 1개씩 배포 (Producer와 같은 서버에 배치하면 네트워크 오버헤드 최소화)
🔹 Consumer: nsqlookupd 주소를 여러 개 설정하면 자동으로 모든 nsqd에서 메시지 수신
consumer.ConnectToNSQLookupd("nsqlookupd-1:4161")
consumer.ConnectToNSQLookupd("nsqlookupd-2:4161")
9. NSQ vs 다른 메시지 큐 비교 🥊
NSQ가 좋다고 했는데, 다른 메시지 큐들과 비교하면 어떨까? 솔직하게 비교해볼게!
| 항목 | NSQ | RabbitMQ | Kafka | Redis Pub/Sub |
|---|---|---|---|---|
| 설정 복잡도 | ⭐ 매우 간단 | ⭐⭐⭐ 복잡 | ⭐⭐⭐⭐ 매우 복잡 | ⭐ 매우 간단 |
| 처리량 | 높음 | 중간 | 매우 높음 | 높음 |
| 메시지 순서 보장 | ❌ 미보장 | ✅ 보장 | ✅ 파티션 내 보장 | ❌ 미보장 |
| 메시지 영속성 | 선택적 | ✅ 기본 지원 | ✅ 기본 지원 | ❌ 미지원 |
| 메시지 재처리 | 제한적 | ✅ 지원 | ✅ 완전 지원 | ❌ 미지원 |
| 분산 구조 | ✅ 기본 지원 | 클러스터 설정 필요 | ✅ 기본 지원 | 클러스터 설정 필요 |
| 학습 곡선 | 낮음 👍 | 중간 | 높음 | 낮음 |
| 적합한 규모 | 중소~대규모 | 중소규모 | 대규모 | 소규모 |
✅ 빠르게 메시지 큐를 도입해야 할 때
✅ 설정 복잡도를 최소화하고 싶을 때
✅ 분산 환경에서 단일 장애점 없이 운영하고 싶을 때
✅ Go 기반 스택을 사용하는 팀
❌ 메시지 순서가 반드시 보장되어야 할 때 → Kafka 고려
❌ 메시지 재처리(리플레이)가 필요할 때 → Kafka 고려
10. 모니터링과 운영 꿀팁 🍯
HTTP API로 상태 확인
# nsqd 전체 통계
curl http://127.0.0.1:4151/stats
# 특정 Topic 정보
curl http://127.0.0.1:4151/stats?topic=order_completed
# 헬스체크
curl http://127.0.0.1:4151/ping
# Topic 목록
curl http://127.0.0.1:4151/topics
# Channel 목록
curl http://127.0.0.1:4151/channels?topic=order_completed
nsq_tail로 실시간 메시지 확인
# 특정 Topic의 메시지를 실시간으로 확인 (디버깅에 유용!)
nsq_tail --lookupd-http-address=127.0.0.1:4161 \
--topic=order_completed \
--channel=debug_channel
nsq_to_file로 메시지 백업
# 메시지를 파일로 저장 (감사 로그, 백업 등)
nsq_to_file --lookupd-http-address=127.0.0.1:4161 \
--topic=order_completed \
--channel=backup \
--output-dir=/var/log/nsq \
--datetime-format="%Y%m%d"
Prometheus + Grafana 연동
NSQ는 StatsD 형식으로 메트릭을 내보낼 수 있어. Prometheus exporter를 사용하면 Grafana 대시보드로 예쁘게 시각화할 수 있어!
# nsqd StatsD 설정
/nsqd --statsd-address=127.0.0.1:8125 \
--statsd-interval=60s \
--statsd-prefix=nsq.
1️⃣ 메시지 크기 제한: 큰 데이터는 S3/GCS에 저장하고 NSQ에는 URL만 전달해
2️⃣ Consumer 수 조절: 처리량이 부족하면 같은 Channel에 Consumer를 추가하면 돼
3️⃣ 타임아웃 설정: 처리 시간이 긴 작업은
msg-timeout을 충분히 늘려줘4️⃣ 채널 정리: 사용하지 않는 채널은 HTTP API로 삭제해서 메모리 낭비를 막아
5️⃣ 로그 레벨: 프로덕션에서는
--log-level=warn으로 설정해서 로그 양을 줄여
11. 완전한 실전 예제: 주문 처리 시스템 🛒
지금까지 배운 내용을 종합해서 실제로 쓸 수 있는 주문 처리 시스템을 만들어보자!
이런 시스템은 재능넷 같은 플랫폼에서도 실제로 활용할 수 있는 패턴이야. 🎯
프로젝트 구조
order-system/
├── cmd/
│ ├── producer/
│ │ └── main.go # 주문 발행 서버
│ ├── notification/
│ │ └── main.go # 알림 Consumer
│ └── inventory/
│ └── main.go # 재고 Consumer
├── pkg/
│ ├── nsq/
│ │ └── client.go # NSQ 공통 클라이언트
│ └── models/
│ └── order.go # 주문 모델
└── docker-compose.yml
공통 NSQ 클라이언트 (pkg/nsq/client.go)
package nsqclient
import (
"log"
"time"
"github.com/nsqio/go-nsq"
)
// NSQ 설정 생성
func NewConfig() *nsq.Config {
config := nsq.NewConfig()
config.MaxInFlight = 20
config.DefaultRequeueDelay = 30 * time.Second
config.MaxRequeueDelay = 10 * time.Minute
config.MaxAttempts = 5
config.HeartbeatInterval = 30 * time.Second
return config
}
// Producer 생성
func NewProducer(addr string) (*nsq.Producer, error) {
producer, err := nsq.NewProducer(addr, NewConfig())
if err != nil {
return nil, err
}
// 연결 테스트
if err := producer.Ping(); err != nil {
return nil, err
}
log.Printf("✅ NSQ Producer 연결 성공: %s", addr)
return producer, nil
}
// Consumer 생성
func NewConsumer(
topic, channel string,
handler nsq.Handler,
lookupAddrs []string,
) (*nsq.Consumer, error) {
consumer, err := nsq.NewConsumer(topic, channel, NewConfig())
if err != nil {
return nil, err
}
consumer.AddHandler(handler)
for _, addr := range lookupAddrs {
if err := consumer.ConnectToNSQLookupd(addr); err != nil {
return nil, err
}
}
log.Printf("✅ NSQ Consumer 시작: topic=%s, channel=%s", topic, channel)
return consumer, nil
}
주문 모델 (pkg/models/order.go)
package models
import "time"
type Order struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Items []Item `json:"items"`
Amount float64 `json:"amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
type Item struct {
ProductID string `json:"product_id"`
Name string `json:"name"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}
Producer 서버 (cmd/producer/main.go)
package main
import (
"encoding/json"
"log"
"time"
"github.com/google/uuid"
nsqclient "order-system/pkg/nsq"
"order-system/pkg/models"
)
const (
NSQ_ADDR = "127.0.0.1:4150"
TOPIC = "order_completed"
)
func main() {
producer, err := nsqclient.NewProducer(NSQ_ADDR)
if err != nil {
log.Fatal("Producer 초기화 실패:", err)
}
defer producer.Stop()
// 테스트 주문 생성 및 발행
for i := 0; i < 10; i++ {
order := models.Order{
OrderID: uuid.New().String(),
UserID: "user_001",
Items: []models.Item{
{
ProductID: "prod_001",
Name: "Go 프로그래밍 강의",
Quantity: 1,
Price: 50000,
},
},
Amount: 50000,
Status: "completed",
CreatedAt: time.Now(),
}
data, err := json.Marshal(order)
if err != nil {
log.Printf("JSON 변환 실패: %v", err)
continue
}
if err := producer.Publish(TOPIC, data); err != nil {
log.Printf("❌ 발행 실패: %v", err)
} else {
log.Printf("✅ 주문 발행: %s", order.OrderID)
}
time.Sleep(500 * time.Millisecond)
}
}
알림 Consumer (cmd/notification/main.go)
package main
import (
"encoding/json"
"log"
"os"
"os/signal"
"syscall"
"github.com/nsqio/go-nsq"
nsqclient "order-system/pkg/nsq"
"order-system/pkg/models"
)
type NotificationHandler struct{}
func (h *NotificationHandler) HandleMessage(msg *nsq.Message) error {
var order models.Order
if err := json.Unmarshal(msg.Body, &order); err != nil {
log.Printf("❌ 파싱 실패: %v", err)
return err
}
// 알림 발송 로직
log.Printf("📢 알림 발송 → 사용자: %s, 주문: %s, 금액: %.0f원",
order.UserID, order.OrderID, order.Amount)
// 실제로는 여기서 FCM, SMS, 카카오 알림톡 등 발송
// sendPushNotification(order.UserID, order.OrderID)
return nil // 처리 완료
}
func main() {
lookupAddrs := []string{"127.0.0.1:4161"}
consumer, err := nsqclient.NewConsumer(
"order_completed",
"notification",
&NotificationHandler{},
lookupAddrs,
)
if err != nil {
log.Fatal("Consumer 시작 실패:", err)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
consumer.Stop()
log.Println("👋 알림 서비스 종료")
}
12. 자주 만나는 문제와 해결법 🔧
문제 1: 메시지가 계속 재시도됨
🔴 증상: Consumer에서 같은 메시지가 계속 들어옴
🟢 원인: HandleMessage에서 에러를 반환하거나 타임아웃이 발생
🟢 해결:
- 처리 로직에서 에러 핸들링 확인
- msg-timeout 값을 처리 시간보다 충분히 크게 설정
- 영구적 실패 메시지는 msg.Finish()로 강제 완료 처리
문제 2: Consumer가 메시지를 받지 못함
🔴 증상: Producer는 발행하는데 Consumer가 수신 못 함
🟢 원인: Channel이 생성되지 않았거나 nsqlookupd 연결 문제
🟢 해결:
- Consumer가 먼저 실행되어 Channel을 생성해야 함
- nsqadmin에서 Topic/Channel 상태 확인
- nsqlookupd 주소 및 포트 확인
문제 3: 메모리 사용량이 계속 증가
🔴 증상: nsqd 프로세스 메모리가 계속 늘어남
🟢 원인: Consumer 처리 속도보다 Producer 발행 속도가 빠름
🟢 해결:
- Consumer 수를 늘리거나 MaxInFlight 값 증가
- --mem-queue-size 조정으로 디스크 오프로드 활성화
- Producer 발행 속도 제한(Rate Limiting) 적용
문제 4: nsqd 재시작 후 메시지 손실
🔴 증상: nsqd 재시작 후 처리 중이던 메시지가 사라짐
🟢 원인: 메모리에만 저장된 메시지는 재시작 시 손실
🟢 해결:
- --mem-queue-size=0으로 모든 메시지를 디스크에 저장
- --data-path를 영속 볼륨으로 설정
- 중요 메시지는 DB에도 상태를 기록하는 이중화 전략 사용
마무리: NSQ, 이제 두렵지 않지? 🎉
자, 여기까지 NSQ의 모든 것을 파헤쳐봤어! 처음엔 "분산형 실시간 메시징 플랫폼"이라는 말이 어렵게 느껴졌을 수도 있는데, 막상 뜯어보면 개념이 정말 깔끔하고 직관적이지 않아? 😄
NSQ의 핵심을 한 줄로 정리하면:
"Producer가 Topic에 메시지를 넣으면, 각 Channel의 Consumer들이 나눠서 처리한다"
이것만 기억해도 NSQ의 80%는 이해한 거야!
✅ NSQ의 3대 컴포넌트: nsqd, nsqlookupd, nsqadmin
✅ Topic(카테고리)과 Channel(구독 그룹)의 차이
✅ Docker로 NSQ 환경 구성하기
✅ Go/Python 클라이언트로 Producer/Consumer 구현
✅ 실전 아키텍처 패턴 (이벤트 드리븐, 작업 큐, HA)
✅ 운영 팁과 트러블슈팅
재능넷에서 다양한 개발 재능을 가진 분들이 활동하고 있는데, NSQ 같은 메시징 시스템 구축 경험은 백엔드 개발자로서 정말 강력한 무기가 될 거야. 실제 프로젝트에 적용해보면서 경험을 쌓아봐! 💪
NSQ 공식 문서(nsq.io)와 GitHub(github.com/nsqio/nsq)에 더 자세한 내용이 있으니 꼭 참고해봐. 그리고 궁금한 점이 생기면 언제든지 파고들어! 개발은 결국 직접 해보는 게 최고야. 🚀
댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.
아직 댓글이 없습니다.