개인 프로젝트

Kafka로 메시지 전송하여 지연이체 처리하기

행복을 찾아서 떠나자 2024. 12. 28. 23:58

1. 서론

https://peaceful-dev.tistory.com/12

 

리눅스 서버에 Docker Compose로 Kafka 구동하기

1. 서론이번에 회사 동기들과 지연이체 서비스를 구현해보려고 한다.시작은 아래 카카오 테크 발표 영상이었고, 우리의 목적은 카프카를 공부하고 적용해보자는 것에 있다.https://www.youtube.com/watc

peaceful-dev.tistory.com

저번 글에 이어서 카프카를 실제로 Tomcat으로 띄운 WAS와 연결하여 지연이체를 처리하도록 만들어 보자.

 

출처: https://www.youtube.com/watch?v=LECTNX8WDHo&ab_channel=kakaotech

 

우선 카카오테크에서 발표한 자료에 의하면 지연이체 아키텍처는 위와 같다.

 

1) 사용자가 지연이체를 신청하면 DB에 지연이체 신청내역을 저장한다.

2) Scheduler가 5분마다 테이블을 조회하여 아직 처리되지 않은 지연이체 대상을 가져와서 Kafka에 메시지를 제공한다.

3) Consumer가 이 메시지를 읽어와서 송금실행 API를 호출한다.

 

이번에는 아주 간단하게 위와 유사한 아키텍처를 설계하고자 한다.

 

2. 아키텍처

https://github.com/Kbank-Tech-Lab/kafka-study

 

GitHub - Kbank-Tech-Lab/kafka-study

Contribute to Kbank-Tech-Lab/kafka-study development by creating an account on GitHub.

github.com

 

수정 개발이 잦은 부분은 로컬에서 Tomcat으로 띄워 테스트하기 용이하게 하였고, 그 외 부분은 각자 환경에서 동일하게 돌아가도록 Docker 컨테이너 위에서 실행하였다.

 

버전 1.0의 테이블 설계는 다음과 같다.

 

3. 지연이체 버전 1.0 테스트

여러 가지 트러블 슈팅 과정이 있었다. 모두 다 해결하기는 했는데 Kafka Topic을 Producer에서 만들기 때문에 Tomcat 서버 실행 순서가 정해져 있다.

 

  1. Core Banking 실행
  2. Core Banking의 초기화 API 호출 (테이블 초기화 및 테스트 데이터 생성)
  3. Producer 실행
  4. Consumer 실행

Producer와 Consumer 실행 전에 미리 호출하는 이유는 송금 API가 호출되었을 때 계좌에 Lock이 걸리면서 초기화 API가 실행되지 못하기 때문이다.

 

결과는 다음과 같다.

 

Producer

1분마다 돌며 처리되지 않은 지연이체 대상을 카프카 메시지로 전송한다.

 

 

Consumer

일단 Producer가 메시지를 보내고 나면 Consumer에서 이를 줄줄이 모두 읽어온다.

Consumer 내부적으로 하나의 쓰레드에서 비동기로 Core Banking에 송금 요청을 보낸다. 이때 새로운 쓰레드 생성 후 Redis 락을 획득(1번)하고 요청을 보낸다(2번). 이후 응답이 돌아오면 락을 해제(3번)하는 모습이다.

 

다만 2번 로그가 찍히는 시점이 요청을 보낸 직후가 아니라 응답이 돌아온 직후인 것은 코드가 아래와 같기 때문이다. 송금 API 호출은 동기이다. (Consumer가 아래 processTransfer 메서드를 호출하는 부분이 비동기)

 

 

Core Banking

계정계에서는 요청이 들어와서 송금을 완료되면 로그 테이블에 결과를 적재한다.

또한 지연이체 신청내역 테이블의 status를 PENDING에서 COMPLETED로 변경함으로써 Consumer에서 중복 송금 요청하지 않도록 방지한다.

 

4. 트러블 슈팅

여러 가지가 있었지만 하나만 기록하자면, 초기에는 Consumer에서 비동기로 여러 개의 요청을 보내지 않고 동기로 하나만 보냈다. 그러다가 10개씩 보내는 걸로 변경하자 갑자기 Core Banking에서 DB connection 획득 실패와 함께 이로 인한 Timeout이 발생하기 시작했다.

 

Hikari의 maximum-pool-size의 default 값은 10이다. 그런데 Consumer에서도 10개의 쓰레드 풀을 사용하므로 최대 10개의 요청만 갈텐데 뭐가 문제지? 알고 보니 송금 API에서 타행이체 Timeout 설정을 위해 타행이체 실행 메서드를 비동기로 호출한 후 60초가 지나면 오류를 반환하고 있었다. 여기서 호출된 타행이체 메서드에서 은행별 점검시간을 가져오기 위해 Bank 테이블을 조회하고 있었다. 따라서 송금 API 트랜잭션타행이체를 실제로 실행하는 트랜잭션이 각각 DB connection을 사용하므로 최대 20개의 connection이 필요한 것이었다. 이에 따라 아래와 같이 설정을 추가하자 문제가 사라졌다.

hikari:
  maximum-pool-size: 20

 

5. 고도화

테스트 결과 처리 속도는 매우 빨랐다. 타행이체 delay를 평균 3초로 설정하였고, Consumer에서는 10개의 요청을 동시에 보내도록 했다. 이에 따라 병목현상 없이 3초에 약 10개, 1분에 약 200개의 요청이 처리됨을 확인할 수 있었다.

 

다만 Redis 글로벌 락을 사용함으로써 중복송금이 발생하지 않았는지, 잔액 부족, 점검 시간 등으로 인한 오류가 잘 처리되었는지는 확인할 수 없었다. 이는 로그 테이블에 지연이체 신청내역 ID가 포함되지 않았기 때문이다. 이에 따라 추적을 위해 로그 시스템 고도화를 진행하려고 한다.

 

이외에도, Consumer 배치 처리, 카프카 파티션 개수 조정 등을 실험해보려고 한다.