MSA 정리
1-1 오리엔테이션
MSA의 기능별로 어떤 역할을 하는지 체크해야 한다.
이에 따라 MSA가 다른 환경에서 개발하더라도 해당 역할/기능들을 구현해 나가면 된다.
1-2 MSA
모놀리틱 구조에서 배포의 문제
특정 기능(서비스)을 수정하면 다른 기능에 영향이 갈 수도 있고,
이후 배포 시 다른 기능들도 같이 업데이트가 된다.
배포에 소요되는 시간 동안 문제가 생길 수 있다.
이때 어느 곳에서 문제가 생긴 지 알 수 없다.
MSA에서는 서비스를 나눠서 독립적으로 관리하기에
각각 따로 배포할 수 있어 다른 서비스에 영향을 미치지 않는다.
하지만 그만큼 각각의 서비스를 관리하는데 발생하는 비용이 증가하고
각 서비스를 연결/호출하는데 비용이 발생한다.
1-3 Spring Cloud
MSA를 구축하는데 필요한 스프링 프레임워크의 확장
- 서비스 등록 및 디스커버리
- Eureka : 각 서비스의 위치를 동적으로 관리한다.
- Ribbon : 라우팅시 서비스의 부하를 낮추기 위해 로드밸런싱을 제공한다.
사용되는 로드밸런싱 알고리즘인 라운드로빈은 각 서비스를 하나씩 돌아가면서 호출한다.
- 서킷 브레이커
- Hystrix : 서비스 간의 호출 실패를 감지 ( 레거시 )
- Resilience4j ( R4j ) : Hystrix의 대안으로 3개의 상태(클로즈드 -> 오픈 -> 하프-오픈)로 나눠 호출 실패 시 로직을 처리한다.
- API 게이트웨이
1. Zuul ( 레거시 )
2. Cloud Gateway
서비스가 나눠짐에 따라 클라이언트 입장에서 일관된 요청 처리를 위해
각 서비스 앞단에서 클라이언트의 요청을 처리하고 각 서비스로 라우팅 한다.
이때, 클라이언트의 요청 전 후에 필터작업이 가능하여 로그인처리 및 HTTP 가공을 할 수 있다.
- 구성 관리
Spring Cloud Config로 Config Server와 Client로 나누고
Config Server에서 각 클라이언트들의 설정 파일을 중앙에서 관리한다.
- Neflix
모놀리틱에서 MSA로 전환한 이유는 확장성 때문이었다.
MSA + CI/CD 파이프라인 + MSA에 필요한 도구를 자체적으로 개발 + AWS로 서비스를 구축했다.
스프링 참고 사이트 https://spring.io/projects/spring-cloud#learn
Spring Boot
spring.io
- GA : 정식 릴리즈 버전
- SNAPSHOT : 개발 중인 버전
- 3.3.1 => 주버전.마이너버전.패치버전
전체적인 틀)

- 각 서비스의 위치는 Eureka Server가 관리한다.
게이트웨이에서 호출 시 Eureka Server에서 레지스트리를 받아 라우팅 한다.
- 각 서비스의 설정 파일은 Config Server가 관리한다.
각각의 DB 정보, 시크릿 키 등의 환경정보를 관리할 수 있다.
1-4 서비스 디스커버리
Eureka Server / Eureka Client는 각자 yml 파일에 설정을 해야 한다.
서버와 클라이언트 둘 다 자신의 포트번호와 호스트명을 설정한다.
서버에서는 다른 Eureka 서버에 등록/가져오기를 하지 않는 설정
클라이언트가 사용할 서버 URL을 등록한다.
그리고 앱에서 @EnableEurekaServer를 붙인다.
클라이언트는 Eureka 서버설정을 가져오고
서버에 등록/가져오기 설정을 한다.
클라이언트는 앱에 별도의 어노테이션을 붙이지 않아도 동작한다.
RestTemplate과 FeignClient 둘 다 다른 서비스를 호출하는 용도이다.
호출 방식이 명령형이냐 선언형이냐의 차이가 있다.
- RestTemplate 사용
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
먼저 @Bean 수동 등록을 함과 동시에
@LoadBalanced로 로드밸런싱 설정을 한다.
@GetMapping("/get-data-rest")
public String getDataWithRestTemplate() {
String serviceUrl = "http://my-service/api/data";
return restTemplate.getForObject(serviceUrl, String.class);
}
컨트롤러에서 해당 서비스의 호스트명을 사용한다.
Eureka 서버에서 호스트명으로 해당 서비스를 찾아내서 요청이 보내지게 된다.
- FeignClient 사용
1. 사용할 서비스앱에서 @EnableFeignClients를 붙인다.
2. @FeignClient를 직접 생성한 인터페이스에 붙이고
옵션으로 name ="유레카에 등록된 서비스 이름"을 넣는다.
그리고 가져올 서비스의 컨트롤러를 참고해서 메서드를 정의한다.
이때 메서드명은 달라도 되지만 파라미터나 HTTP 요청/엔드포인트는 동일하게 설정해야 한다.
@FeignClient(name = "my-service")
public interface MyServiceClient {
@GetMapping("/api/data")
String getData();
}
3. 2에서 만든 인터페이스의 메서드를 필요한 곳에서 호출한다.
@RestController
public class MyFeignClientController {
@Autowired
private MyServiceClient myServiceClient;
@GetMapping("/get-data-feign")
public String getDataWithFeignClient() {
return myServiceClient.getData();
}
}
- 의존성 ( 자바 3.5.11 기준 )
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
}
1-5 로드밸런싱
로드밸런싱 : 클라이언트 사이드 로드밸런싱 / 서버 사이드 로드 밸런싱
클라이언트 사이드 로드 밸런싱 : 클라이언트가 직접 여러 서버 중 하나를 선택해서 요청을 보내는 방식
클라이언트는 서버의 목록을 가지고 있다 => 이를 바탕으로 로드밸런싱 수행
FeignClient
Eureka와 통합하여 서비스 인스턴스의 목록을 동적으로 조회, 로드밸런싱 수행 => 클라이언트 사이드 로드 밸런싱 중 하나
Ribbon이 통합되어 있어 자동으로 로드 밸런싱을 수행한다.
앱에서 @EnableFeignClients를 붙여야 동작한다.
자동 로드 밸런싱을 활성화하기 위해 yml에 아래 설정을 추가한다.
my-service:
ribbon:
eureka:
enabled: true
1-6 서킷브레이커
R4j : 서킷브레이커 라이브러리, 서비스 간 호출 실패 감지, 장애 격리 => 복원력 높임
서킷브레이커는 3개의 상태로 호출 실패를 관리한다.
1. 클로즈드(Closed)
정상적인 상태
실패율이 임계점을 넘기면 오픈 상태로 전환
2. 오픈(Open)
오픈상태로 전환 시 모든 요청을 실패로 처리한다.
이 상태에서 요청이 들어오면 바로 에러 응답을 반환한다.
설정된 대기시간 후 하프-오픈으로 전환
3. 하프-오픈(Half-Open)
제한된 수의 요청을 허용하여 정상 동작하는지 체크
-> 제한된 수의 요청이 모두 성공한다면 클로즈드로 전환
-> 하나라도 실패한다면 다시 오픈상태로 전환
호출 실패 시 대체 로직을 제공하는 Fall back
서킷브레이커의 상태를 모니터링하는 도구 제공
R4j는 깃허브의 구현체가 있는 버전을 사용한다.
스프링 이니셜라이저로 추가한다면 구현체가 포함되지 않는다.
- 의존성
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
- R4j의 yml 설정
resilience4j:
circuitbreaker:
configs:
default: # 기본 구성 이름
registerHealthIndicator: true # 애플리케이션의 헬스 체크에 서킷 브레이커 상태를 추가하여 모니터링 가능
# 서킷 브레이커가 동작할 때 사용할 슬라이딩 윈도우의 타입을 설정
# COUNT_BASED: 마지막 N번의 호출 결과를 기반으로 상태를 결정
# TIME_BASED: 마지막 N초 동안의 호출 결과를 기반으로 상태를 결정
slidingWindowType: COUNT_BASED # 슬라이딩 윈도우의 타입을 호출 수 기반(COUNT_BASED)으로 설정
# 슬라이딩 윈도우의 크기를 설정
# COUNT_BASED일 경우: 최근 N번의 호출을 저장
# TIME_BASED일 경우: 최근 N초 동안의 호출을 저장
slidingWindowSize: 5 # 슬라이딩 윈도우의 크기를 5번의 호출로 설정
minimumNumberOfCalls: 5 # 서킷 브레이커가 동작하기 위해 필요한 최소한의 호출 수를 5로 설정
slowCallRateThreshold: 100 # 느린 호출의 비율이 이 임계값(100%)을 초과하면 서킷 브레이커가 동작
slowCallDurationThreshold: 60000 # 느린 호출의 기준 시간(밀리초)으로, 60초 이상 걸리면 느린 호출로 간주
failureRateThreshold: 50 # 실패율이 이 임계값(50%)을 초과하면 서킷 브레이커가 동작
permittedNumberOfCallsInHalfOpenState: 3 # 서킷 브레이커가 Half-open 상태에서 허용하는 최대 호출 수를 3으로 설정
# 서킷 브레이커가 Open 상태에서 Half-open 상태로 전환되기 전에 기다리는 시간
waitDurationInOpenState: 20s # Open 상태에서 Half-open 상태로 전환되기 전에 대기하는 시간을 20초로 설정
서킷브레이커의 인스턴스가 생겼을 때 위 default 설정으로 생성된다.
- Fallback 사용법
@Service
public class MyService {
@CircuitBreaker(name="서킷브레이커 서비스명", fallback="호출실패시 사용할 메서드명")
public String myMethod(){
// 외부 서비스 호출
return 외부서비스.메서드(); // 꼭 외부서비스 호출 메서드일 필요는 없다.
}
public String 호출실패시사용할메서드명(){
return "호출 실패";
}
}
즉, 위에 name에 적은 서비스명으로 서킷브레이커의 인스턴스가 생성되고
yml에서 설정한 대로 default 설정을 가진다.
Fallback으로 장애가 전파되는 것을 방지한다.
- R4j Dashboard 의존성
dependencies {
implementation 'io.github.resilience4j:resilience4j-micrometer'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
Prometheus로 수집된 메트릭을
Grafana 대시보드로 시각화
체크경로)
http://${hostname}:${port}/actuator/prometheus
- Eureka를 활용
spring:
application:
name: my-service
cloud:
circuitbreaker:
resilience4j:
enabled: true
1-7 API GW
클라이언트의 요청을 백앤드 서비스로 라우팅 + 보안/로깅/모니터링/필터링
- 의존성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
필터)
Global Filter -> 클라이언트의 모든 요청에 대해 작동
Gateway Filter -> 특정 라우트에만 작동
Mono => 0 or 1개의 데이터를 비동기로 처리
동기 vs 비동기
동기 => 순차적으로 처리
비동기 => 동시에 시작, 다음 동작의 순서를 제어할 필요가 있음
ServerWebExchange => HTTP 요청과 응답을 캡슐화한 객체
- exchange.getRequest()
- exchange.getResponse()
GatewayFilterChain
chain.filter(exchange)
Pre 필터 => 요청이 처리되기 전에 실행 ( then 사용 x, 즉 처리하고 return )
Post 필터 => 요청이 처리되고 난 후 실행 ( then 사용, 즉 return문에서 사용 )
- 게이트웨이의 yml 설정
server:
port: 19091 # 게이트웨이 서비스가 실행될 포트 번호
spring:
main:
web-application-type: reactive # Spring 애플리케이션이 리액티브 웹 애플리케이션으로 설정됨
application:
name: gateway-service # 애플리케이션 이름을 'gateway-service'로 설정
cloud:
gateway:
routes: # Spring Cloud Gateway의 라우팅 설정
- id: order-service # 라우트 식별자
uri: lb://order-service # 'order-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/order/** # /order/** 경로로 들어오는 요청을 이 라우트로 처리
- id: product-service # 라우트 식별자
uri: lb://product-service # 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/product/** # /product/** 경로로 들어오는 요청을 이 라우트로 처리
discovery:
locator:
enabled: true # 서비스 디스커버리를 통해 동적으로 라우트를 생성하도록 설정
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/ # Eureka 서버의 URL을 지정
- 다른 서비스처럼 기본적인 Eureka Client 설정
- reactive 설정
- 라우팅, 로드밸런싱 설정
1-8 보안구성
JWT는 값이 변조되었는지 아닌 지를 체크한다.
즉, 값이 그대로 왔는지 아닌 지
로그인 유무, 즉 JWT를 소지하고 있는지 아닌 지를 게이트웨이에서 체크할 수 있다.
Auth 서비스에서는 Spring Security로 로그인 시 JWT를 발급, 회원 가입 시 DB에 저장한다.
게이트웨이에서는 유저가 어떤 요청을 했을 때 JWT를 가지고 있는지 체크한다.
- JWT 의존성
implementation 'io.jsonwebtoken:jjwt:0.12.6'
Auth 앱에서는 기존 Eureka Client 설정과 더불어
JWT 시크릿키와 만료시간 등의 JWT 관련 설정도 추가한다.
- Auth의 yml 설정 일부
service:
jwt:
access-expiration: 3600000
secret-key: "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"
게이트웨이에서 Pre 필터를 만들어서
로그인이나 회원가입 요청은 필터를 바로 통과하게 설정한다.
마찬가지로 Auth에서도 SecurityChainFilter에서 로그인/회원가입 등의 요청은 인증하지 않는 설정을 추가한다.
1-9 Config
각 서비스들의 설정 파일을 중앙에서 관리, 변경 사항을 앱의 재배포 없이 실시간으로 반영할 수 있다.
- Config 서버 의존성
dependencies {
implementation 'org.springframework.cloud:spring-cloud-config-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
추가로 앱에서 @EnableConfigServer을 붙인다.
- 클라이언트 의존성
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-config'
}
- 클라이언트 yml 설정
spring:
application:
name: my-config-client
cloud:
config:
discovery:
enabled: true
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
기본 Eureka 클라이언트 설정에
컨피그 서버를 등록하는 설정을 추가한다.
컨피그 서버에서는 서비스명-프로필.yml을 생성/관리해 각 서비스들의 환경에 맞게 제공할 수 있다.
변경된 설정을 수동 반영하는 법)
- Config Client의 yml 파일을 수정
management:
endpoints:
web:
exposure:
include: refresh
위 부분을 추가한다.
- 각 서비스에서 /actuator/refresh로 POST 요청을 호출한다.
네이티브 모드 => 설정 변경을 위해 재시작을 해야 한다.
1-10 분산추적
각 서비스의 호출 관계와 성능을 시각화하여 지연시간을 확인할 수 있다.
Micrometer
서킷브레이커와 마찬가지로 대시보드를 제공한다.
Zipkin
각 서비스의 트레이스와 스팬 데이터를 저장 + 호출 흐름을 시각화
검색 및 필터링(트레이스와 스팬)
+ 도커로 실행하는 것이 제일 간편
docker run -d -p 9411:9411 openzipkin/zipkin
- Zipkin yml 설정 추가
management:
zipkin:
tracing:
endpoint: "http://localhost:9411/api/v2/spans"
tracing:
sampling:
probability: 1.0
- 의존성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.github.openfeign:feign-micrometer'
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
}
1-11 이벤트 드리븐
1. 이벤트들을 모아서 이벤트 소스를 만든다.
- 배송지 입력
- 결제 처리
- 상품 처리
=> 이벤트 소스
2. 이벤트 버스에서 각 서비스로 이벤트를 전달한다. ( Message Queue )
ex) Kafka, RabbitMQ
3. 이벤트핸들러에서 각 이벤트를 수신하여 처리한다.
- 재고 서비스
- 배송 서비스
- 결제 서비스
3개의 이벤트 중 2개는 성공하고 1개만 실패했을 때,
성공한 2개도 취소되어야 한다면?
다시 취소 요청을 보내야 한다.
이때 에러를 담당하는 서비스가 별도로 존재할 수도 있다.
이 경우엔 에러서비스에서 모아서 다시 메인 큐(이벤트 버스)로 취소 요청을 보낸다.
이후 다시 이벤트 핸들러에서 처리
이벤트를 생성하는 주체를 Producer
이벤트를 처리하는 주체를 Consumer라고 한다.
1-12 마무리
쿠버네티스는 애플리케이션 배포 자동화 및 운영 플랫폼이다.
MSA가 애플리케이션 레벨에 초점을 뒀다면
쿠버네티스는 인프라 레벨에 초점을 뒀다.
실제 기업에서는 쿠버네티스를 커스텀해서 UI로 활용하기도 한다.
Gateway에서 JWT에 대한 검증을 진행한다.
JWT 검증 시 role정보를 요청 헤더에 키를 x-user-id, x-role로, 값을 JWT에 넣을 clain값으로 설정하고
각 앱에서 @RequestHeader 통해서 가져올 수 있다.
이후 가져온 권한에 따라서 CRUD 같은 기능에 접근을 제한할 수 있다.
삭제는 실제로 데이터 한 행을 삭제할 수도 있지만
엔티티에 deletedAt 같은 필드를 만들고
삭제가 필요할 때 deleteAt에 현재 시간을 기록하고
조회 시 deletedAt 칼럼의 값이 null인 값만 조회할 수 있다.
이는 삭제된 데이터를 확인/복구하는데 도움이 된다.
메시지큐(이벤트 버스)를 도입해서 요청을 처리할 수 있다.
API시각화는 로그인/회원가입과 각 서비스의 CRUD + Search들을 시각화하여 나타내어 개발범위를 명확히 할 수 있다.
QueryDSL로 검색 기능에서 조건검색을 여러 개로 나눠 만들지 않고
하나의 메서드로 처리할 수 있다.
- JPA의 경우에는 쿼리메서드로 각각 정의/사용해야 한다.
빌더 패턴 사용 시 액세스 레벨을 설정하여 외부에서 생성하지 못하게 제한할 수 있다.
Enum사용으로 입력값을 제한할 수 있다.
'내배캠' 카테고리의 다른 글
| [내일배움캠프 본캠프] 15일 차 TIL 1차 팀 프로젝트 시작 (0) | 2026.02.24 |
|---|---|
| [내일배움캠프 본캠프] 14일 차 TIL Layered Architecture (0) | 2026.02.23 |
| [내일배움캠프 본캠프] 13일 차 TIL 상품-주문 MSA 프로젝트 (0) | 2026.02.22 |
| [내일배움캠프 본캠프] 12일 차 TIL MSA (0) | 2026.02.21 |
| [내일배움캠프 본캠프] 11일 차 TIL MSA, Git flow (0) | 2026.02.20 |
