Skip to content

FISA-Team-CE/tech-semina-msa

Repository files navigation

우리FISA 1차 기술 세미나: 뱅킹 시스템에서의 MSA 도입

진행 기간 : 2026.01.21 ~ 2026.02.05

👩🏻‍💻 팀원 소개

류승환
@Federico-15
서가영
@caminobelllo
유동균
@dbehdrbs0806
백주연
@juyeonbaeck

⚙️ 기술 스택

  • Language : Java 17
  • Framework : Spring Boot 3.5.10, Spring Cloud 2025
  • Database : PostgreSQL 15, Oracle XE 21, MySQL 8.0
  • Message Broker : Apache Kakfa
  • Cache : Redis
  • Monitoring : Grafana, Prometheus, Zipkin
  • Container : Docker, Kubernetes
  • Build : Gradle

📁 프로젝트 구조

  tech-semina-msa/
  ├── on-premise/                      # 온프레미스 서비스 (폐쇄망)
  │   ├── core-user-service/           # 사용자 관리 (PostgreSQL)
  │   └── core-payment-service/        # 결제 처리 (Oracle DB)
  ├── cloud-services/                  # 클라우드 마이크로서비스
  │   ├── msa-coupon-service/          # 쿠폰 발급 (MySQL, Redis, Kafka)
  │   ├── msa-point-service/           # 포인트 관리 (MySQL)
  │   └── msa-channel-user-service/    # 채널별 사용자 관리 (MySQL)
  ├── docker-compose.yml               # 개발 환경 인프라
  ├── DEPLOYMENT_GUIDE.md              # K8s 배포 가이드
  └── LOCAL_TEST_GUIDE.md              # 로컬 테스트 가이드

📊 ERD

image

🌐 아키텍처 구성

전략

보안과 규제 준수가 중요한 뱅킹 시스템을 위해 온프레미스와 클라우드를 결합한 하이브리드 클라우드 채택

특징

  1. 서비스 단위 스케일링을 통한 트래픽 제어
  2. API Gateway를 중심으로 라우팅 구성
  3. 분산 환경을 고려한 DB 분리 및 트랜잭션 이벤트 흐름 설계
fisa-msa pdf

🍎 서비스별 기능

image

1️⃣ Core User Service

계정계 : 사용자 정보 관리

1. 회원 등록

  • 클라이언트로부터 사용자 정보를 받아 계정계 회원으로 등록
  • 등록된 회원은 채널계에서 로그인을 통해 기능 사용 가능
  • 중복 가입 여부 체크 후 저장
  • 등록이 완료되면 고유 식별자인 UUID를 발급하며, 이는 MSA 시스템에서 유저 식별을 위한 global key로 사용

2. 금융 실명 관리 및 마스킹 처리

  • 금융감독원에서 발행한 <금융분야 가명 · 익명처리 안내서> 기법에 따라 고객의 실명, 주민등록번호 등 민감 정보를 AES-256 등으로 암호화하여 저장
  • 외부 채널계에는 실명 정보 노출하지 않으며, 매핑된 userUuid만 반환해 가명 처리 수행
  • 정보 조회 및 로깅 시에 마스킹 처리 후 반환

2️⃣ Core Payment Service

계정계 : 결제 및 트랜잭션

1. 계좌 관리 및 자산 처리

  • 계좌 개설 : 사용자 고유 ID(userUuid)와 매핑되는 계좌를 생성. 중복 계좌 생성을 방지
  • 입금 : 계좌 잔액을 증가. 트래픽 격리를 위해 주로 Kafka Consumer를 통해 비동기로 호출
  • 출금 : 계좌 잔액을 차감. 잔액 부족 시 예외를 발생시켜 트랜잭션을 롤백

2. 이벤트 기반 아키텍처

Kafka : 채널계에서의 트래픽으로부터 코어 뱅킹 시스템을 보호하기 위한 버퍼 역할

  • 입금 트래픽 격리 : 채널 서비스가 유효한 요청만 kafka로 전송하면, core service는 메시지를 하나씩 꺼내 core Oracle DB에 반영 
  • 쿠폰 발급 이력 저장 : 선착순 쿠폰 발급 정보를 수신해 데이터베이스에 발급된 쿠폰 정보를 영구 저장

3. 분산 트랜잭션 전략

  • AWS 클라우드와 온프레미스 Core Payment 간의 물리적으로 분리된 DB 데이터 정합성을 보장

3️⃣ Channel User Service

채널계 : 클라이언트와 계정계 사이를 중계

1. 인증 및 인가

  • 회원가입 : 채널 계정 생성 & Core User Service 실명 인증 연동
  • 로그인 : ID/PW 검증 후 JWT 토큰 발급

2. 뱅킹 서비스


  • 계좌 개설 : Core Payment Service에 계좌 생성 요청 (동기 REST 처리 방식 사용)
  • 입금 : Kafka를 통한 입금 요청 발행 (비동기 Event 처리 방식 사용)
  • 출금 : Feign Client를 통해 Core Payment Service에 출금 요청 (동기 REST 처리 방식 사용)

4️⃣ Point Service

채널계 : 포인트 결제 및 분산 트랜잭션

1. 복합 결제 (포인트 + 현금)

  • 사용자가 [ 포인트 + 현금 ] 복합 결제 요청 시, MySQL DB에서 포인트 선차감
  • 차감 성공 시 kafka에 payment_request 이벤트를 발행해 Core payment Service에 현금 출금을 요청

2. 보상 트랜잭션

분산 환경에서의 데이터 결과적 일관성 (Eventual Consistency) 보장

 @Service
 public class PaymentService {

     @Transactional
     public void processCompositePayment(PaymentRequest request) {
         // Step 0: 결제 기록 생성 (PENDING)
         Payment payment = Payment.builder()
                 .orderId(request.getOrderId())
                 .userId(request.getLoginId())
                 .pointAmount(request.getPointAmount())
                 .cashAmount(request.getCashAmount())
                 .status("PENDING")
                 .build();
         paymentRepository.save(payment);

         // Step 1: 포인트 차감
         pointService.usePoint(request.getLoginId(), request.getPointAmount());

         // Step 2: 현금 출금 요청 (비동기)
         kafkaTemplate.send("core-withdraw-request", new CashRequestDTO(
             request.getOrderId(),
             request.getLoginId(),
             request.getCashAmount()
         ));
     }

     // 보상 트랜잭션: 출금 실패 시 포인트 환불
     @Transactional
     public void compensatePayment(String orderId) {
         Payment payment = paymentRepository.findByOrderId(orderId).orElseThrow();
         if (!"PENDING".equals(payment.getStatus())) return;

         // 포인트 환불
         pointService.refundPoint(payment.getUserId(), payment.getPointAmount());
         payment.setStatus("FAILED");
     }
 }
  • Core Service로부터 payment_result: FAIL (잔액 부족) 이벤트를 수신 시, 즉시 롤백 로직 실행
  • 앞에서 차감한 포인트를 다시 더함

3. 이벤트 기반 처리

  • Kafka Producer/Consumer를 모두 배치해 Core와의 통신을 비동기 메시지로 처리

5️⃣ Coupon Service

채널계 : 트래픽 처리 및 쿠폰 관리

1. Lua Script 원자성

 -- KEYS[1]: coupon:users (Set)
 -- KEYS[2]: coupon:count (String)
 -- ARGV[1]: userUuid

 -- 1. 이미 발급받은 사용자인지 확인
 if redis.call('SISMEMBER', KEYS[1], ARGV[1]) == 1 then
     return -1  -- 이미 발급받음
 end

 -- 2. 재고 확인
 local count = tonumber(redis.call('GET', KEYS[2]))
 if count == nil or count <= 0 then
     return -2  -- 재고 소진
 end

 -- 3. 원자적으로 발급 처리
 redis.call('SADD', KEYS[1], ARGV[1])    -- 사용자 추가
 redis.call('DECR', KEYS[2])              -- 재고 차감

 return 1  -- 성공
  • Single Redis Call : 중복 체크와 재고 차감을 하나의 Lua 스크립트로 묶어 원자적으로 실행
  • Race Condition 방지 : 다수의 동시 요청이 들어와도 정확한 쿠폰 개수만큼만 발급

2. 비동기 발급

  • Kafka Producer : 쿠폰 당첨 확정 시 DB Insert 대신 Kafka 토픽에 이벤트 발행 후 즉시 응답
  • ExecutorService : 고정 스레드 풀로 Kafka 전송을 관리해 커넥션 고갈 방지
  • 롤백 로직을 통해 Kafka 장애 시에도 데이터 정합성 유지

3. 자동 복구 스케줄러

  • 1분 주기로 실행되어 Redis에는 기록되었으나 DB에 반영되지 않는 쿠폰을 감지해 Kakfa로 재전송
  • 서버 다운 시점의 누락 쿠폰 자동 복구 효과

📳 서비스 간 통신 : kafka 토픽 구조

 coupon_issue                                                    
  │  ├── Producer: core-payment-service, msa-coupon-service         
  │  └── Consumer: msa-coupon-service                              
  │                                                                  
  │  bank_deposit                                                    
  │  ├── Producer: 외부 시스템                                       
  │  └── Consumer: core-payment-service                             
  │                                                                  
  │  core-withdraw-request                                           
  │  ├── Producer: msa-point-service                                
  │  └── Consumer: core-payment-service                             
  │                                                                 
  │  core-result                                                     
  │  ├── Producer: core-payment-service                             
  │  └── Consumer: msa-point-service
 


🎬 다양한 MSA 시나리오

1️⃣ 부하테스트

  • 부하 테스트 환경
    1. 1000명 동시 접속
    2. 쿠폰 발급 API 집중 요청
    3. Prometheus로 실시간 모니터링 + Grafana로 대시보드화
스크린샷 2026-02-09 오후 6 30 26
  • 결과 1 : CPU 사용량 급증
  • auto scaling 동작 : CPU 임계값 초과 감지
스크린샷 2026-02-09 오후 6 30 47
  • 결과 2 : 평균 응답 시간 감소
스크린샷 2026-02-09 오후 6 31 06
  • 결과 3 : 초당 요청수 증가
스크린샷 2026-02-09 오후 6 31 53
  • 결과 4 : k8s pod 개수 증가
  • auto scaling 동작 : pod 개수가 확장되어 부하가 분산됨에 따라 시스템 안정화
스크린샷 2026-02-09 오후 6 32 18

2️⃣ 보상 트랜잭션: SAGA pattern

스크린샷 2026-02-09 오후 6 35 57
4단계 사용자 시나리오
1. 사용자 생성
2. 계좌 개설
3. 현금 입금
4. 포인트 입금

1. 사용자 생성

스크린샷 2026-02-09 오후 6 40 57
  • 보안 회원가입 : POST /users/register api 호출
  • 사용자 정보 암호화 및 고유 uuid 발급
  • 데이터 적재 확인 : PostgreSQL (On-prem) 유저 테이블 적재
  • HTTP 200 ok 및 uuid 리턴 확인
스크린샷 2026-02-09 오후 6 39 27 스크린샷 2026-02-09 오후 6 39 36

2. 계좌 개설

스크린샷 2026-02-09 오후 6 41 23
  • 서비스 매핑 : Post /payment/accounts api 호출
  • 사용자(uuid)와 결제 계좌의 1:1 매핑
  • 초기화 : 금융 데이터 안정성을 위해 Oracle DB 사용
  • 계좌번호 생성 및 잔액 0원 초기화
스크린샷 2026-02-09 오후 6 41 40 스크린샷 2026-02-09 오후 6 41 57

3. 현금 입금

스크린샷 2026-02-09 오후 6 42 21
  • 트랜잭션 처리 : POST /accounts/{id}/deposit api 호출
  • Oracle DB 트랜잭션 커밋
  • 자산 업데이트 : 입금액 10,000원 즉시 반영 / 데이터 정합성 및 입금 로그 확인
스크린샷 2026-02-09 오후 6 43 30 스크린샷 2026-02-09 오후 6 43 38

4. 포인트 충전

스크린샷 2026-02-09 오후 6 43 57
  • 하이브리드 클라우드 연동 : POST /users/register api 호출
  • 사용자 정보 암호화 및 고유 uuid 발급
  • 데이터 영속성 : k8s MySQL StatefulSet에 데이터 저장
  • 포인트 3,000P 적립 및 데이터 저장 확인
스크린샷 2026-02-09 오후 6 46 39 스크린샷 2026-02-09 오후 6 46 52

복합 결제 (포인트 + 현금) 시나리오

단일 성공 케이스와 fail-fast 및 SAGA pattern을 적용해 예외 상황에서도 데이터 정합성 확보

1. 복합 결제 성공 (Normal Case)

핵심 전략 : Hybrid Transaction

  • 상태 : 200 ok (결제 승인 완료)
  • 플로우
    • 현재 잔액 : 현금 10,000원 + 포인트 3,000P
    • 사용 요청 : 현금 5,000원 + 포인트 1,000P
스크린샷 2026-02-09 오후 6 49 56
  • 검증 결과
    • Oracle(현금) & MySQL(포인트) 동시 차감
    • 데이터 일관성 유지
    • 최종 잔액 : 현금 5,000원 + 포인트 2,000P

2. 포인트 결제 실패

핵심 전략 : Fail-Fast (조기 차단)

  • 상태 : 400 Bad Request
  • 플로우
    • 현재 잔액 : 현금 5,000원 + 포인트 2,000P
    • 사용 요청 : 현금 1,000원 + 포인트 3,000P (<- 보유 포인트 부족)
    • 포인트 잔액 부족 예외(RuntimeException) 발생
스크린샷 2026-02-09 오후 6 54 04
  • 검증 결과
    • 데이터 방어 : 현금 결제 트랜잭션으로의 진입 자체를 차단
    • Kafka 메시지 발생 X
    • 현금 잔액 변동 없음

3. 현금 결제 실패

핵심 전략 : SAGA rollback (보상)

  • 상태 : 500 Internal Error
  • 플로우
    • 현재 잔액 : 현금 5,000원 + 포인트 2,000P
    • 사용 요청 : 현금 10,000원 + 포인트 500P (<- 보유 현금 부족)
    • 포인트 선차감 후 현금 결제 실패가 발생
스크린샷 2026-02-09 오후 6 56 08 스크린샷 2026-02-09 오후 6 56 31
  • 검증 결과
    • 포인트 선차감 성공
    • Kafka로부터 현금 실패 이벤트를 수신 후
    • 롤백 프로세스 동작 (차감되었던 포인트 500P 자동 환불 처리)


🚀 트러블 슈팅

1️⃣ 스레드 풀 최적화

  1. 문제
  • 부하 발생 시 응답 속도의 표준 편차가 커서, 사용자 경험이 일정하지 않은 현상 발견
  1. 도입 배경
  • 부하 테스트 중 간헐적인 응답 지연 발생
  • Kafka 메시지 전송 시 동기 처리로 인한 병목 현상
  • 매 요청마다 스레드 생성/제거로 인한 리소스 낭비
  1. 적용 기술
  • 고정 크기 스레드 풀 구현 (크기: 20)
  • CompletableFuture를 활용한 Kafka 비동기 메시지 전송
  • 스레드 재사용을 통한 시스템 리소스 효율화
  1. 최종 결과
  • 스레드 풀 적용
적용
  • 스레드 풀 미적용
미적용 스크린샷 2026-02-04 오후 11 44 17
  • 평균 응답 속도 23배 향상 (3MS 달성)
  • 대규모 트래픽 상황에서도 안정적인 서비스 가능

2️⃣ CoreDNS와 /etc/resolv.conf

  1. 문제
  • CoreDNS가 /etc/resolv.conf 파일의 잘못된 주소를 읽고 참조하여 올바른 DNS를 찾지 못하는 문제 발생
  1. 해결
carbon (1)
  • CoreDNS의 설정 파일의 의존성을 제거하고 외부 공용DNS로 강제통신 하도록 수정하여  조회 실패 문제를 해결

3️⃣ 노드 IP 인식 문제

  1. 문제
  • 기존 VPC 상의 IP 172.31.x.x 를 통해 Pod끼리의 통신이 가능하나, Tailscale 사용 시 tailscale0 인터페이스를 생성
  • 따라서 kubelet이 자동으로 Tailscale의 100.xx  IP를 node IP로 인식해 꼬임 발생
  1. 해결
carbon

About

우리FISA - 1차 기술 세미나 MSA 프로젝트

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 5