1. 개요
이전, 비트코인 개선 기를 올린 지 약 두어 달이 지났다.
그동안 동아리 내에서 신규 서비스 : 북마크 아카이빙 서비스 런칭이 얼마 안 남은 시점이기도 하였고 회사 내에서도 신규 프로젝트를 진행하고 개인적인 이런저런 일들이 많아 포스팅이 뒤로 미뤄졌었다.
해당 프로젝트의 경우 단순 “카프카”, “코틀린” 두가지를 학습해 보자라는 취지로 시작했었는데 하다 보니 실시간성을 유지하며 프로젝트를 발전시켜보고 싶었고 이와 더해 최근 관심이 생긴 애플리케이션 아키텍처를 통해 좀 더 고도화를 해보고 싶단 생각이 들었다.
그렇기에, 이번 목표는 다음과 같이 선정해보았다.
- 기존 애플리케이션 아키텍처 개선
- 빗썸 Open Websocket을 활용한 실시간 데이터 송수신
- 나름대로(?)의 MSA 상황을 연출해 보고 이를 기점으로 한번 고민해 보기…
2. 발전한 부분
2.1 애플리케이션 아키텍처
2.1.1 Before
정말 이게 끝이다....
각 모듈들이 하는 역할은 다음과 같았다.
schedule
- Kafka의 Consumer 역할
- 데이터를 받고, 이를 저장하는 역할
schedule-data
- producer의 역할과 API 역할
- REST API를 통해 유저에게 symbol을 받고 webflux를 통해 http 통신을 하여 10초마다 변동되는 데이터를 가져와 kafka에 실어준다.
해당 아키텍처의 문제점
- 외부 라이브러리의 의존성
- 현재, 볼륨이 작기에 괜찮을 수 있으나 볼륨이 커질수록 모듈 자체가 의존하는 라이브러리들이 많아질 것이다.
- 이는 외부 라이브러리들의 의존성이 많아질 것이고 → 장애로 발전할 수 있는 발판이 될 수 있다고 생각이 들었다.
- Scheduler와 API의 공존
- 이전, schedule-data의 모듈에서는 scheduler와 api 서비스가 공존하고 있었다.
- 만약, scheduler 쪽에서 장애가 발생된다면? 이는 API 서비스에도 영향을 줄 수 있고 서버가 다운될 수 있는 요소가 있다고 생각했다.
- 그렇기에, 해당 API 서비스와 scheduler를 분리하여 보다 안정적인 서비스를 가져가보고자 했다.
2.1.2 After
현재는 위와 같은 아키텍처로 변경하였다.
사내에서 신규서비스에 대한 애플리케이션 아키텍처의 이야기를 나눴을 때 민수님, 건수님과 함께 해당 아키텍처에 대해 논의를 한 적이 있었고 해당 아키텍처를 접했을 때 매우 유용하다는 생각을 많이 했었다.
그렇기에, 좀 더 해당 아키텍처에 대해 더 깊이 고민하고 알아가 보고자 해당 방식을 채택하게 되었었고
이번 프로젝트에서는 최대한 각 모듈들이 필요한 의존성들만을 가질 수 있도록 하기 위해 해당 모듈을 분리하였다.
각 모듈들이 하는 역할을 크게 보면 다음과 같다.
- bitcoin-api
- User와의 http req / res 가 일어나는 모듈
- 현재는 REST API를 통한 http 프로토콜만 사용 중이다.
- 해당 모듈을 통해 내가 요청한 symbol들의 각 거래소 ( 빗썸, 업비트, 코빗 )의 시세 동향 확인이 가능하다.
- coin symbol과 거래소 등록을 진행할 수 있고 이를 삭제할 수도 있다.
- 삭제는 추후 고도화 예정!
- User와의 http req / res 가 일어나는 모듈
- bitcoin-producer
- Redis Pub / sub을 통해 메시지를 Listen을 받고 이를 topic별로 메시지를 전송하는 모듈
- bitcoin-consumer
- Kafka로부터 메시지를 Listen을 받고, 이를 적재하는 모듈
- bitcoin-domain
- 각 모듈에서 일어나는 말 그대로 도메인 모듈
- 해당 모듈을 통해 모듈 간의 데이터 전달이 일어난다. ( api → domain → jpa )…
- bitcoin-infrastructure
- 프로젝트에서 사용되는 외부의존성을 모아놓은 모듈
- 해당 모듈을 통해, 외부 의존성이 관리되며 하위 서브 모듈로 나눠놔 서비스 모듈 (api, producer, consumer, websocket )에서 필요한 의존성만을 사용할 수 있다.
- bitcoin-websocket
- 빗썸, 업비트, 코빗에서 제공하는 웹소켓 서버와의 connection과 이를 통해 데이터 실시간으로 받아오는 모듈
- 해당 모듈의 위치는 많은 고민을 했다.
- 빗썸, 업비트, 코빗 같은 외부 데이터를 받아오는 역할을 하는 것이니… 이는 infrastructure 모듈에 들어가는 게 맞을까?
해당 모듈로 분리하니, 편리한 점들은 다음과 같았다.
- 외부 의존성의 분리를 통한 효율적인 관리
- 각 의존성별로 서브 모듈을 나눈 후, 필요한 모듈에서 해당 의존성을 가지게 되니 관리가 매우 편했다.
- 외부 라이브러리에서 Exception이 발생할 경우 해당 모듈을 바로 체크하면 되었기에 이슈 트래킹도 편했다.
- 확장의 용이성
- 모듈을 다음과 같이 나눠놓으니 확장하기에도 좋았다.
- 최초에 redis는 사용하지 않았었다. 하지만 다음과 같이 모듈을 분리해 놓았을 때 레디스 모듈을 어떻게 해야 하지? 에 대한 큰 고민을 하지 않아도 이미 다음과 같이 나눠져 있었기에 고민하는 리소스를 줄일 수 있었다.
2.2 인프라 아키텍처
2.2.1 Before
2.2.2 After
앞으로 아래 After의 아키텍처를 기반으로 좀 더 고도화를 진행해 볼 계획이다.
Kafka 메시지 유실과 같은 다양한 처리에 대해서 Part2 부분에서 좀 더 심도 있게 다뤄보고자 한다.
Part2에서 진행할 리스트는 다음과 같다.
- upbit, korbit 연동
- 각 클래스별 로직을 기반으로 공통 로직들은 중복성 제거하기
- Kafka 학습을 진행하며 클러스터링과 메시지 유실에 대한 방어로직 구현해 보기
- 동시성 제어
또한, 하나의 RDB에서 각 코인 데이터를 쌓을 경우 SPOF가 발생할 수 있다고 생각하였고
이를 통해 데이터베이스의 부하를 줄이기 위해 추후 샤딩을 통해 각 데이터들 간의 분산저장도 시도해 보면 어떨까?라는 생각을 하였습니다.
3. 고려사항
3.1 왜 하필 카프카였나? ( RabbitMQ vs Kafka )
데이터를 Consume을 받아 적재를 진행하기까지 해당 아키텍처에서는 카프카를 사용하였습니다.
당시, 초창기 시작했던 의도는 개요에서 언급했던 것과 같이 “카프카” 와 “코틀린”을 활용한 실습이 주된 목적이었으나 문득 서경이형이 질문했었던 내용이 머릿속에 남아 한번 고민해 보았었습니다.
해당 실시간 시세는 Websocket , polling을 통해 1초당 무수하게 제공되는 데이터들을 안전하게 서빙받아 적재하기 위한 목적이 1순위 었습니다.
또한, 현재는 개인적인 학습 목적으로 카프카를 선택한 방향도 있었으나 이를 통한 레퍼런스를 참고해 보았었을 때도 RabbitMQ보다는 Kafka를 사용한 레퍼런스들을 주로 찾을 수 있었고 “왜? 카프카를 더 선호하실까?”가 궁금해졌습니다.
해당 이유를 토대로 레퍼런스를 조사해 보고 곰곰이 생각해 보았을 때 다음과 같은 결론을 도출할 수 있었습니다.
- 성능적인 요인
- Kafka는 순차 디스크 I/O를 사용하여 처리량이 높은 메시지를 교환할 수 있기에, 초당 수백만 개의 메시지를 전송할 수 있다.
- RabbitMQ 또한, 수백만 개의 메시지 소비가 가능하지만 이렇게 진행하기 위해선 중개인가 여러개가 필요하고 RabbitMQ의 성능이 평균 초당 수 천개의 메시지를 소비하며 이는 RabbitMQ의 대기열이 혼잡해지며 속도가 느려질 수 있다.
- 메시지 처리의 차이
- RabbitMQ
- 앤드투앤드로 메시지 전달의 우선순위를 지정하는 범용 메시지 브로커
- Kafka
- 지속적인 빅 데이터의 실시간 교환을 지원하는 분산 이벤트 스트리밍 플랫폼
- RabbitMQ
이와 더불어 나는 각 큐에 전달해 주는 방식에서도 차이점이 발생하지 않나?라는 생각이 들었다.
- Exchange와 Topic에서도 차이가 생기지 않을까?
- RabbitMQ는 Exchange 정책에 따라 각 Queue에 메시지를 전달해 주게 된다.
- RabbitMQ는 모니터링을 지원해 주기에, 해당 모니터링에서 Exchange 정책을 추가해줘야 한다.
- 그렇기에, 각 정책이 추가될 경우 해당 Exchange를 모니터링에서 추가해줘야 하는 번거로움 또한 발생할 수 있지 않을까? 란 생각이 들었다.
- 또한, 정책이 많아질수록 발생되는 Exchange를 저장하기 위해 RabbitMQ의 메모리 혹은 용량을 차지하게 될 것이고 Queue 또한 증설될 수 있기에, Kafka에 비해 관리포인트들이 더 늘지 않나?라는 생각이 들었다.
- Kafka는 Topic을 통해 각 파티션을 관리한다.
- 카프카에서는 Topic을 통해 각 파티션이 생성되기에, Exchange와는 다른 개념이다.
- Topic을 통해 각 파티션들은 논리적인 주소를 획득하게 되고, 메시지가 전달될 경우 각 Topic에 맞는 Queue에 데이터가 들어오기 때문에 관리 측면에서도 용이하단 생각이 들었다.
- Topic이 증설될수록 Queue 또한 Topic의 논리적인 주소를 가지고 증설되기에 관리적인 측면에서의 이점을 가질 수 있지 않을까? 란 생각을 하였다.
이에 대한 이야기는 더 길어질 수 있기에, 추후 더 조사하여 정리해보고자 한다. 🙂
3.2 Redis pub / sub vs Webflux
Websocket에서 각 거래소들을 subscribe 하여 메시지를 가져올 때, produce 서버에 전달하기 위해 고민했던 두 가지였다.
이때 Webflux non-blocking 처리를 하여, 메시지를 전송하는 방법과 Redis pub / sub을 통한 메시지 전송
두 가지를 고민하게 되었고 고민했던 요인들은 다음과 같습니다.
- 실시간성:
- 프로젝트의 핵심 목표는 실시간 데이터 전송이었다.
- WebFlux의 비동기 처리는 대량의 동시 접속 및 처리를 다루기에 적합하지만, 여전히 네트워크 I/O 비용이 발생할 수 있다.
- 또한, WebSocket 연결이 지속될 때마다 연결 상태를 유지하고 있어야 하며, 이로 인한 부하 및 시간 초과 문제가 발생할 수 있다.
- 리액티브 웹소켓 처리:
- WebFlux는 리액티브 프로그래밍 모델을 활용하여 비동기 및 논블로킹 처리를 제공한다.
- 이러한 특성을 활용하여 WebSocket 연결을 최적화하고, 병렬 처리를 통해 성능을 극대화할 수 있다.
- Redis Pub/Sub의 실시간성:
- Redis Pub/Sub은 메시지를 구독하는 서버에 실시간으로 메시지를 전달하는 가벼운 메시징 시스템이다.
- 웹소켓 서버는 구독하지 않아도 메시지를 발행할 수 있으며, 데이터 소비자는 메시지를 수신하기 전에 해당 채널을 구독할 수 있다.
- 빠르고 가벼운 Redis 메모리 기반 아키텍처:
- Redis는 메모리 기반 데이터 저장소로, 메시지를 빠르게 읽고 쓸 수 있어 실시간성을 보장한다.
- 메시지 중개인으로서의 Redis Pub/Sub은 웹소켓을 통한 데이터 전송에 이상적이다.
결국 해당 프로젝트에서는 Redis pub/sub을 채택하여 사용하게 되었고 추후 TODO 요소로는 다음을 생각할 수 있었습니다.
- Redis 장애상황 발생 시
- 센티넬 방식 적용
4. Repository
Before Repository
https://github.com/JoeCP17/spring-study/tree/master/kafka-bitcoin
After Repository
https://github.com/JoeCP17/kafka-bitcoin-stream
5. 마치며
이번 프로젝트 Part 1을 진행하며, 아직 해야 할 요소는 많지만 평소 관심 있어 하는 주제를 토대로 진행하니 재미있었고 다소 부족했던 부분이 어떤 부분이 있는지를 파악할 수 있었다.
현재, 빗썸의 연동이 완료되어 있고 같은 방식대로 업비트와 코빗도 함께 연동할 예정이다.
연동을 진행하며 위에 써놓았던 TODO들을 하나씩 진행하고, 이와 더불어 Kafka도 하나씩 건드려보며 진행할 생각이다.
조만간 더 발전시켜 Part2로 나아가보고자 한다.
REF
'Spring' 카테고리의 다른 글
Feature Flags? 누구냐 넌 (1) | 2024.02.19 |
---|---|
Chained Transaction Manager 파헤치기 (1) | 2023.12.09 |
빗썸 API를 활용한 매수 / 매도 데이터 적재 (1) | 2023.06.11 |
[Spring Boot + Chat GPT] Open AI API 적용기 (1) | 2023.04.16 |
분산락과 네임드락 그리고... 동시성 (1) | 2023.02.02 |