저희팀은 온엔더팜이라는 서비스를 만들때, 먼저 모놀리식 구조로 설계를 하였습니다.
모놀리식 구조에서 MSA구조로 전환할때 가장 중요하게 생각했던 것이 트랜잭션이었습니다.
그래서 서비스의 모든 트랜잭션을 조사하여 트랜잭션 단위를 묶어서 서비스를 나누었습니다.
그리하여 저희 팀은 6개의 서비스로 나누게 되었습니다.
MSA구조를 설계하기 위해 Spring cloud를 사용하였습니다. 저희는 spring cloud에서 제공하는 discovery service인 유레카서버, 인증,권한 필터, 부하분산등을 위한 apigateway, 서킷브레이커 중 하나인 resilience4j, 모니터링 툴인 zipkin서버, 서비스 간의 통신을 위한 openfeign을 사용하였습니다. 로드밸런싱의 경우 유레카서버에서 제공하는 client-side 로드밸런싱을 이용하였습니다. 또한 결과적 일관성을 구현하기 위해 카프카를 사용하였습니다.
아래는 ZIPKIN으로 본 저희 서비스들의 연관관계를 나타낸 화면입니다.
저희 온엔더팜은 주문으로 인해 재고차감 및 판매 count증가 , 졀제 처리, 멤버 포인트 적립과 같은 트랜잭션들이 분산 되어있습니다.
이러한 MSA구조에서는 분산되어 있는 트랜잭션들을 제어할 수 있는 방법이 필요합니다.
여러 방법이 있겠지만 저는 TCC방식을 이용해 간단한 분산 트랜잭션을 구현하였습니다.
TCC방식이란 API호출을 한번에 끝내는 것이 아니라 TRY와 CONFIRM 2번에 걸치는 방식을 의미합니다.
즉, TRY하고 전부 CONFIRM하거나 전부 CANCEL하는 방식입니다.
위의 화면이 제가 구현한 전반적인 TCC 방식의 분산트랜잭션 구조입니다.
이제 하나씩 그 과정을 설명드리도록 하겠습니다.
첫번째, 사용자가 주문을 요청합니다.
두번째, 주문 상품의 재고 차감과 판매 카운트 증가를 위해 TRY를 합니다. 이때, 재고 차감과 판매 카운트의 정보를 PRODUCT DB의 예약 테이블에 저장합니다.
세번째 결제를 위한 TRY를 하고, 결제를 위한 리소스를 PAYMENT DB의 예약 테이블에 저장합니다.
네번째 포인트적립을 위한 TRY를 하고, 포인트 적립을 위한 리소스를 MEMBER DB의 예약 테이블에 저장합니다.
다섯번째는 실제 주문서를 ORDER DB에 저장합니다.
6,7,8번째는 예약된 리소스들을 확정처리하게 됩니다. 이때, 결과적 일관성을 유지하기 위해 카프카를 사용하게 됩니다.
이제 결과적 일관성에 대해 설명드리겠습니다.
만약, 주문을 할때, 결제를 CONFIRM하는 과정에서 실패를 한다면 이 주문에 대한 데이터 일관성이 깨지게 됩니다.
저는 데이터 일관성을 유지하기 위해 결과적 일관성을 구현하였습니다.
결과적 일관성이란 단기적으로 데이터 일관성이 깨지더라도 결국은 데이터 일관성이 유지된다는 개념입니다.
이는 아마존이나 스포티파이 와 같은 기업에서도 사용되는 개념입니다. 저는 결과적 일관성을 구현하기 위해 카프카를 사용하였습니다.
결제에 대한 예약된 리소스를 처리하기 위해 결제 처리를 위한 메시지를 카프카에 발행을 하게 되고, 그 메시지를 구독을 하여 처리를 시도합니다.
저희는 카프카를 사용할때, 수동 커밋을 사용하였는데, 이는 메시지 처리를 실패할 경우 재처리를 하기 위함입니다.
카프카에서는 offset이라는 개념이 존재합니다. offset이란 메세지의 상대적인 위치를 나타낸 것을 의미한다. 이때 다음 메시지를 읽기 위해서 offset을 옮기는 것을 commit이라고 부른다.
commit의 경우 자동commit과 수동commit으로 나뉘는데, 아래처럼 java코드를 사용해 구현할 수 있었습니다.
그래서 저희는 1초의 기간을 두고 5번 재 처리를 하도록 구현하였으며, 이마저도 실패할 경우 별도의 데드레터토픽을 두어 처리되지 못한 메시지를 보관하도록 하였습니다. 또한 이러한 메시지는 관리자가 확인할 수 있도록 하였습니다.
저희 온엔더팜은 단건 주문을 할 경우 다수의 데이터베이스 접근하게 됩니다.
그래서 저희는 insert에 대한 작업을 JPA가 아닌 카프카 커넥트로 위임하여 성능 개선을 하게 되었습니다.
즉, 아래와 같이 order-service는 메시지를 생성만 하고 해당 메시지에 대한 INSERT를 카프카 싱크에 맡겨 비동기적 처리를 구현하였습니다.
카프카 커넥트의 경우 REST API를 제공하기 때문에 다음과 같이 POSTMAN을 사용하여 SINK-CONNECT를 생성할 수 있습니다.
저는 iamport라는 api를 사용하여 결제를 구현하였습니다.
프론트의 경우 손은성 팀원과 함께 결제 페이지를 구현하였습니다.
또한 결제에 대한 검증을 micro-service 중 하나인 payment-service에서 구현하였습니다. 결제에 대한 검증은 실제 결제된 금액과 결제할 금액이 맞는지를 검증합니다.
iamport에서 제공하는 rest api를 이용해 JWT를 가져오고 해당 JWT와 가맹점 식별코드를 사용하여 실제 결제 금액을 가져와 검증을 하게됩니다.
참고자료
https://velog.io/@h1225hs/%EC%95%84%EC%9E%84%ED%8F%AC%ED%8A%B8
https://www.popit.kr/rest-%EA%B8%B0%EB%B0%98%EC%9D%98-%EA%B0%84%EB%8B%A8%ED%95%9C-%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B5%AC%ED%98%84-1%ED%8E%B8/
안녕하세요. 질문 드리고 싶은게 있는데 혹시 이메일 알려주실 수 있을까요?