to-do 애플리케이션 메시지 큐 적용 * to-do 애플리케이션은 웹 프론트엔드와 SQL 데이터베이스로 구성된다. * 많은 수의 사용자를 감당할 수 있도록 수백 개의 웹 컨테이너를 실행해야 한다고 하자. * 모든 통신이 동기적으로 이뤄지기 때문에 금세 데이터베이스 서버의 최대 커넥션 수를 초과하고 애플리케이션이 오류를 일으킬 것이다. * 이런 상황에 메시지 큐를 도입하면 성능과 확장성을 함께 개선할 수 있다. * 수정된 to-do 애플리케이션은 할 일을 저장할 때 비동기 통신을 사용한다. * 사용자가 새로운 할 일을 추가하면, 웹 애플리케이션이 이 데이터를 큐에 메시지로 보낸다.
NATS * NATS는 CNCF에서 관리하는 프로젝트로, 높은 완성도와 신뢰도를 바탕으로 널리 쓰이고 있다. * NATS는 메시지를 메모리에 저장하기 때문에 속도가 매우 빠르고 컨테이너끼리의 통신에 적합하다. * 이번 실습에서는 레디스가 아닌 NATS를 사용한다.
실습 NATS를 컨테이너로 실행하라. NATS는 현재 큐에 연결된 클라이언트의 수를 알려 주는 간단한 관리자 API를 제공한다.
001) # 실습 디렉터리로 이동
002) ➜ cd ch21/exercises/todo-list
003)
004) # 메시지 큐 컨테이너 실행
005) ➜ docker-compose up -d message-queue
006)
007) # 로그 확인하기
008) ➜ docker container logs todo-list-message-queue-1
009) [1] 2023/08/20 03:46:25.093799 [INF] Starting nats-server version 2.1.9
010) [1] 2023/08/20 03:46:25.093838 [INF] Git commit [7c76626]
011) [1] 2023/08/20 03:46:25.094181 [INF] Starting http monitor on 0.0.0.0:8222
012) [1] 2023/08/20 03:46:25.094240 [INF] Listening for client connections on 0.0.0.0:4222
013) [1] 2023/08/20 03:46:25.094246 [INF] Server id is NDEI6E43Z5PFDJKKBYZFMPTFMS3MCNTD6DYKBMWGJBLIO25RSYL7SMEG
014) [1] 2023/08/20 03:46:25.094247 [INF] Server is ready
015) [1] 2023/08/20 03:46:25.094462 [INF] Listening for route connections on 0.0.0.0:6222
016)
017) # 현재 메시지 큐의 클라이언트 수 확인하기
018) ➜ curl http://localhost:8222/connz
019) {
020) "server_id": "NDEI6E43Z5PFDJKKBYZFMPTFMS3MCNTD6DYKBMWGJBLIO25RSYL7SMEG",
021) "now": "2023-08-20T03:47:59.543166876Z",
022) "num_connections": 0,
023) "total": 0,
024) "offset": 0,
025) "limit": 1024,
026) "connections": []
027) }
- 011~012: 관리자 API는 8222 포트를 사용하고 클라이언트 접속은 4222 포트를 사용한다.
- 022: 관리자 API를 통해 현재 클라이언트 수를 알 수 있다. 지금은 큐에 접속한 클라이언트가 없다.
예제 21-1 데이터베이스 서버 대신 메시지 큐에 데이터 저장하기
public void AddToDo(ToDo todo)
{
MessageQueue.Publish(new NewItemEvent(todo));
_NewTasksCounter.Inc();
}
- NATS에는 채널 개념이 없다.
- 그 대신 모든 메시지에는 서브젝트(subiec)가 부여된다.
- 이 서브젝트를 통해 메시지의 유형을 구분하며, 서브젝트에 원하는 명명규칙을 적용할 수 있다.
- 여기서는 events.todo.newitem 이라는 서브젝트를 사용했다.
- 어떤 서브스크라이버가 new-item 이벤트에 관심이 있다면 해당 서브젝트를 구독하면 된다.
- 하지만 서브스크라이버가 없더라도 애플리케이션은 계속 이 서브젝트로 메시지를 전달한다.
실습 수정된 버전의 to-do 애플리케이션 및 데이터베이스 서버를 실행한다. 아무 오류 없이 애플리케이션이 정상적으로 실행됐지만, 제대로 동작하지는 않을 것이다.
# 웹 컨테이너, 데이터베이스 컨테이너 실행
➜ docker-compose up -d todo-web todo-db
# 웹 브라우저에서 http://localhost:8080 에 접속해 새 할 일을 추가한다
- 애플리케이션에서 새로운 할 일을 추가할 수 있지만, 목록을 다시 확인해 보면 추가됐어야 할 할 일이 사라졌다.
- 새 할 일 추가 이벤트 메시지는 NATS 메시지 큐에 전달된다. 그러나 이 메시지 큐를 구독하는 대상이 없다.
- 메시지 큐 시스템은 이러한 상황을 서로 다른 방법으로 처리한다.
- 메시지가 큐에 전달됐는데 이 큐를 구독하는 서브스크라이버가 없는 상황이 발생하면, 관리자가 대신 처리할 수 있도록 데드-레터 큐(dead-letter queue)에 이 메시지를 모아 두거나 해당 큐에 서브스크라이버가 생길 때까지 메시지를 저장해 두는 방법을 사용하기도 한다.
- 레디스와 NATS는 모두 메시지를 수신할 서브스크라이버가 없다면 해당 메시지를 그대로 버리는 방식을 취한다.
- 그러므로 레디스 또는 NATS 메시지 큐를 새로 구독한 서브스크라이버는 자신이 구독을 시작한 이후 발행된 메시지만 수신할 수 있다.
실습 깃허브에서 배포되는 NATS의 예제 코드 중에는 간단한 NATS 서브스크라이버 도구가 포함돼 있다. 이 도구를 사용하면 특정 서브젝트에 해당하는 메시지를 수신할 수 있다. to-do 애플리케이션의 새로운 할 일 메시지가 메시지 큐에 제대로 발행되고 있는지 확인하라.
001) # 'events.todo.newitem' 서브젝트 메시지를 수신하는 서브스크라이버 실행
002) ➜ docker container run -d --name todo-sub --network todo-list_app-net diamol/nats-sub events.todo.newitem
003)
004) # 서브스크라이버의 컨테이너 로그 확인
005) ➜ docker container logs todo-sub
006) Listening on [events.todo.newitem]
007)
008) # 웹 브라우저에서 http://localhost:8080 에 접근해 새로운 할 일 추가
009)
010) # 새 할 일 이벤트 메시지가 제대로 발행되는지 확인
011) ➜ docker container logs todo-sub
012) [#1] Received on [events.todo.newitem]: '{"Subject":"events.todo.newitem","Item":{"ToDoId":0,"Item":"aaa","DateAdded":"2023-08-20T04:18:12.5502815Z"},"CorrelationId":"f618ba8a-13e1-4db5-9828-054c3d4cb0c2"}'
-
006: 이 시점에는 메시지가 없다. 서브스크라이버는 자신이 서브젝트를 구독하기 전에 발행된 메시지를 수신하지 못한다.
-
비동기 메시징을 적용하려면 메시지 큐, 이벤트 발생 시 메시지를 발행할 퍼블리셔, 메시지를 수신해 이벤트를 처리할 서브스크라이버까지 최소 세 가지의 구성 요소가 필요하다.
- to-do 애플리케이션에는 이 중 마지막 요소인 서브스크라이버가 빠져 있다