[Spring] Spring-Cloud를 활용한 MSA 설계(0) - Intro
Spring-Cloud를 통해 MSA 설계를 하기에 앞서,MSA의 개념과 Spring-Cloud의 주요 모듈 및 구성 요소를 정리한 서론입니다. ✅ MSA란?MSA(Micro Services Architecture)는 하나의 애플리케이션을 여러 개의 독립
zapzook.tistory.com
저번 포스팅에 이어서, 이번엔 Spring Cloud의 주요 모듈인 서비스 디스커버리, 로드 밸런싱에 대해
좀 더 자세히 알아보고 직접 실습을 해보겠다.
✅ 서비스 디스커버리(Eureka)
서비스 디스커버리는 MSA에서 각 서비스의 위치를 동적으로 관리하고 찾아주는 기능을 뜻한다.
각 서비스는 등록 서버에 자신의 위치를 등록하고 다른 서비스는 이를 조회하여 통신하는 방식이다.
주요 기능으로는 서비스 등록, 서비스 조회, 헬스 체크 등이 있다.
본 포스팅에선 서비스 디스커버리로 Spring Cloud의 Eureka를 사용할 예정이다.
📌 Eureka란?
넷플릭스가 개발한 서비스 디스커버리 서버로, MSA에서 각 서비스의 위치를 동적으로 관리한다.
모든 서비스 인스턴스의 위치를 저장하는 중앙 저장소 역할을 하며, 서비스 인스턴스의 상태를 주기적으로 확인하여
가용성을 보장한다. 이에 따라 Eureka는 크게 서버와 클라이언트로 나뉘게 된다.
📌 Eureka 서버
Eureka 서버는 서비스 레지스트리를 구성하는 중앙 서버이다.
서버 설정 파일(yml) 예시
server:
port: 8761
eureka:
client:
register-with-eureka: false # 다른 Eureka 서버에 이 서버를 등록하지 않음
fetch-registry: false # 다른 Eureka 서버의 레지스트리를 가져오지 않음
server:
enable-self-preservation: false # 자기 보호 모드 비활성화
해당 설정을 통해 Eureka 서버를 구성하고 클라이언트가 등록될 수 있도록 준비할 수 있다.
📌 Eureka 클라이언트
각 마이크로 서비스는 클라이언트로서 Eureka 서버에 자신을 등록해야 한다.
spring-cloud-starter-netflix-eureka-client
위 의존성을 추가하고, 애플리케이션 이름만 설정파일에 있으면 Eureka에 등록된다.
클라이언트 설정 파일(yml) 예시
spring:
application:
name: my-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ # Eureka 서버 URL
register-with-eureka: true # Eureka 서버에 등록
fetch-registry: true # Eureka 서버로부터 레지스트리 정보 가져오기
instance:
hostname: localhost # 클라이언트 호스트 이름
prefer-ip-address: true # IP 주소 사용 선호
lease-renewal-interval-in-seconds: 30 # 리스 갱신 간격
lease-expiration-duration-in-seconds: 90 # 리스 만료 기간
📌 헬스 체크 및 장애 처리
Eureka 서버와 클라이언트가 연결되고 나면 서버는 주기적으로 서비스 인스턴스의 상태를 확인하여 가용성을 유지한다.
기본 헬스 체크 엔드포인트로 /actuator/health를 사용한다.
또, 서비스 장애 시 Eureka 서버는 해당 인스턴스를 레지스트리에서 제거하여 다른 서비스의 접근을 차단하는
장애 처리 기능을 제공한다.
📌 클러스터를 통한 고가용성 구성
Eureka 서버의 고가용성을 위해 여러 인스턴스를 구성할 수도 있다.
다중 인스턴스로 구성하여 고가용성을 유지하며, 각 인스턴스는 서로를 피어로 등록해 상호 백업이 가능해진다.
설정 파일(yml) 예시
eureka:
client:
service-url:
defaultZone: http://eureka-peer1:8761/eureka/,http://eureka-peer2:8761/eureka/
✅ 서비스 디스커버리(Eureka) 실습
위 그림과 같이 Eureka를 통해 하나의 서버에 인스턴스 2개를 연결해보겠다.
📌 Eureka server
Eureka Server 디펜던시를 추가하여 Spring 프로젝트를 생성한다.
ServerApplication.java
@EnableEurekaServer
@SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
메인 애플리케이션에 @EnableEurekaServer 애너테이션을 달아 해당 애플리케이션이
Eureka Server로 동작할 수 있게 해준다.
application.properties
spring.application.name=server
server.port=19090
# 유레카 서버에 자신을 등록할지 여부를 설정합니다.
# true로 설정하면 유레카 클라이언트가 유레카 서버에 자신을 등록합니다.
# 유레카 서버에서는 일반적으로 false로 설정하여, 서버가 자기 자신을 등록하지 않도록 합니다.
eureka.client.register-with-eureka=false
# 유레카 서버로부터 레지스트리를 가져올지 여부를 설정합니다.
# true로 설정하면 유레카 클라이언트가 유레카 서버로부터 다른 서비스 인스턴스 목록을 가져옵니다.
# 유레카 서버에서는 일반적으로 false로 설정하여, 레지스트리를 가져오지 않도록 합니다.
eureka.client.fetch-registry=false
# 유레카 서버 인스턴스의 호스트 이름을 설정합니다.
# 유레카 서버가 자신의 호스트 이름을 다른 서비스에 알릴 때 사용합니다.
eureka.instance.hostname=localhost
# 유레카 클라이언트가 유레카 서버와 통신하기 위해 사용할 기본 서비스 URL을 설정합니다.
# 클라이언트 애플리케이션이 유레카 서버에 연결하고 등록하거나 레지스트리를 가져올 때 사용할 URL을 지정합니다.
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
설정 파일(properties, yml)에 필요한 설정값들을 넣어준다.
📌 Eureka Client(service instance)
마찬가지로 서비스 인스턴스가 될 프로젝트를 2개 생성해준다.
단, 이번엔 Eureka Server가 아닌 Eureka Discovery Client 디펜던시를 추가한다.
application.properties
spring.application.name=first
server.port=19091
# 유레카 클라이언트가 유레카 서버와 통신하기 위해 사용할 기본 서비스 URL을 설정합니다.
# 유레카 서버의 포트와 호스트 이름을 정확히 지정해야 합니다.
eureka.client.service-url.defaultZone=http://localhost:19090/eureka/
설정 파일(properties, yml)에 필요한 설정값들을 넣어준다.
두 번째 서비스 인스턴스도 같은 형식으로 설정값을 넣되, name과 port를 다르게 설정한다.
ex) name=second, port=19092
📌 Run
Eureka 서버와 클라이언트의 설정이 끝났으니 이제 각 서버를 실행시켜 연결을 확인한다.
Eureka Server -> service instance 1(first) -> service instance 2(second) 순으로 실행시킨다.
localhost:19090/ 으로 접속하면 두 개의 인스턴스가 있는 것을 확인할 수 있다.
Eureka 서버와 각 마이크로 서비스가 성공적으로 연결된 모습이다.
✅ 클라이언트 사이드 로드 밸런싱(FeignClient)
📌 로드 밸런싱
로드 밸런싱은 네트워크 트래픽을 여러 서버로 분산시켜 서버의 부하를 줄이고, 시스템의 성능과 가용성을
높이는 기술이다.
이러한 로드 밸런싱은 클라이언트 사이드 로드 밸런싱과 서버 사이드 로드 밸런싱으로 나뉜다.
본 포스팅에선 클라이언트 사이드 로드 밸런싱을 다룰 것이다.
📌 클라이언트 사이드 로드 밸런싱
클라이언트 사이드 로드 밸런싱은 클라이언트가 직접 여러 서버 중 하나를 선택하여 요청을 보내는 방식이다.
클라이언트는 서버의 목록을 가지고 있으며, 이를 바탕으로 로드 밸런싱을 수행한다.
📌 FeignClient
FeignClient는 Spring Cloud에서 제공하는 HTTP 클라이언트로, 선언적으로 RESTful 웹 서비스를 호출할 수 있다.
Eureka와 같은 서비스 디스커버리와 연동하여 동적으로 서비스 인스턴스를 조회하고 로드 밸런싱을 수행한다.
주요 특징
- 선언적 HTTP 클라이언트 : 인터페이스와 어노테이션을 사용해 REST API를 호출할 수 있다
- Eureka 연동 : Eureka와 통합하여 서비스 인스턴스 목록을 동적으로 조회하고 로드 밸런싱을 수행한다.
- 자동 로드 밸런싱 : Ribbon이 통합되어 있어 자동으로 로드 밸런싱을 수행한다.
📌 로드 밸런싱 알고리즘
라운드 로빈
- 각 서버에 순차적으로 요청을 분배하는 방식이다
- 간단하고 공평하게 트래픽을 분산할 수 있다
가중치 기반 로드 밸런싱
- 각 서버에 가중치를 부여하고, 가중치에 비례하여 요청을 분배하는 방식이다
- 서버의 성능이나 네트워크 상태에 따라 가중치를 조절한다
기타 알고리즘
- 최소 연결 : 현재 연결된 클라이언트 수가 가장 적은 서버로 요청을 보내는 방식이다
- 응답 시간 기반 : 서버의 응답 시간을 기준으로 가장 빠른 서버로 요청을 보내는 방식이다
✅ 클라이언트 사이드 로드 밸런싱(FeignClient) 실습
위 그림과 같이 유레카 서버 하나에 주문 인스턴스 1개를 연결하고,
같은 기능을 하는(포트만 다른) 상품 인스턴스 3개를 연결한다.
이후 상품을 주문하면 응답하는 인스턴스의 포트를 받아서 노출 시킬 것이다.
이를 통해 라운드 로빈으로 요청이 로드밸런싱 되는 것을 확인할 것이다.
Eureka 서버는 이전 실습에서 만들어둔 유레카 서버를 그대로 사용할 예정이다.
📌 Product
이전 실습에서 진행했듯이 Eureka Client가 될 Product 프로젝트를 생성한다.
단, 이번에는 로드밸런싱을 실습하기 위해 OpenFeign 디펜던시를 추가로 넣어준다.
ProductApplication.java
@SpringBootApplication
@EnableFeignClients
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
FeignClient의 활성화를 위해 @EnableFeignClients 애너테이션을 추가해준다.
설정 파일의 설정값은 이전 실습에서 다룬 그대로이니 생략한다.
ProductController.java
@RestController
public class ProductController {
@Value("${server.port}") // 애플리케이션이 실행 중인 포트를 주입받습니다.
private String serverPort;
@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
return "Product " + id + " info!!!!! From port : " + serverPort ;
}
}
요청이 들어오면 서버의 포트를 반환하도록 컨트롤러 Api를 설정한다.
이를 통해 로드 밸런싱의 성공 유무를 체크할 수 있다.
19092, 19093, 19094 포트에 같은 애플리케이션 실행하기
인텔리제이의 옵션을 통해 포트만 다른 세 개의 애플리케이션을 실행시킬 것이다.
인텔리제이 상단 메뉴에서 실행 > 구성 편집으로 들어간다.
ProductApplication의 이름을 ProductApplication:19092로 변경한다.
이후 우측 상단의 복사버튼을 클릭해 ProductApplication을 두 개 더 생성하고 이미지와 같이
**19093, 19094로 이름을 변경한다.**
이미지와 같이 -Dserver.port=19093을 입력한다. 이를 통해 포트번호를 설정하여 애플리케이션을 실행시킬 수 있다.
19094에도 같은 작업을 해준다.
📌 Order
Order는 요청이 오면 product를 호출하여 상품의 정보를 가져온다.
Product와 마찬가지로 프로젝트를 생성해준다(디펜던시는 똑같다)
OrderApplication.java
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
Product와 마찬가지로 @EnableFeignClients 애너테이션을 추가해준다.
OrderController.java
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/order/{orderId}")
public String getOrder(@PathVariable String orderId) {
return orderService.getOrder(orderId);
}
}
컨트롤러 API를 구성해준다.
ProductClient.java
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/product/{id}")
String getProduct(@PathVariable("id") String id);
}
product 서비스와 통신할 ProductClient를 생성해준다.
이때, 클래스가 아닌 인터페이스로 생성해야 한다.
OrderService.java
@Service
@RequiredArgsConstructor
public class OrderService {
private final ProductClient productClient;
public String getProductInfo(String productId) {
return productClient.getProduct(productId);
}
public String getOrder(String orderId) {
if(orderId.equals("1") ){
String productId = "2";
String productInfo = getProductInfo(productId);
return "Your order is " + orderId + " and " + productInfo;
}
return "Not exist order...";
}
}
ProductClient를 통해 product 서비스에 접근하여 product 정보(포트 번호)를 가져온다.
📌 Run
유레카 서버 → order → product(3개 모두) 순으로 서버를 모두 실행한다.
localhost:19090/ 으로 접속하면 Order와 Product 인스턴스가 모두 잘 연결된 모습을 확인할 수 있다.
Product-Service는 설정한대로 19092, 19093, 19094 3개의 포트로 떠있는 모습이다.
이후 localhost:19091/order/1 에 접속하게 되면 접속할 때마다 포트 번호가 변경되는 것을 볼 수 있다.
이를 통해 매 요청마다 라운드 로빈으로 로드밸런싱이 이루어짐을 확인할 수 있다.
'Study > Spring' 카테고리의 다른 글
Intellij에서 공공 데이터셋(csv) 동기화 및 활용하기 (0) | 2025.02.17 |
---|---|
[Spring] Spring-Cloud를 활용한 MSA 설계(2) - 서킷 브레이커 (1) | 2025.02.13 |
[Spring] Spring-Cloud를 활용한 MSA 설계(0) - Intro (0) | 2025.02.11 |
[Spring][Redis] 동시성 이슈와 분산락을 이용한 동시성 제어 (0) | 2024.03.27 |
[Spring] AOP를 활용한 기능 모듈화 (0) | 2024.03.25 |