인프라 기술 및 아키텍처

04. MSA - 클라이언트 사이드 로드 밸런싱 (FeignClient와 Ribbon)

Dev.99_tale 2025. 3. 13. 21:32

클라이언트 사이드 로드 밸런싱 개요

4. 로드 밸런싱이란

  • 로드 밸런싱은 네트워크 트래픽을 여러 서버로 분산시켜 서버의 부하를 줄이고, 시스템의 성능과 가용성을 높이는 기술
  • 서버 간 트래픽을 고르게 분배하여 특정 서버에 부하가 집중되는 것을 방지
  • 종류
    • 클라이언트 사이드 로드 밸런싱, 서버 사이드 로드 밸런싱

4.1 클라이언트 사이드 로드 밸런싱이란

  • 클라이언트가 직접 여러 서버 중 하나를 선택하여 요청을 보내는 방식
  • 클라이언트는 서버의 목록을 가지고 있으며, 이를 바탕으로 로드 밸런싱을 수행

4.2 FeignClient란

  • Spring Cloud에서 제공하는 HTTP 클라이언트로, 선언적으로 RESTful 웹 서비스를 호출할 수 있음
  • Eureka와 같은 서비스 디스커버리와 연동하여 동적으로 서비스 인스턴스를 조회하고 로드 밸런싱을 수행

FeignClient의 주요 특징

선언적 HTTP 클라이언트

  • 인터페이스와 어노테이션을 사용하여 REST API를 호출할 수 있음

Eureka 연동

  • Eureka와 통합하여 서비스 인스턴스 목록을 동적으로 조회하고 로드 밸런싱을 수행

자동 로드 밸런싱

  • Ribbon이 통합되어 있어 자동으로 로드 밸런싱을 수행

Ribbon 개요

Ribbon이란

  • 넷플릭스가 개발한 클라이언트 사이드 로드 밸런서로, 마이크로서비스 아키텍처에서 서비스 인스턴스 간의 부하를 분산
  • 다양한 로드 밸런싱 알고리즘을 지원하며, Eureka와 같은 서비스 디스커버리와 연동하여 사용

Ribbon의 주요 특징

서버 리스트 제공자

  • Eureka 등으로부터 서비스 인스턴스 리스트를 제공받아 로드 밸런싱에 사용

로드 밸런싱 알고리즘

  • 라운드 로빈, 가중치 기반 등 다양한 로드 밸런싱 알고리즘 지원

Failover

  • 요청 실패 시 다른 인스턴스로 자동 전환

 FeignClient와 Ribbon 설정

기본 설정

  • FeignClient와 Ribbon을 사용하려면 Spring Boot 애플리케이션에 의존성을 추가해야 함
  • build.gradle 파일 예시
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
  • Spring Boot 애플리케이션 설정:
@SpringBootApplication
@EnableFeignClients
public class MyServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyServiceApplication.class, args);
    }
}

FeignClient 인터페이스 작성

  • 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와 Eureka 연동

Eureka 설정

  • Eureka와 FeignClient를 함께 사용하면 동적으로 서비스 인스턴스를 조회하여 로드 밸런싱을 수행
  • application.yml 파일 설정 예시
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/
eureka

FeignClient와 Ribbon 설정

  • FeignClient에서 제공하는 서비스 인스턴스를 사용하여 로드 밸런싱을 수행
  • 예시 설정 파일
my-service:
  ribbon:
    eureka:
      enabled: true

FeignClient와 Ribbon 동작 원리

  1. 서비스 이름: @FeignClient(name = "my-service") 어노테이션은 Eureka에 등록된 서비스 이름을 참조
  2. 서비스 인스턴스 조회: Eureka 서버에서 my-service라는 이름으로 등록된 서비스 인스턴스 목록을 조회
  3. 로드 밸런싱: 조회된 서비스 인스턴스 목록 중 하나를 선택하여 요청을 보냅니다. 이는 기본적으로 Ribbon을 사용하여 로드 밸런싱을 수행
  4. 요청 분배: 여러 서비스 인스턴스가 있을 경우, Round Robin 또는 다른 설정된 로드 밸런싱 알고리즘을 사용하여 요청을 분배
  • 시나리오로 생각해보기
💡 Order 서비스는 Product 서비스를 호출하여 상품 정보를 가져옵니다. 이 과정에서 어떻게 동작하는지 설명하겠습니다.

- Order 서비스 인스턴스: 1개
- Product 서비스 인스턴스: 3개

 

  1. Order 서비스 실행: Order 서비스가 실행되면 Eureka 서버에서 Product 서비스 인스턴스 목록을 가져옵니다.
  2. Product 서비스 호출: Order 서비스에서 Product 서비스의 정보를 가져오기 위해 FeignClient를 사용하여 호출합니다.
  3. Ribbon을 통한 로드 밸런싱: FeignClient는 Ribbon을 통해 3개의 Product 인스턴스 중 하나를 선택하여 호출합니다. 이 과정에서 Round Robin 알고리즘을 사용하여 요청을 순차적으로 분배합니다.
  4. 응답 처리: 선택된 Product 인스턴스에서 응답을 받아 Order 서비스에 반환하고, 최종적으로 클라이언트에 응답을 전달합니다.

FeignClient 인터페이스 작성 예시

  • 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를 사용하여 다른 서비스 호출을 수행
  • 예시 코드
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @Autowired
    private MyServiceClient myServiceClient;

    @GetMapping("/call-service")
    public String callService(@RequestParam String param) {
        return myServiceClient.getResponse(param);
    }
}

4.7 실습

💡 유레카 서버 하나에 주문 인스턴스 1개와 같은 기능의 포트만 다른 상품 인스턴스 3개를 연결합니다.
상품을 요청(http://localhost:19091/order/1) 하면 응답하는 인스턴스의 포트를 받아서 노출합니다.
이를 통해 라운드로빈으로 로드밸런싱이 되는것을 확인합니다.

 

 

Eureka Server

서비스 디스커버리 (Eureka) 강의에서 진행했던 유레카 서버를 그대로 복사해서 실행합니다.

Product instance

// Product는 요청이 오면 아래의 문자열을 반환합니다.
”Product {productId} info!!!! From port : ${serverPort}”

start.spring.io 에 접속하여 프로젝트를 생성합니다 (디펜던시는 이미지 참고)

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);
	}
}

MSA 32ProductController.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 ;
	}
}

resources/application.yml (application.properties 파일은 삭제)

spring:
  application:
    name: product-service
server:
  port: 19092
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

19092,19093,19094 포트에 같은 애플리케이션 실행하기

인텔리제이의 상단 메뉴에서 실행 > 구성 편집으로 들어갑니다.

 

ProductApplication 의 이름을 ProdcutApplication:19092로 변경합니다.

 

  • 우측 상단의 복사버튼을 클릭하여 ProductApplication을 두개 더 생성하고 이미지와 같이 19093,19094로 이름을 변경합니다.
  • 옵션수정을 클릭하여 VM옵션 추가를 클릭합니다.
  • 이미지와 같이 -Dserver.port=19093 을 입력합니다. 19094에도 같은 작업을 해줍니다.

 

Order instance

Order는 요청이 오면 product를 호출하여 상품의 정보를 가져옵니다. 그리고 아래의  문자열을 반환합니다.
”Your order {orderId} product info => ${프로덕트에서 받은 문자열}”

주문은 주문아이디가 1인 주문만 있다고 가정하겠습니다.
1 주문은 112번 상품을 호출한다고 가정합니다.

start.spring.io 에 접속하여 프로젝트를 생성합니다 (디펜던시는 이미지 참고)

 

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);
	}

}

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);
    }
    
}

ProdcutClient.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);
}

 

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...";
        }
    }

application.yml

application.yml
spring:
  application:
    name: order-service
server:
  port: 19091
eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/

 

Run

  • 유레카 서버 → order → product(3개 모두) 순으로 실행
  • http://localhost:19090/ 으로 접속하면 인스턴스를 확인할 수 있음
  • PRODUCT-SERVICE에 포트가 19092,19093,19094 3개가 떠있는것을 확인할 수 있음

 

확인

  • http://localhost:19091/order/1 에 접속할때마다 텍스트의 포트가 변경되는것을 볼 수 있음 이를통해 요청마다 라운드로빈으로 동작함을 확인할 수 있음

 

'인프라 기술 및 아키텍처' 카테고리의 다른 글

03. MSA - 서비스 디스커버리(Eureka)  (0) 2025.02.12
SQL Query 문법  (0) 2025.02.11
02. MSA - Spring Cloud  (0) 2025.02.08
01. MSA - 오리엔테이션 + MSA란 (TIL)  (4) 2025.02.05