12일 차 TIL
어제 배운 Eureka와 로드밸런싱과 더불어
오늘은 MSA에 대한 전반적인 지식과 사용법에 대해서 배웠다.
ex) R4j, Spring Cloud Gateway, Config Server, Zipkin, 이벤트 드리븐 아키텍처
+) Talend API
내일 Todo)
1. 다시 한번 MSA의 전반적인 개념들을 복습
2. MSA 마무리 강의 수강 ( 복습 프로젝트 )
Talend API
크롬 확장프로그램으로 Postman과 같이 백엔드의 기능을 테스팅할 수 있는 앱이다.

브라우저에서 실행되는 만큼 쿠키나 세션정보도 자동으로 불러온다.
또한 따로 앱을 실행해야 하는 Postman에 비해 간편하다.
서킷브레이커
서비스 간 호출실패 감지, 안정성을 유지하는 패턴
클로즈드 -> 오픈 -> 하프오픈으로 상태 변화를 관리
Resilience4j
서킷브레이커 라이브러리
클로즈드(Closed)
기본 상태, 호출이 실패하면 카운터가 증가
실패율이 임계값을 넘기면 서킷브레이커가 오픈 상태로 전환
원하는 요청을 정상적으로 통과시키는 상태가 Closed 상태이다.
ex) 5번 중 3번 실패 -> 실패율이 60%에 도달 -> 오픈상태 전환
오픈(Open)
클로즈드 -> 오픈 전환 시 즉시 모든 요청을 즉시 실패처리,
이 상태에서는 요청이 실패하지 않고 에러 응답 반환
설정된 대기 시간 후 하프-오픈 상태로 전환
ex) 클로즈드 -> 오픈 전환 후 20초간 모든 요청 차단
하프-오픈(Half-Open)
오픈에서 설정한 대기시간이 지나면 하프오픈으로 전환된다.
하프-오픈 상태에서는 제한된 요청을 받고
그 요청 중 1개라도 실패 시 다시 오픈상태로 전환
모두 성공 시 클로즈드로 전환
Fallback : 호출 실패 시 대체 로직 제공
모니터링 : 상태를 모니터링/관리할 수 있는 도구 제공
- 의존성
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
깃허브의 R4j.boot3을 사용한다.
이유 : 스프링에서 제공하는 R4j는 추상화계층을 포함, 구현체가 포함되지 않는다.
yml 설정
spring:
application:
name: sample
server:
port: 19090
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초로 설정
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
서킷브레이커의 인스턴스명은 config의 default 설정을 함으로써,
인스턴스가 자동 생성되고 default 설정을 복사하여 생성된다.
Fallback 하는 법?
@CircuitBreaker(name="서킷브레이커인스턴스명", fallback="호출실패시사용할메서드명")
public String myMethod(){
// 외부 서비스 호출
return 외부서비스.메서드();
}
public String 호출실패시사용할메서드명(){
return "호출 실패";
}
Fallback을 통해 애플리케이션 담당자에게 알릴 수 있고, 다른 서비스로 장애가 전파되는 걸 막는다.
R4j Dashboard
서킷브레이커의 상태를 모니터링한다.
- 의존성
dependencies {
implementation 'io.github.resilience4j:resilience4j-micrometer'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
http://${hostname}:${port}/actuator/prometheus로 접근해서 확인이 가능하다.
prometheus에서 Grafana 대시보드에서 수집한 메트릭(시간에 따라 변화하는 수치 데이터)을 시각화
R4j - Spring Cloud 연동
Spring Cloud의 서비스 디스커버리 + 로드 밸런싱을 활용
Spring Cloud Gateway
API 게이트웨이는 클라이언트의 요청을 백엔드 서비스로 라우팅 + 부가 기능을 제공하는 중간 서버다.
클라이언트 - 서비스 간의 단일 진입점역할 + 보안 + 로깅 + 모니터링 + 요청 필터링 등을 처리한다.
Spring Cloud Gateway는 Spring Cloud Netflix 패키지의 일부로 API Gateway 중 하나이다.
동적 라우팅 : URL 패턴에 따라 동적으로 라우팅
필터링 : 요청 전후에 작업을 수행하는 필터 체인 제공
모니터링 : 요청 로그 및 메트릭을 통한 서비스 모니터링
보안 : 요청의 인증/검증
사용자는 각각의 서비스 호스트로 요청을 보내는 것이 아닌
통합된 호스트로 요청을 보냄.
게이트웨이는 뒤에 엔드포인트를 보고 라우팅
JWT를 통해 Gateway필터에서 로그인/검증 처리
=> 로그인 안 한 유저가 특정 요청을 한 횟수를 알 수 있다.
- 의존성
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'
}
web과 actuator에 더불어 gateway와 유레카-클라이언트 의존성을 주입한 모습이다.
- yml 설정
Gateway는 서비스 인스턴스명으로 라우팅을 하기 때문에 서비스 디스커버리 즉, Eureka와 연동을 해야 한다.
# spring.application.name : service-gw 같은 설정이 생략됨 ( Eureka를 위함 )
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 서비스 디스커버리를 통해 동적으로 라우트를 생성하도록 설정
routes:
- id: users-service # 라우트 식별자
uri: lb://users-service # 'users-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/users/** # /users/** 경로로 들어오는 요청을 이 라우트로 처리
- id: orders-service # 라우트 식별자
uri: lb://orders-service # 'orders-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/orders/** #/orders/** 경로로 들어오는 요청을 이 라우트로 처리
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
ex) localhost:19091/user/* 로 오는 요청을
서비스명이 users-service/user/*로 라우팅함.
이는 users-service에서 등록한 포트번호로 자동 변경된다.
users-service는 Eureka에서 해당 이름으로 된 서비스를 조회한다.
ex) users-service의 포트번호가 19092라면?
localhost:19092/user/*로 라우팅해준다.
lb는 로드밸런서를 뜻한다.
즉, Spring Cloud Gateway는 “이 요청을 users-service라는 이름을 가진 인스턴스 중 하나로 보내라”라고 하고,
users-service의 인스턴스가 3개이고 각각의 포트번호가 19092, 19093, 19094라면?
로드밸런서에서 19092 19093 19094 등에서 자동으로 하나 선택 후 라우팅해준다.
- gateway-order-product 서버(서비스)간의 동작

Spring Cloud Gateway의 필터기능
전역 설정 => GlobalFilter
특정 라우트에만 => GatewayFliter
위 2개의 필터 중 하나를 선택해서 인터페이스를 구현하고 filter를 오버라이딩
주요 객체
- Mono : 0 or 1개의 데이터를 비동기적으로 처리
- ServerWebExchange : HTTP 요청/응답을 캡슐화한 객체
- exchange.getRequest() : HTTP 요청 가져오기
- exchange.getResponse() : HTTP 응답 가져오기 - GatewayFilterChain : 필터 체인
- chain.filter(exchange) : 다음 필터로 요청을 전달
필터의 종류
- Pre 필터 : 요청 전, then은 사용 x
- Post 필터 : 요청 후 응답이 반환되기 전에, then 사용
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 응답 로깅
System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
}));
그리고 두 필터다 getOrder()로 체인의 순서를 정한다.
@Override
public int getOrder() {
return -1;
}
그리고 적용한 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을 지정
포트번호와, Mono 사용을 위한 reactive설정, 애플리케이션 네임 설정, 로드밸런싱을 위한 설정이 추가된 모습이다.
보안
OAuth2
토큰 기반의 인증/권한 부여 프로토콜
리소스 소유자, 클라이언트, 리소스 서버, 인증 서버로 역할을 정의
JWT
자가포함 : 토큰 자체에 모든 정보를 포함
간결성
서명 및 암호화 : 데이터의 무결성 즉, 데이터가 변조됐는지를 보장함
일종의 티켓
Auth 서비스앱을 만들고,
기존에 사용하던 SecurityFilterChain 그대로 사용한다.
- SecurityFilterChain
@Configuration
@EnableWebSecurity
public class AuthConfig {
// SecurityFilterChain 빈을 정의합니다. 이 메서드는 Spring Security의 보안 필터 체인을 구성합니다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// CSRF 보호를 비활성화합니다. CSRF 보호는 주로 브라우저 클라이언트를 대상으로 하는 공격을 방지하기 위해 사용됩니다.
.csrf(csrf -> csrf.disable())
// 요청에 대한 접근 권한을 설정합니다.
.authorizeHttpRequests(authorize -> authorize
// /auth/signIn 경로에 대한 접근을 허용합니다. 이 경로는 인증 없이 접근할 수 있습니다.
.requestMatchers("/auth/signIn").permitAll()
// 그 외의 모든 요청은 인증이 필요합니다.
.anyRequest().authenticated()
)
// 세션 관리 정책을 정의합니다. 여기서는 세션을 사용하지 않도록 STATELESS로 설정합니다.
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
// 설정된 보안 필터 체인을 반환합니다.
return http.build();
}
}
Jwt에 대한 인증/인가 필터는 Gateway에서 구현한다.
- 컨트롤러에서 회원가입 요청 시
@GetMapping("/auth/signIn")
public ResponseEntity<?> createAuthenticationToken(@RequestParam String user_id){
return ResponseEntity.ok(new AuthResponse(authService.createAccessToken(user_id)));
}
- JWT 생성 메서드
public String createAccessToken(String user_id) {
return Jwts.builder()
// 사용자 ID를 클레임으로 설정
.claim("user_id", user_id)
.claim("role", "ADMIN")
// JWT 발행자를 설정
.issuer(issuer)
// JWT 발행 시간을 현재 시간으로 설정
.issuedAt(new Date(System.currentTimeMillis()))
// JWT 만료 시간을 설정
.expiration(new Date(System.currentTimeMillis() + accessExpiration))
// SecretKey를 사용하여 HMAC-SHA512 알고리즘으로 서명
.signWith(secretKey, io.jsonwebtoken.SignatureAlgorithm.HS512)
// JWT 문자열로 컴팩트하게 변환
.compact();
}
시크릿 키를 통해 암호화해서 JWT를 발급한다.
Spring Cloud Gateway에 yml에 Auth 앱을 추가해 주고,
spring:
cloud:
gateway:
routes:
- id: auth-service # 라우트 식별자
uri: lb://auth-service # 'auth-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
predicates:
- Path=/auth/signIn # /auth/signIn 경로로 들어오는 요청을 이 라우트로 처리
service:
jwt:
secret-key: "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"
SecurityChainFilter에서 하던 JWT인증/인가 필터링을
GlobalFilter 인터페이스를 Gateway 서버에서 구현한다.
로그인은 필터 처리 x, Bearer 제거, 토큰 Validation을 진행한다.
그 후 성공 시, Auth로 라우팅 해서 회원가입을 진행한다.
API 흐름)
유저 회원가입 신청 => Gateway에서 인증/인가 진행 => 성공 시 Auth로 라우팅 => Auth에서 회원가입 진행, 토큰 발급
컨피그 서버(Spring Cloud Config)
애플리케이션의 설정을 중앙에서 관리
변경사항을 서버 재시작 없이 실시간으로 반영
환경별(개발, 테스트, 운영 등)로 구성을 분리해서 관리
Spring Cloud Config 서버 설정
- 의존성
dependencies {
implementation 'org.springframework.cloud:spring-cloud-config-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
- 스프링 애플리케이션 설정
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
- yml 설정 파일 구성
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/my-config-repo/config-repo
clone-on-start: true
Spring Cloud Config 클라이언트 설정
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/
컨피그 서버의 서비스명을 적어주면 된다.
이는 유레카를 통해 관리되기에 유레카 클라이언트로 선언해줘야 한다.
설정 파일명으로 환경을 분리하고, 프로필 설정을 통해 사용할 수 있다.
application-dev.yml처럼 "-"(대시)로 구분해서 프로필명을 적고, spring:profiles:active: dev로 활성화할 수 있다.
실시간 구성 변경 반영하는 방법
1. Spring Cloud Bus
메시징 시스템(RabbitMQ or Kafka)을 사용해야 한다.
2. 수동 구성 갱신
Spring Actuator의 /actuator/refresh 엔드포인트 사용
- Config 서버의 설정 파일 변경
- 클라이언트 애플리케이션에서 위 엔드포인트로 POST 요청으로 호출한다.
=> 각 클라이언트의 애플리케이션에서 수동으로 엔드포인트를 호출해야 함
3. Spring Dev Tools
파일변경을 자동으로 감지, 재시작 ( classpath 내의 파일 변경도 포함 )
4. Git 저장소 사용
Spring Cloud Config가 Git 저장소에서 설정 파일을 읽도록 설정

실습)
spring:profiles:active: native로 지정 ( 변경 반영을 위해 서버 재시작을 해야 함 )
native는 먼저, git 저장소 대신
내 컴퓨터에 있는 폴더를 저장소로 쓰는 것이다.
- 컨피그 서버에서 yml 설정
server:
port: 18080
spring:
profiles:
active: native
application:
name: config-server
cloud:
config:
server:
native:
search-locations: classpath:/config-repo
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
프로필을 native로 설정
그리고 저장소를 /resources/config-repo로 설정
그리고 config-repo에 컨피그 클라인언트들이 사용할 yml들을 만들어준다.
어플리케이션명-프로필.yml 형식으로 생성한다.
ex) product-service-local.yml
=> product-service에서 프로필을 local로 사용할 때 쓸 설정 파일이다.
- 컨피그 클라이언트 yml 설정
spring:
application:
name: product-service
profiles:
active: local
config:
import: "configserver:"
cloud:
config:
discovery:
enabled: true
service-id: config-server
eureka:
client:
service-url:
defaultZone : http://localhost:19090/eureka/
management:
endpoints:
web:
exposure:
include: refresh
message: "default message"
server:
port: 0
port를 0으로 설정해도, 컨피그 서버로부터 port를 설정했다면 그 값으로 포트를 사용한다.
프로필을 local로 설정
actuator를 활용한 수동 설정 동기화를 위해 refresh를 설정
컨피그 서버에서 설정 변경 후
컨피그 클라이언트에서 /actuator/refresh로 POST 요청 시 설정이 반영된다.
분산 추적
각 서비스마다 어느 정도의 시간이 걸렸는 지 체크할 수 있다.
오래 걸린 서비스를 파악하고, 그 서비스에서 해결을 진행할 수 있다.
트레이스
스팬
컨텍스트
Micrometer
prometheus, Grafana와 연동하여 메트릭 수집 및 모니터링하기 위한 라이브러리
분산 추적을 위한 기능도 제공
Zipkin
각 서비스의 트레이스와 스팬 데이터를 저장 + 호출 흐름을 시각화
검색 및 필터링(트레이스와 스팬)
+ 도커로 실행하는 것이 제일 간편
- 의존성
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'
- yml 설정 추가 ( 클라이언트들에게 )
management:
zipkin:
tracing:
endpoint: "http://localhost:9411/api/v2/spans"
tracing:
sampling:
probability: 1.0
Zipkin은 도커로 실행
docker run -d -p 9411:9411 openzipkin/zipkin
Zipkin은 기본적으로 9411 포트를 사용한다.
localhost:9411/zipkin/ 으로 접속해서 시각화된 것을 확인할 수 있다.
이벤트 드리븐 아키텍처
시스템에서 발생하는 이벤트를 기반으로 동작하는 소프트웨어 설계 스타일
이벤트는 비동기적으로 처리 + 독립적으로 동작

주문을 처리하기 위해 A, B, C 작업이 필요할 때,
Producer에서 이벤트를 생성 => 메인 큐로 전달 => 각 앱으로 라우팅 => 처리
만약 A와 B작업은 성공했지만
C앱에서 문제가 생긴다면 A, B 작업을 취소를 해야 한다.
이를 위해 C앱에서 에러 처리를 하는 앱으로 취소 요청을 보내면
에러 처리 앱에서 메인 큐로 요청을 보내고
메인 큐에서 취소해야 할 앱들로 취소 요청을 보낸다.
A와 B앱에서 취소 요청을 받고 취소 처리를 실행한다.
정리)
성공할 때: Producer → Queue → A, B, C (Happy Path)
실패할 때: C 에러 발생 → 에러 앱(지휘자) → Queue → A, B 취소 (Compensation Path)
마무리 구현
간단한 상품 주문 구현

'내배캠' 카테고리의 다른 글
| [내일배움캠프 과제] MSA 강의 정리 (0) | 2026.02.23 |
|---|---|
| [내일배움캠프 본캠프] 13일 차 TIL 상품-주문 MSA 프로젝트 (0) | 2026.02.22 |
| [내일배움캠프 본캠프] 11일 차 TIL MSA, Git flow (0) | 2026.02.20 |
| [내일배움캠프 본캠프] 10일 차 TIL Spring AI (0) | 2026.02.19 |
| [내일배움캠프 본캠프] 9일 차 TIL 소셜 로그인(OAuth), 단위 테스트, AOP, 예외 처리 (0) | 2026.02.18 |
