마이크로서비스(MSA)의 필요성
MSA의 출현 배경
- 잘개 쪼개져 있다, 기존 방식으로 해결할 수 없는 비즈니스의 압박으로 마이크로 단위로 쪼갬
- 왜 모뮬리식 아키텍쳐를 버리고 마이크로 서비스 아키텍처로 전환했는지?
- 비즈니스 환경의 변화
- VUCA : 한치앞도 예측할 수 없는 시대, 계획을 세우고 1년 뒤 제품 서비스시작하면 이미 시장은 변해있음
- Time To Market : 아이디어 떠올랐을때 코드 구현 후 시장에 출품할때까지의 시간, 과거엔 배포를 1년에 1번씩 진행했으나 현재는 배포 빈도가 기하급수적으로 늘어남 , 과거 개발은 무결성이 중요하나 현재는 빨리 내놓고 고객의 피드백을 받아 고객이 원하는 것을 구현하는 것 => 속도가 중요해짐
- Digital Transformation : 모든 기업은 소프트웨어 기업이 되어버림, 기능에 SW가 필수요소가 됨 (뱅킹앱 오더앱 등) SW 경쟁력 = 기업의 경쟁력
- 요구사항에 비해서 개발 속도가 느려짐 => MSA의 도입
- 안정적이지만 느린 시스템이 아닌 안정적이면서 빠른 것을 목표
- 비즈니스 환경의 변화
모놀리식 아키텍처(Monolithic) 란 ?
- 모든 기능이 하나의 어플리케이션에서 통합되어있는 구조
- UI / 비즈니스 로직 / 데이터 접근 등 하나의 큰 Application 안에 구성됨
- 공유 데이터베이스 : 모든 모듈이 하나의 거대한 통합 DB를 바라보게 됨
- 데이터 정합성 맞추기 쉬움 등 중앙관리 / 통제 수월함
- Debug , 테스트 용이
- 기능이 복잡해지고 사용자가 많아질 수록 전체를 파악하기 힘듦
- The Big Ball of Mud (거대한 진흙 덩어리) : 성공한 모놀리식 시스템
- 수년간 잦은 요구사항의 변경, 긴급 패치 , 개발자 교체 등 => 아키텍쳐 규칙이 서서히 무너짐
- 작은 기능 하나를 고치기 위해 전체 프로젝트의 히스토리를 파악해야 하는 문제 (A를 수정했으나 영향도로 인해 C D 에서 오류가 발생하는 등)
- 생산성 하락 .. 이직.. 퇴사..
부분 장애의 전파
- 사용하지 않는 기능에서 메모리 누수 발생할 경우 ? : 같은 서버에 있는 Heap Memory 다 잡아먹어서 로그인 등 다른 서비스도 같이 문제 발생
- A 서비스가 응답하지 않을 경우 , 연계되는 모든 서비스가 무너짐
- 모놀로식 -> 전파 속도가 함수 호출로 연결되어 있기 때문에 장애 전파를 막을 벽이 없음
- 전부 살거나 전부 죽거나 : 게시판 때문에 쇼핑몰을 전부 닫아야 하는 등
모놀리식 = 영향도 크고 배포하기가 힘듦 배포 단위를 쪼개야 함
부분 장애를 전체 장애로 키우지 않는 것 => MSA
비효율적인 확장성
- 각 서비스마다 Traffic이 다름
- Cloud => 성능마다 비용 측정하여 결제
- 모놀리식 : C 기능 처리를 위해서 필요하지 않은 A B 서비스 복제됨 (비용이 N배, 리소스 낭비) ![[Pasted image 20260126102015.png]]
효율적인 확장 : 필요한 서비스에서만 자원을 확장하는 것
MSA의 특성과 문제점
- 기능들이 서로 다른방에 있음, 서로 네트워크로 대화
- 얼마나 작게 구성해야 Micro한 것인지?
- 다른팀에게 허락을 받지 않아도 되는 크기
- 서비스 단위
- 효율적인 리소스 사용 , 유연한 확장 (필요한 놈만 배포)
![[Pasted image 20260126104103.png]]
Database per Service
- 모든 서비스는 각자의 DB를 가져야 한다
- 최적의 DB 저장소 고를 수 있는 자유
- 가장 큰 문제
- JOIN 불가능
- 주문 DB에는 회원 ID만 있고 이름이 없음
- Transaction
- JOIN 불가능
- 해결책 : 통신
- 동기 API 통신 (REST) : 강한 결합의 문제 발생
- 비동기 : 주문 발생 시 Message Queue에 던져놓음, 다른곳에서 그걸 받아서 처리
- Kafka, RebittMQ 구현 쉽지않음 but 비동기 EVENT 통신이 필수적임
- 격리의 중요성
- 모든 것은 반드시 고장난다
- 네트워크 끊김, DB Timeout 등 피할 수 없음
- but 한 녀석의 실패가 전체로 번지게 해서는 안된다 (도미노를 끊어야함)
- 서킷 브레이커(Circuit Breaker Pattern)
- Fail Fast (빨리 실패하기) : 되는거 먼저 빠르게 살리자
- 안전성 확보를 위한 방어 운전 시스템
![[Pasted image 20260126104502.png]]
배포 독립성 (Deployment Independence)
- 인프라 자동화 : 코드 커밋되면 자동 빌드 - 배포 - 테스트 (CI/CD)
- MSA => 쪼개진 조각들을 자동으로 관리할 수 있는 플랫폼 역량이 받쳐주어야 함
- 데이터를 섞지 않는다.
- 기다리지 않는다 (Aysnc X)
- 같이 죽지 않는다 (장애 격외 생존)
- 손으로 하지 않는다 (자동화 배포)
MSA 핵심 아키텍처 패턴
- 분산된 데이터
- 복잡한 통신
- 장애 전파
이를 해결하기 위해 아키텍처 패턴 구성이 필요함
The Landscape of MSA Patterns
MSA는 수 많은 패턴들의 집합체임
- 분해 (어떻게 쪼갤 것인가)
- 통신 (어떻게 서로 통신할 것인가)
- 데이터 (JOIN 어떻게 할지 등)
- 관측 (흩어진 로그를 묶어서 볼지)
- 회복 (죽지 않는 프로그램을 구성)
![[Pasted image 20260126105741.png]]
분해 패턴 (Decomposition Patterns) - Strangler Fig
- 기능 하나하나를 야금야금 새 시스템으로 옮겨간다
- 핵심 가치 : 리스크 최소화 (Rollback 빠름) ![[Pasted image 20260126110102.png]]
분해 패턴 (Decomposition Patterns) - Bulkhead
- 암초에 부딪혀서 구멍이 나더라도 배가 침몰하지 않게 하기위한 원리
- 하나의 서비스가 과부하로 쓰러질 때, 여파가 시스템 전체로 퍼지는 것을 막아줌
- 핵심 : 자원 격리
- 보통은 ThreadPool Java에서 하나로 할당함 / 해당 패턴에선 서비스별 Thread 부여
Integration Patterns (통합 패턴) - API Gateway
- 하나의 Gateway로 모든 Client 처리
- MSA는 서비스가 수십개로 쪼개짐
- 손님은 호텔 주방 구조를 알 필요가 없음
- 모든 클라이언트는 모바일이든 웹이든 API Gateway를 바라보게 됨
- 적절한 서비스를 라우팅 해줌
- 공통 기능 처리 : 인증, 인가, 로깅 등
- 주의점 : API Gateway가 새로운 모놀리식이 될 수 있음
- Gateway Team이 병목되어서 개발 속도가 느려질 수 있음
- Gateway에선 비즈니스 로직을 넣지 말고 보안에 충실해야 함
Integration Patterns (통합 패턴) - API Gateway + BFF
- 하나의 Gateway로 모든 Client 처리하지 않음
- Client별로 전용 Gateway 생성 (Backend For Frontend)
- 모바일용 BFF , Web용 BFF가 따로 있음
- BFF 패턴은 조직의 구조와도 연결이 됨 (FE에서 직접 관리하는 경우 등)
Data Management Patterns (데이터관리 패턴) - DB per Service
- 서비스 별 DB 구성
- 데이터의 성격에 맞는 저장소 선택할 수 있는 자유성이 존재
- JOIN 문제 발생
- 데이터 정합성
Data Management Patterns (데이터관리 패턴) - SAGA
- Transaction을 여러개의 local Transaction으로 쪼개서 순차적으로 실행함
- 시스템은 모든 상태를 일치시켜야 함
- 보상 Transcation : 주문 했다고 돈을 냈더라도 커피가 바로 주어지지 않음 (이름 불릴때까지 기다림 or 재료가 떨어지면 환불하거나 다른 메뉴 선택 등 처리)
Data Management Patterns (데이터관리 패턴) - CQRS
- 보여주는 것과 쓰는 것은 서로 달라야 한다
- 주문내역, 상품정보, 회원정보가 합쳐진 화면은 어떻게 보여줄지?
- CQRS는 쓰기모드 / 읽기모드를 분리함
- 데이터가 변경되면 이벤트를 발행함, 조회에 최적화된 형태로 뭉쳐서 저장
- 사용자가 주문 목록을 요청하면 복잡한 계산 없이 읽기 DB에서 꺼내기만 하면 됨
- 문제 : 읽기 / 쓰기 DB 설계에 시차가 존재 => 시스템의 구조 복잡해짐
- Traffic 복잡한 일부 서비스에서만 적용해야 함
Data Management Patterns (데이터관리 패턴) - Event Sourcing
- 상태를 저장하지 않고 상태를 변경시킨 EVENT (사건)을 저장함
- 내 돈 왜 만원임? 어제 5만원 있었는데 ..
- 기존 DB 에선 LOG 뒤져야 함
- 행동, 사건들을 저장해서 역추적
- 장점
- 완벽한 감사로그 (데이터 변화 추적 가능)
- 은행 , 회계, 보험시스템 등
- 타임머신 생김
- 버그 발생 시 과거 상태 재현해서 디버깅 가능
- 완벽한 감사로그 (데이터 변화 추적 가능)
- 주의 : 쓰는쪽은 이벤트를 쌓고 읽기쪽은 현재상태를 미리 만들어 두어야 함 (CQRS)
Observability Patterns (관측성 패턴) - Distributed Tracing
- 요청에 여권을 담 : Tracing ID
- 요청 처음 들어올 때 고유한 ID를 발급함, 서비스 A가 B를 호출할 때 고유한 id, B -> C에서도 해당 ID를 같이 들고감
- 시각화도구 사용 시 id 하나로 전체 여정을 그래프로 볼 수 있게됨 (추적 가능)
- A B 는 비교적 빨랐는데 C에서 오래걸렸구나, E에서 더 오래걸렸구나 라고 병목지점 확인가능 : 가시성 (Obserbility)을 확보한다
- 개발단계부터 요구-추적 전략을 세워야 함
Observability Patterns (관측성 패턴) - Monitoring & Logging
- 대시보드 플랫폼 Grappa 등 연결해서 사용가능
- 측정 x -> 관리 x
- 중앙집중형 로깅 / 모니터링 체계 구축 필요
Resiliency Patterns (회복성 패턴) - Circuit Breaker
- 과전류 흐르게되면 전류 끊겨서 가전제품 타는걸 막아줌
- 서비스 A 호출 => B가 응답이 없거나 에러를 뱉으면 A는 기다림
- A 기다리는 쓰레드 죽으면 Circuit Breaker 개입
- Close (Circuit Breaker 닫혀있는 상태)
- Open (열리면서 차단기가 올라감 , 흘러가는게 막힘)
- Half Open (회복이 되었는지 확인하는 상태, 요청 한번만 보내봄)
- 미리 준비된 poll back을 보여줌 => 고객은 오류난건지 모름
Resiliency Patterns (회복성 패턴) - Retry
- 분산 시스템에서는 네트워크 잠깐 끊길 수 있음
- 실패하면 Retry 한다
- 함정 : 잘못쓰게되면 시스템 파괴 가능
- 전략
- 언제 재시도 할 것인가?
- 404, 400은 100번 보내도 실패함 / 500 or Time에서 Retry
- 재시도 시 Traffic 과부화 막기 위해 Backoff Time 걸어놓음
- 실패하면 1초 / 2초 / 4초.. 간격을 늘려가며 서버가 숨 쉴 틈을 주어야 함
- 멱등성
- 결재 요청 보냈을 때 Timeout나면 첫번째요청이 성공했다면 고객은 두번 빠져나갈 수 있음 : Retry 쓰려면 서버에서 같은 요청이 두 번 와도 한번만 처리하는 멱등성 처리가 되어있어야 함
- 언제 재시도 할 것인가?
This post is licensed under CC BY 4.0 by the author.