동기 연동과 비동기 연동
동기
- 순차적으로 실행
- 한 작업이 끝날 때까지 다음 작업이 진행되지 않는다
- 흐름을 직관적으로 이해 가능
- 디버깅 용이
비동기
- 한 작업이 끝날 때까지 기다리지 않고 바로 다음 작업을 진행할 수 있음
- 사용 가능한 경우
- 연동에 약간의 시차가 생겨도 문제가 되지 않을 때
- 일부 기능은 실패했을 때 재시도 가능할 때
- 연동에 실패했을 때 나중에 수동으로 처리할 수 있는 기능
- 연동에 실패했을 때 무시해도 되는 기능
- 후처리를 용이하게 할 수 있게 된다.
별도 스레드로 실행하기
비동기 연동을 하는 가장 쉬운 방법
- 스프링 프레임워크는 @Async 를 이용한 비동기 실행 기능 제공
- 트랜잭션 문제
- 예외 사항인데도 롤백이 안되는 경우
- 비동기로 실행되어 익셉션이 전파되지 않았음
- 별도 스레드로 실행되는 메소드를 같은 스레드에서 실행되도록 수정
- 그 후 다시 비동기로 실행할 수 잇는 방법을 찾아 적용
메시징
서로 다른 시스템 간에 비동기로 연동할 때 주로 사용하는 방식
장점
- 두 시스템이 서로 영향을 주지 않는다
- 확장이 용이하다
단점
종류
카프카 고려사항
- 높은 처리량 자랑 → 초당 백 만 개 이상의 메시지를 처리할 수 있다
- 수평 확장 용이 → 서버, 파티션, 소비자를 늘리면 된다
- 카프카는 메시지를 파일에 보관해서 메시지가 유실되지 않는다
- 1개의 토픽이 여러 파티션을 가질 수 있는데, 팥티션 단위로 순서를 보장, 토픽 수준에서는 순서 보장할 수 없다.
- 소비자는 메시지를 언제든지 재처리할 수 있다
- 풀 모델을 사용한다. → 소비자가 카프카 브로커에서 메시지를 읽어 가는 방식
래빗MQ 고려 사항
- 클러스트를 통해 처리량을 높일 수 있다 → 카픜카보다 더 많은 자원을 필요로 한다.
- 메모리에만 메시지를 보관하는 큐 설정을 사용하면 장애 상황 시 메시지가 유실될 수 있다
- 메시지는 큐에 등록된 순서대로 소비자에 전송된다
- 메시지가 소비자에 전달됐는지 확인하는 기능을 제공
- 푸시 모델을 사용 → 래빗MQ 브로커가 소비자에 메시지를 전송, 소비자 성능이 느려지면 큐에 과부하가 걸려 전반적으로 성능 저하 발생
- 다재 다능하다 → AMQP, STOMP 등 여러 프로토콜을 지원하고, 게시/구독 패턴뿐만 아니라 요청/응답, 점대점 패턴을 지원 → 우선 순위를 지정해서 처리 순서를 변경할 수도 있다.
레디스 pub/sub 고려 사항
- 메모리를 사용하므로 지연 시간이 짧고, 래빗MQ 대비 처리량이 높다
- 구독자가 없으면 메시지가 유실
- 기본적으로 영구 메시지를 지원하지 않는다
- 모델이 단순해서 사용하기 쉽다
결론 : 메시지 유실 상관없다면 레디스, 카프카나 래빗MQ에 비해 사용법이 간단, 적은 장비로 높은 성능을 낼 수 있다.
트래픽이 대량으로 발생한다면 카프카 고려,
트래픽 규모가 크지 않고 메시지를 정확하게 순서대로 소비자에 전달해야 하거나 AMQP나 STOM{P 프로토콜로 연동해야 한다면 래빗MQ 고려
메시지 생성 측 고려 사항
메시지를 생성할 때 고려할 점
무시한다.
재시도 한다.
- 일시적인 네트워크 불안정과 같은 오류
- 재시도하는 과정에서 중복된 메시지가 전송될 수 있다.
- 메시지마다 고유 식별자를 사용하면 중복 메시지 여부 판단 유용
실패 로그를 남긴다
- 로그는 나중에 후처리를 하는 데 사용
- 후처리에 필요한 데이터를 남긴다 → DB나 파일
메시지 소비 측 고려 사항
메시지 생성자가 같은 데이터를 가진 메시지를 메시징 시스템에 두 번 전송
- 메시지에 고유한 ID를 부여해서 이미 처리했는지 여부를 추적한다.
- 이미 처리한 메시지는 다시 처리하지 않고 무시할 수 있다.
소비자가 메시지를 처리하는 과정에서 오류가 발생해서 메시지 재수신
- 소비자는 메시지 처리에 실패했다고 생각하고 메시징 시스템으로부터 같은 메시지를 다시 수신해서 재시도할 수 있다.
- API가 멱등성을 가지면 동일 요청을 여러 번 해도 결과가 바뀌지 않음
메시지 종류: 이벤트와 커맨드
예시
| 1. 주문함
2. 로그인에 실패함
3. 상품 정보를 조회함
4. 배송을 완료함 | 1. 포인트 지급하기
2. 로그인 차단하기
3. 배송 완료 문자 발송하기 |
이벤트
- 어떤 일이 발생했음을 알려주는 메시지
- 상태 변경과 관련
- 정해진 수신자가 없다.
- 소비자 확장에 적합
커맨드
- 무엇가를 요청하는 메시지
- 소비자는 메시지로 요구한 기능을 실행
트랜잭션 아웃박스 패턴
메시지 데이터를 DB에 보관하는 방식
실제 없무 로직에 필요한 DB 변경 작업을 수행
- DB 트랜잭션 범위에서 아웃박스 테이블에 메시지 데이터를 추가하므로 메시지 데이터가 유실되지 않음
- 트랜잭션을 롤백하면 메시지 데이터도 함꼐 롤백 → 잘못된 메시지 데이터가 전송될 일 없음
메시지 데이터를 아웃박스 테이블에 추가
- 3가지 상태(발송 대기, 발송 완료, 발송 실패)를 갖는다
- 쉽게 모니터링할 수 있다
아웃박스 테이블 구조
컬럼명 | 타입 | 설명 |
---|
id | BIGINT (PK) | 아웃박스 레코드의 고유 식별자 |
message_type | VARCHAR(100) | 메시지 타입 |
message_id | VARCHAR(100) | 메시지 고유 id |
payload | clob | 메시지 데이터 |
status | VARCHAR(20) | 처리 상태 (waiting, done, failed 등) |
fail_count | int | 실패 횟수 |
created_at | TIMESTAMP | 메시지 발생 시간 |
processed_at | TIMESTAMP | 메시지 처리 시간 |
falled_at | TIMESTAMP | 마지막 실패 시간 |
배치 전송
데이터를 비동기로 연동하는 가장 전통적인 방법
실행 과정
- DB에서 전송할 데이터를 조회
- 조회한 결과를 파일로 기록
- 파일을 연동 시스템에 전송
사용하는 파일 형식
값1(구분자)값2(구분자)값3(구분자)
- 각 값을 특정 문자를 이용해 구분하는 방식
- 값의 위치에 따라 의미가 결정
- 구현 간단
- 파싱 속도도 빨라서 널리 사용
이름1=값1 이름2=값2 이름3=값4
- 이름과 값 한쌍으로 묶음
- 각 쌍은 공백 문자로 구분, 값에 공백이 포함될 수 있다면 탭이나 ^같은 구분자 사용하기도 함
- 위치에 관계없이 어떤 값인지 알 수 있다
- 값/구분자보다 데이터 크기가 더 커짐
json문자열
- 대부분의 프로그래밍 언어는 json 변환 기능을 제공
- 데이터 크기가 커진다.
재처리 기능 만들기
어떤 이유에서든 전송에 실패하면 일정 시간 후에 재전송하는 기능을 구현해 두어야 한다
- 배치가 성공했는지 확인하고 실패했다면 재실행하는 방식
- 한두 번 정도만 재시도해도 수작업으로 재처리하는 번거로움을 상당히 줄일 수 있음
CDC
변경된 데이터를 추적하고 판별해서 변경된 데이터로 작업을 수행할 수 있도록 하는 소프트웨어 설계 패턴
- DB는 변경된 데이터를 CDC 처리기에 전송
- 커밋된 데이터만 변경된 순서에 맞게 전달
- CDC 처리기에는 롤백된 데이터가 전달되지 않는다
- 잘못된 순서로 데이터가 전달되는 일도 없다
CDC 처리기 대상 시스템 변경 데이터 전파 형태
- 변경 데이터를 그대로 대상 시스템에 전파
- 1대1의 관계를 가질 때 적합
- 변경 데이터를 가공/변환해서 대상 시스템에 전파
- 목적에 따라 CDC 처리기
- 메시징 시스템에 데이터를 전파하면 여러 시스템에 변경된 데이터를 전달할 수 있어 확장에 유리
CDC와 데이터 위치
변경 데이터를 어디까지 처리했는지 기록해야 한다.
- 로그 파일에서의 위치를 기록해야 CDC 처리기를 재시작할 때 마지막으로 조회한 로그부터 읽어올 수 있음
- 위치를 기록하지 않으면 마지막 로그 데이터부터 읽어와야 하는데, 재시작하는 시간 동안 발생한 변경 데이터를 놓치게 된다.
CDC가 유용할 때
시스템이 복잡해서 연동 코드를 넣기 부담스러울 때 유용하다
- 코드를 수정하지 않고도 CDC를 사용해 타 시스템에 관련 데이터를 전파할 수 있음
- 시스템 개발 일정에 주는 영향을 최소화할 수 있음
자료 출처
https://product.kyobobook.co.kr/detail/S000216376461