이번에는 FiegnClient와 Ribbon (로드 밸런싱) 내용에 대해 정리하려고 한다.
📌 로드 밸런싱
`로드 밸런싱`이란 네트워크 트래픽을 여러 서버로 분산시켜 서버의 부하를 줄이고, 시스템의 성능과 가용성을 높이는 기술을 말한다.
즉, 서버 간 트래픽을 고르게 분배하여 한 서버에 부하가 집중되는 것을 방지한다.
종류에는 클라이언트 사이드 로드 밸런싱 / 서버 사이드 로드 밸런싱이 있다.
우리는 클라이언트가 직접 여러 서버 중 하나를 선택하여 요청을 보내는 방식인 `클라이언트 사이드 로드 밸런싱`을 사용해볼 것이다.
📌 FeignClient
`FeignClient`는 Spring Cloud에서 제공하는 HTTP 클라이언트로, Restful 웹 서비스를 호출할 수 있다.
`Eureka`와 같은 서비스 디스커버리와 연동하여 동적으로 서비스 인스턴스를 조회하고 `로드 밸런싱`을 수행한다.
`FeignClient`는 인터페이스와 어노테이션을 사용하여 REST API를 호출할 수 있다.
실습을 하면서 헷갈렸던 점이 이 부분이었다. 우리가 호출하는 것이 아니라 `FeignClient`가 대신 API를 호출하는 게 되게 생소했다!
또한 `Eureka`와 연동하여 서비스 인스턴스 목록을 동적으로 조회하고 로드 밸런싱을 수행한다.
📌 Ribbon
넷플릭스가 개발한 `클라이언트 사이드 로드 밸런서`로, 마이크로서비스 아키텍처에서 서비스 인스턴스 간의 부하를 분산하고 다양한 로드 밸런싱 알고리즘을 지원한다.
`Ribbon`은 `Eureka`로부터 서비스 인스턴스 리스트를 제공받아 로드 밸런싱에 사용하며 다양한 로드 밸런싱 알고리즘을 지원한다.
또한 `Failover`를 사용하여 요청 실패 시 다른 인스턴스로 자동 전환할 수 있다.
📌 Feign Client 예제
먼저 `FeignClient`와 `Ribbon` 사용을 위해 의존성을 추가한다.
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
@SpringBootApplication
@EnableFeignClients
public class MyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyServiceApplication.class, args);
}
}
어노테이션도 추가한다!
그리고 `FeignClient` 인터페이스를 작성해준다.
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "my-service")
public interface MyServiceClient {
@GetMapping("/endpoint")
String getResponse(@RequestParam(name = "param") String param);
}
`FeignClient` 라이브러리는 해당 인터페이스만 정의하면 Spring이 자동으로 구현체를 생성한다.
`@FeignClient` 어노테이션을 통해 MyServiceCleint가 Feign Client임을 나타내고 name 파라미터 안에 `호출하려는 서비스의 이름`을 지정한다. 지정된 이름을 가진 서비스의 인스턴스를 찾아 HTTP 요청을 보낼 수 있다.
`@GetMapping`은 요청할 API의 엔드포인트 경로를 나타낸다. 이 경우 `/endpoint` 경로로 Get 요청을 보낸다.
이 부분이 처음에 이해가 안 갔는데 우리는 보통 엔드포인트를 지정할 때 해당 경로로 요청 시 매핑 어노테이션이 적용된 메서드를 실행하는 로직으로 수행이 됐다.
근데 `Feign Client`의 경우 해당 메서드 즉, `getResponse()` 호출 시 해당 경로인 '/endpoint?param=...' 형식으로 Get 요청을 `my-service` 이름을 가진 서비스에 전송한다.. 원래 하던 거랑 반대다! 그래서 헷갈렸다!!!
이러한 방식은 MSA에서 서비스 간 통신을 단순화하는 데 유용하며,
개발자가 직접 HTTP 클라이언트를 구현하지 않고도 선언적으로 REST API를 호출할 수 있다.
📌 FeignClient, Eureka, Ribbon 실습
Eureka 서버 하나에 order 인스턴스 1개와 같은 기능의 포트만 다른 product 인스턴스 3개를 연결한다.
상품을 요청하면 응답하는 인스턴스의 포트를 받아 노출하여 라운드로빈 방식으로 로드밸런싱이 되는 것을 확인해본다.
먼저 Product 어플리케이션을 작성해보겠다.
spring:
application:
name: product-service
server:
port: 19092
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
`application.yml` 파일을 작성한다.
우리가 이전에 생성한 `Eureka` 서버와 연결해준다.
호출당하는 어플리케이션 `Product`를 작성한다.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
`EnableFeignClients` 어노테이션을 붙여 `Feign Client` 기능을 활성화한다.
Spring이 Feign 클라이언트 인터페이스들을 찾아 처리할 수 있게 만든다. 이를 통해 MSA 또는 외부 API와 통신할 수 있다.
아마 order에서 product를 선언적으로 호출할 수 있도록 어노테이션을 추가하는 듯 하다.
오잉!!!!!! 수정해서 실행해보니까 호출 당하는 어플리케이션은 해당 어노테이션이 없어도 된다!!!!!!!! 굿!!
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@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 ;
}
}
이제 호출하는 어플리케이션 `Order`를 작성한다.
spring:
application:
name: order-service
server:
port: 19091
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
설정 파일을 작성하고
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
`@EnableFeignClients` 어노테이션을 붙인다.
얘는 필요하다. 이를 통해 MSA 또는 외부 API와 통신할 수 있다. 고정된 URL을 사용해 직접 다른 서비스를 호출한다.
'
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/order/{orderId}")
public String getOrder(@PathVariable String orderId) {
return orderService.getOrder(orderId);
}
}
`/port 번호/order/{orderId}`로 요청이 들어오면 `orderService`의 `getOrder`를 호출한다.
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/product/{id}")
String getProduct(@PathVariable("id") String id);
}
위의 `FeignClient` 실습에서 작성하였던 코드와 같다. 인터페이스로 FeignClient를 설정한다.
`getProduct` 메서드 호출 시 `/product/{id}`로 Get 요청을 보낸다.
포트번호가 다른데 따로 포트번호를 써주지 않아도 되는 이유는 `product-service`가 `Eureka` 서버에 등록돼있고 그 서비스를 name으로 지정해주고 있기 때문이다.
또한 `product-service` 인스턴스가 여러개라면 인스턴스 목록 중 하나를 선택하여 요청을 보낸다. 이는 기본적으로 `Ribbon`을 사용하여 로드 밸런싱을 수행한다.
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@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...";
}
}
`OrderService`의 `getOrder`는 `orderId`가 1이라면 `productClient`의 `getProduct` 메서드를 호출하여 `product-service`로 Get 요청을 보낸다.
동일한 어플리케이션을 다른 포트로 실행하려면 이렇게 하면 된당!!
유레카 서버 → order → product(3개 모두) 순으로 실행한 뒤
http://localhost:19090/ 으로 접속하면 인스턴스를 확인할 수 있다.
http://localhost:19090/order/1 에 접속할때마다 텍스트의 포트가 변경되는 것을 보아 요청마다 라운드 로빈 방식으로 로드밸런싱이 이루어지는 것을 확인할 수 있다.
근데 우리는 `Ribbon` 설정을 따로 해주지 않았는데 어떻게 로드밸런싱이 수행됐을까?
바로 `Spring Cloud`는 `Ribbon`과 `Eureka`를 통합하여 사용자가 별도로 설정할 필요 없이 자동으로 로드 밸런싱을 처리하도록 설계되어 있기 때문이다.
즉, `Eureka`를 사용하는 경우, Ribbon은 자동으로 설정되어 추가적인 설정 없이 자동으로 로드 밸런싱 기능이 수행된다.
그냥 강의 보고 따라 칠 때는 이해가 거의 안 됐는데 이제는 대충 감이 잡힌 듯 하다.
역시 한 번 정리해보는 게 무척 도움된다. 굿굿!
'공부 > Spring' 카테고리의 다른 글
[Spring Cloud] API Gateway (Spring Cloud Gateway) (0) | 2024.08.05 |
---|---|
[Spring Cloud] 서킷 브레이커 (Resilience4j) (0) | 2024.08.05 |
[Spring Cloud] MSA Spring Cloud (Eureka) (0) | 2024.08.01 |
Spring Framework 각 계층의 역할 / 비즈니스 로직은 누구의 역할일까? (0) | 2024.07.08 |
Springboot 의존성 주입이란? @Autowired의 원리와 동작 과정 (0) | 2024.06.26 |