
Eureka 서버 하나에 order 인스턴스 1개와 같은 기능의 포트만 다른 product 인스턴스 3개를 연결합니다.
상품을 요청(http://localhost:19091/order/1) 하면 응답하는 인스턴스의 포트를 받아서 노출합니다.
이를 통해 라운드로빈으로 로드밸런싱이 되는것을 확인합니다.
먼저 Eureka 서버를 준비해둔다.
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/
ProductApplication.java
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 에너테이션을 추가하여 @FeignClient가 선언된 인터페이스가 자동으로 Spring Bean으로 등록되도록 한다.ProductController.java
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 ;
}
}
/product/{id} 경로로 GET메서드 요청이 들어오면 아래의 문자열을 반환한다."Product {id} info!!!! From port: {serverPort}"application.yml
spring:
application:
name: product-service
server:
port: 19092
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
IntelliJ에서 실행 > 구성 편집에서 ProductApplication을 복사하여 19093, 19094 포트로 두개 더 생성한다.
OrderApplication.java
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 애너테이션을 추가한다.OrderController.java
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);
}
}
/order/{orderId} 경로로 GET 메서드 요청이 들어오면 orderService의 getOrder() 메서드를 호출하여 문자열을 반환한다.ProductClient.java
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);
}
/product/{id} 엔드포인트로 GET 요청을 보낸다.OrderService.java
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...";
}
}
OrderController에서 /order/{orderId}로 GET 메서드 요청이 들어오면 orderService의 getOrder() 메서드를 호출한다.
getOrder() 메서드에서는 orderId가 1이 아니라면 "Not exist order..." 문자열을 반환한다.
orderId가 1이면 productId를 2로 설정하고 getProductInfo(productId)를 호출한다. 반환받은 문자열을 productInfo에 담고 "Your order is {orderId} and {productInfo}"를 반환한다.
getProductInfo() 메서드는 주입받은 productClient를 사용하여 getProduct(productId)를 호출한다. 그럼 서비스 디스커버리에서 product-service를 찾아 /product/{productId}로 GET 요청을 보낸다.
application.yml
spring:
application:
name: order-service
server:
port: 19091
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
Eureka 서버 -> order -> product(3개) 순서로 모두 실행한다.
http://localhost:19090으로 접속하면 order-service와 product-service 3개가 떠있는 것을 확인할 수 있다.
이제 http://localhost:19091/order/1로 접속하면 아래의 문자열이 나온다.
Your order is 1 and Product 2 info!!!!! From port : 19092
다시 http://localhost:19091/order/1로 접속하면 Your order is 1 and Product 2 info!!!!! From port : 19093으로 나온다. 접속할 때마다 port가 19092 -> 19093 -> 19094 번 순서로 돌아가면서 나오는 것을 확인할 수 있다.
이렇게 라운드 로빈 방식으로 로드 밸런싱이 되는 것을 볼 수 있다.
위의 실습에서 우리는 별도의 로드 밸런싱 설정을 하지 않았음에도 ProductClient가 여러 인스턴스에 요청을 보낼 수 있었던 이유는 Spring Cloud의 자동 설정 덕분이다.
자동 설정 메카니즘
spring-cloud-starter-netflix-eureka-client가 클래스패스에 존재하는지 체크한다.LoadBalancerClient를 자동으로 빈으로 등록한다.다른 로드 밸런싱 방식을 사용하려면 어떻게 해야 하는지 찾아보다가 Spring Cloud 2020.0 이후 버전부터는 Ribbon 대신 Spring Cloud LoadBalancer를 표준으로 사용한다고 한다.
그래서 Spring Cloud LoadBalancer에서 다른 방식을 사용하려면 로드 밸런싱 알고리즘 설정 클래스를 만들어서 FeignClient에 설정을 연결해주어야 한다고 한다.