2025.03.31
모놀로식 아키텍처
마이크로 서비스 아키텍처
모놀로식
마이크로 서비스 아키텍처
모놀로식 아키텍처
마이크로 서비스 아키텍처
모놀로식 아키텍처
마이크로 서비스 아키텍처
모놀로식 아키텍처
마이크로 서비스 아키텍처
Eureka

서버는 각각의 서비스 인스턴스를 클라이언트로 인식하고, 이를 관리하기 위해 Service Discovery 기능 사용
Eureka
Eureka Client
Eureka Server


dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
# Eureka Server 역할을 하는 서버를 통상적으로 사용하는 포트 = 8761
server:
port: 8761
# eureka 설정
eureka:
client:
# 서버는 클라이언트가 아니기 때문에 서버에 등록하지 않게 설정
register-with-eureka: false
# 다른 Eureka 서버로부터 Service Registry를 가져오지 않음 (자체가 Registry이기 때문)
# 다른 서비스를 주기적으로 갱신해서 인지 할 필요가 없음
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
@SpringBootApplication
<!-- 해당 애플리케이션을 Eureka Server로 활성화 -->
@EnableEurekaServer
public class [EurekaServerApplication] {
public static void main(String[] args) {
SpringApplication.run([EurekaServerApplication].class, args);
}
}


인텔리제이에서 계속 프로젝트를 실행시키면 개발 환경이 무겁고 리소스가 많이 소모
-> 프로젝트를 jar 파일로 빌드(배포)해서
운영 환경에서는 jar 파일만 실행해서 필요한 기능을 가져다 씀

jar 파일 (Java Archive) :
빌드 방식 (Gradle 기준) :

Gradle의 빌드 작업(Tasks)에서 원하는 빌드 방식을 더블 클릭해서 실행
-> build/libs 디렉토리에 [프로젝트명]-[버전]-SNAPSHOT.jar 파일이 생성

window의 경우 백그라운드에서 실행하는 방법




dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
# 클라이언트 포트 설정
server:
port: 8001
# 어플리케이션 이름 설정
# Eureka에 등록될 이름
spring:
application:
name: eureka-client
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# Eureka Server 주소
defaultZone: http://localhost:8761/eureka
@SpringBootApplication
<!-- Eureka Client(또는 Consul 등 Service Discovery Client)로 활성화 -->
@EnableDiscoveryClient
public class [EurekaClientApplication] {
public static void main(String[] args) {
SpringApplication.run([EurekaClientApplication].class, args);
}
}
@RestController
public class HelloController {
@GetMapping("/hello")
public String hi(){
return "안녕?";
}
}

어플리케이션 실행 시켜보면 설정해놓은 Eureka 대시보드에 설정해 놓은 Eureka Client 이름이 정상적으로 등록된 것을 확인할 수 있다.

Status 항목에서 링크를 눌러서 controller에 저장해 놓은 엔드포인트 url 주소를 입력하게 되면 정상적으로 동작하는지 테스트할 수 있다.
Eureka Client: First Service 등록
여러 개의 서비스를 등록해서 확인한다.
First Service


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
server:
port: 8111
spring:
application:
name: 1st-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
@SpringBootApplication
@EnableDiscoveryClient
public class [FirstServiceApplication] {
public static void main(String[] args) {
SpringApplication.run([FirstServiceApplication].class, args);
}
}

어플리케이션 실행 시켜보면 설정해놓은 Eureka 대시보드에 설정해 놓은 Eureka Client(Service) 이름이 정상적으로 등록된 것을 확인할 수 있다.
@RestController
public class FirstController {
/* Environment를 통해 현재 애플리케이션의 설정 값을 가져올 수 있음 (포트 번호 등) */
private Environment environment;
public FirstController(Environment environment) {
this.environment = environment;
}
@GetMapping("/health")
public String health() {
/* 현재 실행 중인 로컬 서버의 포트 번호를 반환 */
return "나는 잘 살아있어 내 포트 = " + environment.getProperty("local.server.port");
}
}

Status 항목에서 링크를 눌러서 controller에 저장해 놓은 엔드포인트 url 주소를 입력하게 되면 정상적으로 동작하는지 테스트할 수 있다.
Local 환경에서 Scale Out (확장 테스트)


IntelliJ 구성 편집 → 구성 복제 (Configuration 복사)




복제한 구성에서 포트 번호 변경, 그 뒤 실행을 눌러준다.

두 개의 인스턴스가 생긴 것을 확인할 수 있다.

Eureka 대시보드에서도 두 개의 인스턴스가 등록되 것을 확인

가상의 포트번호를 랜덤하게 받기
# 랜덤 포트 할당
server:
port: 0
spring:
application:
name: 1st-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka

port: 0 으로 포트 설정하면 Spring boot는 5만 ~ 6만번 대의 포트를 랜덤으로 할당한다.
server:
port: 0
spring:
application:
name: 1st-service
eureka:
instance:
# instance-id를 "서비스 이름:랜덤 값" 형태로 설정
instance-id: ${spring.application.name}:${spring.instance.instance-id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
${spring.instance.instance-id} 값이 존재하면 → 존재값을 사용
값이 없으면 → ${random.value}로 랜덤한 값 생성

복제한 인스턴스에 설정한 포트번호를 지우고 실행을 시킨다.

Second Service


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
server:
port: 0
spring:
application:
name: 2nd-service
eureka:
instance:
# instance-id를 "서비스 이름:랜덤 값" 형태로 설정
instance-id: ${spring.application.name}:${spring.instance.instance-id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
@SpringBootApplication
@EnableDiscoveryClient
public class [SecondServiceApplication] {
public static void main(String[] args) {
SpringApplication.run([SecondServiceApplication].class, args);
}
}
@RestController
public class SecondController {
private final Environment environment;
public SecondController(Environment environment){
this.environment = environment;
}
@GetMapping("/health")
public String health(){
return "나는 잘 살아있어 내 포트는 " + environment.getProperty("local.server.port");
}
}

API Gateway와 Load Balancer를 Client 형태로 구성하고, 단일 진입점을 설정하여 클라이언트 요청을 처리한다.


Gateway는 예전 기술로 동기처리에 특화, Servlet 대상으로 움직인다.
Reactive Gateway 비동기 관련 특화, webflux 기술 사용
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
server:
# 단일 진입점으로 만들 것이라 포트를 고정한다.
port: 8000
spring:
application:
name: gateway-server
# api gateway 서버는 client 들을 라우팅 핵심
# 핵심 기술인 라우팅 추가
cloud:
gateway:
routes:
- id: 1st-service
# lb: load balance의 약자
# gateway는 load balancer에게 인스턴스를 요청
# 인스턴스의 설정한 이름을 작성한다.
uri: lb://1ST-SERVICE
# 요청경로 설정, predicates : id로 찾아 갈 수 있게 설정한다.
predicates:
- Path=/first-service/**
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka
@SpringBootApplication
@EnableDiscoveryClient
public class [GatewayOrLoadBalancerApplication] {
public static void main(String[] args) {
SpringApplication.run([GatewayOrLoadBalancerApplication].class, args);
}
}


Gateway 서버가 8000 포트로 고정된 모습을 확인할 수 있다.
server:
# 단일 진입점으로 만들 것이라 포트를 고정한다.
port: 8000
spring:
application:
name: gateway-server
# api gateway 서버는 client 들을 라우팅 핵심
# 핵심 기술인 라우팅 추가
cloud:
gateway:
routes:
- id: 1st-service
# lb: load balance의 약자
# gateway는 load balancer에게 인스턴스를 요청
# 인스턴스의 설정한 이름을 작성한다.
uri: lb://1ST-SERVICE
# 요청경로 설정, predicates : id로 찾아 갈 수 있게 설정한다.
predicates:
- Path=/first-service/**
filters:
- RewritePath=/first-service/(?<segment>.*), /$\{segment}
- id: 2nd-service
uri: lb://2ND-SERVICE
predicates:
- Path=/second-service/**
filters:
- RewritePath=/second-service/(?<segment>.*), /$\{segment}
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka

진입점은 포트번호 하나로 설정되며 id 값으로 어떤 service로 이동할지 결정한다.
[서비스의 YML]
spring.application.name: 1st-service
│
▼
[Eureka 등록 이름]
1st-service (Eureka 서버에 등록)
│
▼
[Gateway YML - uri]
uri: lb://1ST-SERVICE ◀️ 대소문자를 구분하지 않는다.
- (Gateway YML - id는 단순히 Gateway 안에서 라우팅 규칙 구분용)
프로젝트 진행 시 모놀로식으로 진행
-> 모니터링 후 MSA로 변환
NETFLIX에는 Ribbon, Zuul 존재
API Gateway의 라우팅과 Load Balancer의 차이