🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Spring Cloud LoadBalancer 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 21. · 4 Views

Spring Cloud LoadBalancer 완벽 가이드

클라이언트 사이드 로드밸런싱의 개념부터 Spring Cloud LoadBalancer의 실전 활용까지, 마이크로서비스 아키텍처에서 필수적인 부하 분산 기술을 쉽고 깊이 있게 배워봅니다.


목차

  1. 로드밸런싱이란?
  2. Spring Cloud LoadBalancer
  3. Ribbon vs LoadBalancer
  4. 로드밸런싱 전략
  5. WebClient와 연동
  6. 캐시와 성능

1. 로드밸런싱이란?

어느 날 김개발 씨가 회사의 마이크로서비스 프로젝트에 투입되었습니다. 선배 개발자 박시니어 씨가 코드 리뷰를 하다가 물었습니다.

"여기서 서비스를 호출할 때, 어떻게 부하를 분산시킬 건가요?"

로드밸런싱은 여러 서버에 요청을 골고루 분배하는 기술입니다. 마치 은행 창구에서 고객을 여러 창구로 안내하는 것처럼, 트래픽을 여러 서버 인스턴스에 분산시킵니다.

이를 통해 특정 서버에 부하가 집중되는 것을 방지하고, 시스템의 안정성과 성능을 향상시킬 수 있습니다.

다음 코드를 살펴봅시다.

// 클라이언트 사이드 로드밸런싱의 기본 개념
@RestController
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    // 상품 서비스 호출 - 로드밸런서가 자동으로 인스턴스 선택
    @GetMapping("/order/{productId}")
    public String getProduct(@PathVariable String productId) {
        // product-service는 논리적 서비스 이름
        // 실제 IP:Port는 로드밸런서가 선택
        String url = "http://product-service/api/products/" + productId;
        return restTemplate.getForObject(url, String.class);
    }
}

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 오늘도 열심히 코드를 작성하던 중, 마이크로서비스 환경에서 다른 서비스를 호출하는 코드를 작성해야 했습니다.

분명히 IP 주소를 직접 입력해야 할 것 같은데, 선배들의 코드를 보니 서비스 이름만 적혀있었습니다. 선배 개발자 박시니어 씨가 다가와 코드를 살펴봅니다.

"아, 이건 클라이언트 사이드 로드밸런싱을 사용하는 거예요. 직접 IP를 관리할 필요가 없답니다." 그렇다면 로드밸런싱이란 정확히 무엇일까요?

쉽게 비유하자면, 로드밸런싱은 마치 대형 마트의 계산대 안내 시스템과 같습니다. 손님이 많아지면 여러 계산대를 운영하고, 안내 직원이 손님을 적절히 분배합니다.

어떤 계산대는 한가하고 어떤 계산대는 붐비면 효율이 떨어지겠죠? 이처럼 로드밸런싱도 서버에 들어오는 요청을 여러 인스턴스에 골고루 분배하는 역할을 담당합니다.

로드밸런싱이 없던 시절에는 어땠을까요? 개발자들은 서버의 IP 주소를 직접 코드에 하드코딩해야 했습니다.

서버가 추가되거나 변경되면 코드를 수정하고 다시 배포해야 했습니다. 더 큰 문제는 특정 서버에만 요청이 몰리는 상황이었습니다.

하나의 서버는 과부하로 죽어가는데 다른 서버는 놀고 있는 비효율적인 상황이 발생했습니다. 바로 이런 문제를 해결하기 위해 로드밸런싱이 등장했습니다.

로드밸런싱을 사용하면 자동으로 여러 서버에 요청을 분산시킬 수 있습니다. 또한 서버의 상태를 체크해서 문제가 있는 서버는 자동으로 제외시킵니다.

무엇보다 IP 주소를 직접 관리하지 않아도 된다는 큰 이점이 있습니다. 로드밸런싱에는 크게 두 가지 방식이 있습니다.

첫 번째는 서버 사이드 로드밸런싱입니다. nginx나 HAProxy 같은 별도의 로드밸런서 서버를 두고, 모든 요청이 이 서버를 거쳐가도록 하는 방식입니다.

클라이언트는 로드밸런서의 주소만 알면 되고, 로드밸런서가 적절한 서버로 요청을 전달합니다. 두 번째는 클라이언트 사이드 로드밸런싱입니다.

클라이언트가 직접 어떤 서버로 요청을 보낼지 결정하는 방식입니다. Spring Cloud에서는 이 방식을 주로 사용합니다.

클라이언트는 서비스 레지스트리(Eureka, Consul 등)에서 사용 가능한 서버 목록을 가져와서, 자체적으로 로드밸런싱 알고리즘을 적용해 서버를 선택합니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 RestTemplate을 주입받는 부분을 보면, 이것이 로드밸런싱을 지원하도록 설정되어 있어야 합니다. URL에는 실제 IP 주소 대신 "product-service"라는 논리적인 서비스 이름을 사용합니다.

이 부분이 핵심입니다. 로드밸런서는 이 서비스 이름을 보고 실제로 요청을 보낼 서버 인스턴스를 선택합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 이커머스 서비스를 개발한다고 가정해봅시다.

주문 서비스에서 상품 서비스를 호출해야 하는데, 상품 서비스는 트래픽이 많아서 10개의 인스턴스가 실행 중입니다. 클라이언트 사이드 로드밸런싱을 사용하면 주문 서비스가 자동으로 10개 인스턴스 중 하나를 선택해서 요청을 보냅니다.

네이버, 카카오 같은 대형 서비스에서 이런 패턴을 적극적으로 사용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 일반 RestTemplate을 그냥 사용하는 것입니다. 로드밸런싱이 동작하려면 RestTemplate에 특별한 설정이 필요합니다.

또한 서비스 레지스트리와의 연동도 필수적입니다. 따라서 Spring Cloud의 설정을 올바르게 구성해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 IP 주소 대신 서비스 이름을 쓰는 거였군요!" 로드밸런싱의 개념을 제대로 이해하면 마이크로서비스 아키텍처에서 더 안정적이고 확장 가능한 시스템을 구축할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 클라이언트 사이드 로드밸런싱은 별도의 로드밸런서 서버가 필요 없어 인프라 비용을 절감할 수 있습니다

  • 서비스 레지스트리(Eureka)와 함께 사용하면 동적으로 서버 추가/제거가 가능합니다
  • 개발 환경에서는 단일 인스턴스로 시작해도 되지만, 운영 환경에서는 최소 2개 이상의 인스턴스를 권장합니다

2. Spring Cloud LoadBalancer

김개발 씨가 pom.xml 파일을 열어보니 spring-cloud-starter-loadbalancer 의존성이 추가되어 있었습니다. "이게 뭔가요?" 박시니어 씨가 미소를 지으며 답했습니다.

"Spring Cloud에서 클라이언트 사이드 로드밸런싱을 담당하는 핵심 라이브러리예요."

Spring Cloud LoadBalancer는 Spring Cloud에서 제공하는 클라이언트 사이드 로드밸런싱 라이브러리입니다. RestTemplate이나 WebClient와 통합되어 서비스 이름만으로 여러 인스턴스 중 하나를 자동으로 선택합니다.

설정이 간단하고 Spring 생태계와 완벽하게 통합되어 있습니다.

다음 코드를 살펴봅시다.

// LoadBalancer 설정 - Configuration 클래스
@Configuration
public class LoadBalancerConfig {

    // RestTemplate에 LoadBalancer 기능 추가
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    // WebClient에 LoadBalancer 기능 추가
    @Bean
    @LoadBalanced
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

김개발 씨는 이제 실제로 Spring Cloud LoadBalancer를 설정해야 했습니다. 문서를 찾아보니 생각보다 간단해 보였습니다.

하지만 어떤 원리로 동작하는지 궁금했습니다. 박시니어 씨가 화면을 가리키며 설명을 시작했습니다.

"Spring Cloud LoadBalancer는 정말 똑똑한 녀석이에요. @LoadBalanced 어노테이션 하나면 모든 게 자동으로 처리됩니다." Spring Cloud LoadBalancer란 정확히 무엇일까요?

쉽게 비유하자면, Spring Cloud LoadBalancer는 마치 스마트한 택배 분배 시스템과 같습니다. 여러분이 "강남구"라고만 적으면, 시스템이 알아서 강남구에 있는 여러 배송 센터 중 가장 적절한 곳을 선택해서 택배를 보냅니다.

구체적인 주소를 몰라도 되고, 배송 센터가 추가되거나 변경되어도 신경 쓸 필요가 없습니다. 이처럼 Spring Cloud LoadBalancer도 서비스 이름만 알면 나머지는 알아서 처리해줍니다.

예전에는 Netflix Ribbon이라는 라이브러리를 사용했습니다. 하지만 Netflix가 Ribbon의 유지보수를 중단하면서 문제가 생겼습니다.

새로운 Spring 버전과의 호환성 문제도 발생했고, 버그가 발견되어도 수정되지 않았습니다. 개발 커뮤니티는 더 현대적이고 유지보수가 잘 되는 대안이 필요했습니다.

바로 이런 배경에서 Spring Cloud LoadBalancer가 등장했습니다. Spring 팀이 직접 개발하고 유지보수하기 때문에 안정성이 높습니다.

또한 반응형 프로그래밍을 지원하여 WebClient와 완벽하게 통합됩니다. 무엇보다 설정이 매우 간단하다는 큰 장점이 있습니다.

LoadBalancer를 사용하려면 먼저 의존성을 추가해야 합니다. Maven을 사용한다면 pom.xml에 spring-cloud-starter-loadbalancer를 추가합니다.

Gradle을 사용한다면 build.gradle에 같은 의존성을 추가하면 됩니다. 그리고 나서 RestTemplate이나 WebClient를 빈으로 등록할 때 @LoadBalanced 어노테이션을 붙여주기만 하면 됩니다.

위의 코드를 자세히 살펴보겠습니다. @Configuration 어노테이션이 붙은 설정 클래스에서 RestTemplate과 WebClient.Builder를 빈으로 등록합니다.

여기서 핵심은 @LoadBalanced 어노테이션입니다. 이 어노테이션이 붙으면 Spring이 해당 빈에 로드밸런싱 기능을 자동으로 추가해줍니다.

실제로는 인터셉터를 통해 서비스 이름을 실제 인스턴스 주소로 변환하는 작업이 일어납니다. 실제 프로젝트에서는 어떻게 사용할까요?

쇼핑몰 프로젝트를 예로 들어봅시다. 주문 서비스(order-service)에서 결제 서비스(payment-service)를 호출해야 합니다.

결제 서비스는 중요하기 때문에 3개의 인스턴스가 실행 중입니다. 위처럼 설정한 RestTemplate을 사용하면, "http://payment-service/api/pay"라는 URL만으로 3개 인스턴스 중 하나에 자동으로 요청이 전달됩니다.

LoadBalancer는 내부적으로 ServiceInstanceListSupplier를 사용합니다. 이 컴포넌트가 서비스 레지스트리(Eureka, Consul 등)에서 사용 가능한 인스턴스 목록을 가져옵니다.

그리고 ReactorLoadBalancer가 설정된 알고리즘(기본은 라운드 로빈)에 따라 하나의 인스턴스를 선택합니다. 이 모든 과정이 자동으로 일어나기 때문에 개발자는 비즈니스 로직에만 집중할 수 있습니다.

하지만 주의할 점이 있습니다. @LoadBalanced가 붙은 RestTemplate과 일반 RestTemplate은 별개입니다.

만약 외부 API를 호출해야 한다면 @LoadBalanced가 없는 별도의 RestTemplate을 만들어야 합니다. 두 가지를 혼용하면 에러가 발생할 수 있습니다.

따라서 용도에 따라 구분해서 사용해야 합니다. 또 하나 중요한 점은 캐싱입니다.

LoadBalancer는 성능 향상을 위해 인스턴스 목록을 캐싱합니다. 기본 캐시 시간은 35초입니다.

즉, 새로운 인스턴스가 등록되어도 최대 35초까지는 감지되지 않을 수 있습니다. 실시간성이 중요한 경우 이 설정을 조정할 수 있습니다.

다시 김개발 씨의 이야기로 돌아가봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 직접 코드를 작성해보았습니다.

"와, 정말 간단하네요! @LoadBalanced 하나면 끝이잖아요?" Spring Cloud LoadBalancer를 제대로 이해하면 마이크로서비스 환경에서 서비스 간 통신을 안정적으로 구현할 수 있습니다.

여러분도 다음 프로젝트에서 직접 적용해보세요.

실전 팁

💡 - @LoadBalanced를 붙이지 않으면 서비스 이름으로 호출할 때 "Unknown host" 에러가 발생합니다

  • application.yml에서 spring.cloud.loadbalancer.ribbon.enabled=false 설정으로 Ribbon을 완전히 비활성화할 수 있습니다
  • 헬스 체크를 활성화하면 장애가 발생한 인스턴스를 자동으로 제외할 수 있습니다

3. Ribbon vs LoadBalancer

회사의 레거시 코드를 보던 김개발 씨는 @RibbonClient라는 어노테이션을 발견했습니다. "이게 뭔가요?

LoadBalancer랑 다른 건가요?" 박시니어 씨가 한숨을 쉬며 답했습니다. "아, 그건 옛날 방식이에요.

이제는 쓰지 않는 게 좋습니다."

Ribbon은 Netflix에서 만든 클라이언트 사이드 로드밸런서로, 한때 Spring Cloud의 표준이었습니다. 하지만 Netflix가 유지보수를 중단하면서 Spring Cloud LoadBalancer가 공식 대체재가 되었습니다.

두 라이브러리는 비슷한 기능을 제공하지만, 현대적인 프로젝트에서는 LoadBalancer 사용을 권장합니다.

다음 코드를 살펴봅시다.

// Ribbon 방식 (Deprecated - 사용 권장하지 않음)
@Configuration
@RibbonClient(name = "user-service")
public class RibbonConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

// Spring Cloud LoadBalancer 방식 (권장)
@Configuration
public class LoadBalancerConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

김개발 씨는 혼란스러웠습니다. 인터넷에서 찾은 많은 예제들이 Ribbon을 사용하고 있었기 때문입니다.

"왜 이렇게 Ribbon 예제가 많은 거죠?" 박시니어 씨가 설명을 시작했습니다. "Ribbon이 오랫동안 업계 표준이었거든요.

하지만 상황이 바뀌었어요. Netflix가 2018년에 Ribbon의 유지보수 중단을 선언했습니다." Ribbon과 Spring Cloud LoadBalancer의 차이점은 무엇일까요?

쉽게 비유하자면, Ribbon은 오래된 명차 같은 존재입니다. 성능도 좋고 검증도 충분히 되었지만, 이제는 부품을 구할 수 없고 AS도 받을 수 없습니다.

반면 Spring Cloud LoadBalancer는 최신 모델입니다. 비슷한 성능을 내면서도 지속적으로 업데이트되고, 새로운 기능도 추가됩니다.

여러분이라면 어떤 차를 선택하시겠습니까? Ribbon이 왜 이렇게 인기가 많았을까요?

Netflix는 전 세계 스트리밍 서비스를 운영하면서 얻은 노하우를 오픈소스로 공개했습니다. Ribbon, Eureka, Hystrix 같은 라이브러리들이 Netflix OSS(Open Source Software) 프로젝트의 일부였습니다.

Spring Cloud는 이 라이브러리들을 적극적으로 채택했고, 많은 기업들이 안심하고 사용했습니다. Ribbon은 안정적이었고, 다양한 로드밸런싱 전략을 제공했으며, 설정도 유연했습니다.

하지만 문제가 생겼습니다. Netflix가 자사의 아키텍처를 변경하면서 많은 OSS 프로젝트의 유지보수를 중단했습니다.

Ribbon도 그중 하나였습니다. 2018년 이후 새로운 기능 추가나 버그 수정이 이루어지지 않았습니다.

Spring Boot 2.4 이상에서는 호환성 문제도 발생하기 시작했습니다. Spring 팀은 대안을 만들기로 결정했습니다.

그렇게 탄생한 것이 Spring Cloud LoadBalancer입니다. Ribbon의 핵심 기능을 유지하면서도 더 가볍고 현대적으로 재설계되었습니다.

가장 큰 차이점은 반응형 프로그래밍 지원입니다. WebFlux와 WebClient를 사용하는 비동기 애플리케이션에서도 완벽하게 동작합니다.

두 라이브러리의 코드를 비교해보겠습니다. Ribbon을 사용할 때는 @RibbonClient 어노테이션으로 각 서비스별 설정을 지정해야 했습니다.

설정 클래스도 따로 만들어야 하고, application.yml에도 많은 설정을 추가해야 했습니다. 반면 Spring Cloud LoadBalancer는 훨씬 간단합니다.

@LoadBalanced 어노테이션만 붙이면 기본 설정으로 바로 동작합니다. 성능 면에서는 어떨까요?

실제 벤치마크 결과를 보면 Spring Cloud LoadBalancer가 Ribbon보다 약간 빠릅니다. 메모리 사용량도 적습니다.

Ribbon은 여러 해 동안 기능이 추가되면서 코드가 복잡해졌지만, LoadBalancer는 처음부터 깔끔하게 설계되었기 때문입니다. 마이그레이션은 어떻게 할까요?

기존 Ribbon 프로젝트를 LoadBalancer로 전환하는 것은 생각보다 간단합니다. 먼저 spring-cloud-starter-netflix-ribbon 의존성을 제거하고, spring-cloud-starter-loadbalancer를 추가합니다.

그다음 @RibbonClient 어노테이션을 제거하면 됩니다. 대부분의 경우 코드 변경 없이 동작합니다.

하지만 주의할 점도 있습니다. Ribbon에서 제공하던 일부 고급 기능은 LoadBalancer에서 다르게 구현되어 있습니다.

예를 들어 커스텀 로드밸런싱 전략을 만들었다면, LoadBalancer의 방식에 맞게 다시 작성해야 합니다. 또한 Ribbon 전용 설정들은 application.yml에서 제거하고 LoadBalancer 설정으로 대체해야 합니다.

다시 김개발 씨의 이야기로 돌아가봅시다. 박시니어 씨의 설명을 듣고 김개발 씨는 결심했습니다.

"알겠습니다. 새 프로젝트에서는 무조건 LoadBalancer를 쓰겠습니다!" 기술은 계속 발전합니다.

과거의 좋았던 라이브러리도 언젠가는 새로운 것으로 대체됩니다. Ribbon에서 Spring Cloud LoadBalancer로의 전환은 자연스러운 진화입니다.

여러분도 새 프로젝트를 시작한다면 망설이지 말고 LoadBalancer를 선택하세요.

실전 팁

💡 - Spring Cloud 2020.0.0 (코드명 Ilford) 이후 버전에서는 LoadBalancer가 기본입니다

  • 레거시 프로젝트라도 보안 업데이트를 받으려면 LoadBalancer로 마이그레이션해야 합니다
  • Ribbon 설정 대부분은 LoadBalancer에서 동일한 기능을 제공하므로 마이그레이션 부담이 적습니다

4. 로드밸런싱 전략

김개발 씨가 물었습니다. "그런데 LoadBalancer는 어떤 기준으로 서버를 선택하나요?

그냥 랜덤인가요?" 박시니어 씨가 고개를 저었습니다. "아니에요.

여러 가지 전략이 있습니다. 상황에 맞게 선택할 수 있어요."

로드밸런싱 전략은 여러 서버 인스턴스 중 어떤 것을 선택할지 결정하는 알고리즘입니다. Spring Cloud LoadBalancer는 기본적으로 라운드 로빈 방식을 사용하지만, 랜덤, 가중치 기반 등 다양한 전략으로 변경할 수 있습니다.

상황에 맞는 전략을 선택하면 시스템의 성능과 안정성을 크게 향상시킬 수 있습니다.

다음 코드를 살펴봅시다.

// 커스텀 로드밸런싱 전략 - 랜덤 방식
@Configuration
public class CustomLoadBalancerConfig {

    // RandomLoadBalancer 사용
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {

        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name);
    }
}

김개발 씨는 서비스를 운영하면서 궁금증이 생겼습니다. 3개의 서버 인스턴스가 있는데, 항상 같은 순서로 요청이 가는 것 같았습니다.

"이게 맞는 건가?" 박시니어 씨가 모니터링 화면을 보여주며 설명했습니다. "맞아요.

기본 전략이 라운드 로빈이거든요. 순서대로 돌아가면서 요청을 보내는 방식입니다." 로드밸런싱 전략이란 정확히 무엇일까요?

쉽게 비유하자면, 로드밸런싱 전략은 마치 교사가 학생들에게 발표 기회를 주는 방식과 같습니다. 라운드 로빈은 번호 순서대로 한 명씩 시키는 방식입니다.

랜덤은 제비뽑기로 무작위로 선택하는 방식입니다. 가중치 기반은 성적이 좋은 학생에게 더 많은 기회를 주는 방식입니다.

이처럼 서버 선택에도 여러 가지 전략이 존재합니다. 가장 기본적인 전략은 라운드 로빈입니다.

**라운드 로빈(Round Robin)**은 사용 가능한 서버를 순서대로 선택합니다. 서버가 A, B, C 세 개라면, 첫 번째 요청은 A로, 두 번째는 B로, 세 번째는 C로, 네 번째는 다시 A로 보냅니다.

구현이 간단하고, 모든 서버를 골고루 사용한다는 장점이 있습니다. Spring Cloud LoadBalancer의 기본 전략이기도 합니다.

하지만 라운드 로빈에도 단점이 있습니다. 모든 서버의 성능이 같다고 가정합니다.

실제로는 어떤 서버는 고성능이고 어떤 서버는 저성능일 수 있습니다. 또한 요청의 처리 시간도 고려하지 않습니다.

어떤 요청은 1초 만에 끝나고, 어떤 요청은 10초가 걸릴 수 있는데 이를 구분하지 못합니다. 두 번째 전략은 랜덤입니다.

랜덤(Random) 전략은 말 그대로 무작위로 서버를 선택합니다. 구현이 매우 간단하고, 특정 패턴이 없어서 예측이 불가능합니다.

라운드 로빈처럼 상태를 저장할 필요도 없습니다. 요청 수가 충분히 많으면 결과적으로 모든 서버에 고르게 분산됩니다.

세 번째 전략은 가중치 기반입니다. 가중치 기반(Weighted) 전략은 각 서버에 가중치를 부여합니다.

고성능 서버는 높은 가중치를, 저성능 서버는 낮은 가중치를 설정합니다. 예를 들어 서버 A의 가중치가 3이고 B의 가중치가 1이라면, A로 요청이 3배 더 많이 갑니다.

서버 성능이 불균등할 때 유용합니다. 네 번째로 반응 시간 기반 전략도 있습니다.

과거의 응답 시간을 측정해서, 빠르게 응답하는 서버를 우선적으로 선택하는 방식입니다. Ribbon에서는 BestAvailableRule이라는 이름으로 제공되었습니다.

하지만 Spring Cloud LoadBalancer에서는 기본 제공하지 않으므로, 필요하다면 직접 구현해야 합니다. 커스텀 전략을 만드는 방법도 있습니다.

위의 코드처럼 ReactorLoadBalancer 인터페이스를 구현하면 됩니다. choose() 메서드에서 원하는 로직으로 서버를 선택할 수 있습니다.

예를 들어 특정 시간대에는 특정 서버로만 보내거나, 요청 헤더의 값에 따라 서버를 선택하는 등 비즈니스 요구사항에 맞게 구현할 수 있습니다. 실제 프로젝트에서는 어떤 전략을 선택해야 할까요?

대부분의 경우 기본 전략인 라운드 로빈으로 충분합니다. 서버 성능이 동일하고, 요청의 처리 시간도 비슷하다면 라운드 로빈이 가장 공평하고 효율적입니다.

하지만 서버 스펙이 다르다면 가중치 기반을 고려해보세요. A/B 테스트를 한다면 랜덤이 적합할 수 있습니다.

주의할 점도 있습니다. 너무 복잡한 로드밸런싱 로직은 오히려 성능을 떨어뜨릴 수 있습니다.

서버를 선택하는 데 시간이 오래 걸리면 전체 응답 시간이 늘어납니다. 또한 디버깅도 어려워집니다.

따라서 정말 필요한 경우가 아니라면 간단한 전략을 사용하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 모니터링 그래프를 다시 확인했습니다. "아, 세 서버에 요청이 정확히 1:1:1로 분산되네요.

라운드 로빈이 확실하네요!" 로드밸런싱 전략을 제대로 이해하고 상황에 맞게 선택하면, 시스템의 성능을 최대한 끌어올릴 수 있습니다. 여러분도 여러 전략을 테스트해보면서 최적의 설정을 찾아보세요.

실전 팁

💡 - 특별한 이유가 없다면 기본 라운드 로빈을 사용하세요. 검증되었고 안정적입니다

  • 가중치 기반 전략은 클라우드 환경에서 인스턴스 크기가 다를 때 유용합니다
  • 커스텀 전략을 만들 때는 성능 테스트를 반드시 수행하세요

5. WebClient와 연동

김개발 씨가 새로운 프로젝트를 시작하게 되었습니다. 요구사항을 보니 "비동기 처리"라는 단어가 눈에 들어왔습니다.

"RestTemplate으로도 되나요?" 박시니어 씨가 고개를 저었습니다. "이제는 WebClient를 써야 할 때예요."

WebClient는 Spring WebFlux에서 제공하는 비동기 논블로킹 HTTP 클라이언트입니다. RestTemplate의 동기 방식과 달리 반응형 프로그래밍을 지원하며, Spring Cloud LoadBalancer와 완벽하게 통합됩니다.

높은 동시성이 필요한 마이크로서비스 환경에서 특히 유용합니다.

다음 코드를 살펴봅시다.

// WebClient에 LoadBalancer 적용
@Configuration
public class WebClientConfig {

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

// 서비스에서 WebClient 사용
@Service
public class ProductService {

    private final WebClient webClient;

    public ProductService(@LoadBalanced WebClient.Builder builder) {
        this.webClient = builder.build();
    }

    // 비동기 논블로킹 방식으로 호출
    public Mono<Product> getProduct(String productId) {
        return webClient.get()
            .uri("http://product-service/api/products/{id}", productId)
            .retrieve()
            .bodyToMono(Product.class);
    }
}

김개발 씨는 RestTemplate을 능숙하게 사용할 수 있게 되었습니다. 하지만 새 프로젝트는 높은 트래픽을 처리해야 했고, 여러 서비스를 동시에 호출해야 하는 요구사항이 있었습니다.

박시니어 씨가 WebFlux 문서를 보여주며 말했습니다. "RestTemplate은 동기 방식이에요.

요청을 보내고 응답이 올 때까지 기다리죠. 하지만 WebClient는 다릅니다." WebClient란 정확히 무엇일까요?

쉽게 비유하자면, RestTemplate은 전통적인 식당 주문 방식과 같습니다. 손님이 주문하면 종업원이 주방에 가서 음식이 나올 때까지 기다렸다가 가져옵니다.

그동안 그 종업원은 다른 일을 할 수 없습니다. 반면 WebClient는 현대적인 진동벨 방식입니다.

주문만 받고 바로 다른 손님을 받습니다. 음식이 준비되면 진동벨로 알려줍니다.

이처럼 WebClient는 응답을 기다리는 동안 다른 작업을 처리할 수 있습니다. RestTemplate의 한계는 무엇이었을까요?

동기 블로킹 방식은 간단하고 이해하기 쉽습니다. 하지만 성능 면에서 문제가 있습니다.

100개의 요청을 처리하려면 100개의 스레드가 필요합니다. 스레드는 메모리를 많이 소비하고, 컨텍스트 스위칭 비용도 큽니다.

트래픽이 많아지면 서버가 버티지 못합니다. 바로 이런 문제를 해결하기 위해 WebClient가 등장했습니다.

비동기 논블로킹 방식으로 동작하기 때문에 적은 수의 스레드로도 많은 요청을 처리할 수 있습니다. 또한 **반응형 프로그래밍(Reactive Programming)**을 지원하여 데이터 스트림을 효율적으로 다룰 수 있습니다.

Spring Cloud LoadBalancer와의 통합도 완벽합니다. WebClient를 설정하는 방법을 살펴보겠습니다.

먼저 WebClient.Builder를 빈으로 등록할 때 @LoadBalanced 어노테이션을 붙입니다. RestTemplate과 똑같은 방식입니다.

그리고 나서 이 Builder를 주입받아서 WebClient 인스턴스를 생성합니다. 한 번 생성한 WebClient는 재사용할 수 있으므로 필드에 저장해두는 것이 좋습니다.

위의 코드를 자세히 분석해보겠습니다. getProduct() 메서드는 Mono<Product>를 반환합니다.

Mono는 0개 또는 1개의 결과를 비동기로 반환하는 리액티브 타입입니다. webClient.get()으로 GET 요청을 시작하고, uri()로 URL을 지정합니다.

여기서도 "http://product-service"처럼 서비스 이름을 사용합니다. LoadBalancer가 자동으로 실제 인스턴스를 선택해줍니다.

retrieve()와 bodyToMono()는 무엇일까요? retrieve()는 응답을 가져오는 작업을 시작합니다.

bodyToMono(Product.class)는 응답 바디를 Product 객체로 변환합니다. 중요한 점은 이 모든 작업이 즉시 실행되지 않는다는 것입니다.

Mono를 구독(subscribe)하거나, 컨트롤러에서 반환할 때 비로소 실행됩니다. 실제 프로젝트에서는 어떻게 활용할까요?

여러 서비스를 동시에 호출해야 하는 상황을 생각해봅시다. 주문 상세 페이지를 보여주려면 주문 정보, 상품 정보, 배송 정보를 각각 다른 서비스에서 가져와야 합니다.

RestTemplate이라면 순차적으로 3번 호출해야 하므로 시간이 오래 걸립니다. 하지만 WebClient라면 Mono.zip()을 사용해서 3개를 동시에 호출하고, 모두 완료되면 결과를 합칠 수 있습니다.

에러 처리는 어떻게 할까요? WebClient는 onErrorResume(), onErrorReturn() 같은 연산자로 에러를 우아하게 처리할 수 있습니다.

예를 들어 상품 서비스 호출이 실패하면 기본값을 반환하거나, 다른 서비스를 호출하는 폴백 로직을 구현할 수 있습니다. 이는 마이크로서비스의 회복탄력성(Resilience)을 높여줍니다.

주의할 점도 있습니다. WebClient를 사용하려면 반응형 프로그래밍에 대한 이해가 필요합니다.

Mono, Flux 같은 개념이 처음에는 낯설 수 있습니다. 또한 디버깅이 RestTemplate보다 어렵습니다.

비동기 호출이라 스택 트레이스가 복잡하기 때문입니다. 따라서 충분한 학습과 연습이 필요합니다.

또 하나 중요한 점은 블로킹 코드와의 혼용입니다. WebClient를 사용하면서 중간에 block()을 호출하면 비동기의 장점이 사라집니다.

반응형 체인 전체가 논블로킹으로 유지되어야 최대 성능을 낼 수 있습니다. 데이터베이스도 R2DBC 같은 반응형 드라이버를 사용하는 것이 좋습니다.

다시 김개발 씨의 이야기로 돌아가봅시다. 박시니어 씨의 지도하에 WebClient를 적용한 김개발 씨는 놀라운 결과를 확인했습니다.

"와, 동일한 서버 스펙으로 처리량이 3배나 늘었어요!" WebClient와 LoadBalancer의 조합은 현대적인 마이크로서비스 아키텍처의 핵심입니다. 비동기 논블로킹 방식으로 높은 성능을 내면서도, 로드밸런싱으로 안정성을 확보할 수 있습니다.

여러분도 다음 프로젝트에서 도전해보세요.

실전 팁

💡 - WebClient는 스레드를 적게 사용하므로 고트래픽 환경에서 유리합니다

  • 테스트할 때는 WebTestClient를 사용하면 편리합니다
  • Spring Boot 3.0부터는 RestTemplate보다 WebClient 사용을 권장합니다

6. 캐시와 성능

시스템을 운영하던 김개발 씨가 이상한 현상을 발견했습니다. 새로운 서버 인스턴스를 추가했는데도 한동안 요청이 가지 않았습니다.

"버그인가요?" 박시니어 씨가 설명했습니다. "아니에요.

캐시 때문입니다."

Spring Cloud LoadBalancer는 성능 향상을 위해 서비스 인스턴스 목록을 캐싱합니다. 기본 캐시 시간은 35초이며, 이 시간 동안은 새로운 인스턴스가 추가되어도 감지되지 않습니다.

캐시 설정을 조정하면 실시간성과 성능 사이의 균형을 맞출 수 있으며, 헬스 체크를 활성화하면 장애 인스턴스를 자동으로 제외할 수 있습니다.

다음 코드를 살펴봅시다.

// application.yml - 캐시 설정
spring:
  cloud:
    loadbalancer:
      cache:
        enabled: true
        ttl: 35s  # 캐시 유지 시간 (기본값)
        capacity: 256  # 캐시 크기
      health-check:
        initial-delay: 0  # 헬스 체크 시작 딜레이
        interval: 25s  # 헬스 체크 주기

// 캐시 무효화 - 필요시 수동으로 호출
@Service
public class LoadBalancerCacheService {

    @Autowired
    private LoadBalancerCacheManager cacheManager;

    public void evictCache(String serviceId) {
        // 특정 서비스의 캐시만 삭제
        cacheManager.getCache(serviceId).clear();
    }
}

김개발 씨는 운영 중인 서비스에 새로운 인스턴스를 추가했습니다. Eureka 대시보드에서는 즉시 등록된 것을 확인할 수 있었습니다.

하지만 실제 트래픽을 보니 새 인스턴스로는 요청이 가지 않았습니다. 박시니어 씨가 시계를 보며 말했습니다.

"30초만 기다려보세요. 그러면 트래픽이 가기 시작할 거예요." LoadBalancer의 캐시는 왜 필요할까요?

쉽게 비유하자면, 캐시는 마치 연락처 목록과 같습니다. 친구에게 전화할 때마다 전화번호부를 뒤지지 않고, 저장된 연락처를 바로 사용합니다.

연락처가 바뀌어도 즉시 반영되지는 않지만, 대신 전화를 거는 속도가 매우 빠릅니다. 이처럼 LoadBalancer도 매번 서비스 레지스트리에 물어보는 대신 캐시를 사용해서 성능을 높입니다.

캐시가 없다면 어떻게 될까요? 요청이 올 때마다 Eureka 같은 서비스 레지스트리에 "product-service의 인스턴스 목록을 주세요"라고 물어봐야 합니다.

네트워크 왕복이 발생하고, 서비스 레지스트리에도 부하가 걸립니다. 초당 1000개의 요청이 온다면 서비스 레지스트리에도 1000번 조회가 발생합니다.

이는 심각한 성능 저하를 유발합니다. 바로 이런 문제를 해결하기 위해 캐시가 도입되었습니다.

LoadBalancer는 한 번 조회한 인스턴스 목록을 로컬 메모리에 저장합니다. 기본적으로 35초 동안 캐시가 유지됩니다.

이 시간 동안은 서비스 레지스트리에 조회하지 않고 캐시된 목록을 사용합니다. 35초가 지나면 캐시가 만료되고, 다음 요청에서 새로 조회합니다.

캐시 시간을 어떻게 조정할까요? application.yml에서 spring.cloud.loadbalancer.cache.ttl 값을 변경하면 됩니다.

실시간성이 중요하다면 10초나 15초로 줄일 수 있습니다. 반대로 인스턴스가 자주 변경되지 않는다면 60초나 그 이상으로 늘려서 성능을 더 향상시킬 수 있습니다.

헬스 체크는 또 다른 중요한 기능입니다. 캐시 때문에 생기는 문제가 하나 더 있습니다.

인스턴스가 죽었는데도 캐시에는 남아있어서 계속 요청이 가는 상황입니다. 이를 방지하기 위해 헬스 체크(Health Check) 기능을 활성화할 수 있습니다.

LoadBalancer가 주기적으로 각 인스턴스의 상태를 확인하고, 문제가 있으면 자동으로 제외합니다. 위의 설정 코드를 살펴보겠습니다.

health-check.interval을 25초로 설정하면 25초마다 각 인스턴스에 헬스 체크 요청을 보냅니다. 인스턴스가 응답하지 않으면 즉시 캐시에서 제거합니다.

이렇게 하면 장애가 발생해도 빠르게 대응할 수 있습니다. 실제 프로젝트에서는 어떻게 설정해야 할까요?

일반적으로 기본값(35초)으로도 충분합니다. 하지만 빠른 오토스케일링이 필요한 환경이라면 15-20초로 줄이는 것을 고려해보세요.

반대로 매우 안정적인 환경에서 성능이 중요하다면 60초 이상으로 늘릴 수 있습니다. 헬스 체크는 반드시 활성화하는 것을 권장합니다.

캐시 용량 설정도 중요합니다. capacity 값은 캐시할 수 있는 서비스의 개수입니다.

기본값은 256으로, 대부분의 경우 충분합니다. 하지만 수백 개의 마이크로서비스를 운영한다면 이 값을 늘려야 할 수도 있습니다.

수동으로 캐시를 무효화할 수도 있습니다. 긴급하게 서버를 교체해야 하는 상황이라면, 위의 코드처럼 LoadBalancerCacheManager를 사용해서 캐시를 직접 삭제할 수 있습니다.

그러면 다음 요청에서 즉시 새로운 인스턴스 목록을 조회합니다. 주의할 점도 있습니다.

캐시 시간을 너무 짧게 설정하면 서비스 레지스트리에 부하가 증가합니다. 특히 마이크로서비스가 많고 트래픽이 높은 환경에서는 주의해야 합니다.

반대로 너무 길게 설정하면 새 인스턴스가 추가되어도 한참 후에야 트래픽을 받기 시작합니다. 모니터링도 중요합니다.

Micrometer 같은 메트릭 라이브러리를 사용하면 캐시 히트율, 헬스 체크 실패 횟수 등을 모니터링할 수 있습니다. 이 데이터를 분석해서 최적의 캐시 설정값을 찾아야 합니다.

다시 김개발 씨의 이야기로 돌아가봅시다. 박시니어 씨의 말대로 30초 후, 새 인스턴스로도 트래픽이 가기 시작했습니다.

"아, 캐시가 만료되었군요!" 김개발 씨는 이제 LoadBalancer의 동작 원리를 완전히 이해했습니다. 캐시는 성능과 실시간성 사이의 트레이드오프입니다.

적절한 설정값을 찾아서 시스템에 맞게 최적화하세요. 헬스 체크를 활성화하고, 모니터링을 통해 지속적으로 개선하면 안정적이고 빠른 마이크로서비스를 운영할 수 있습니다.

실전 팁

💡 - 캐시 TTL과 헬스 체크 주기를 비슷하게 맞추면 효율적입니다 (예: TTL 35초, 헬스 체크 25초)

  • 운영 환경에서는 반드시 헬스 체크를 활성화하세요. 장애 대응 시간이 크게 단축됩니다
  • Actuator를 활성화하면 /actuator/health를 통해 LoadBalancer 상태를 확인할 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Spring Cloud#LoadBalancer#Microservices#WebClient#RestTemplate

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

Istio 설치와 구성 완벽 가이드

Kubernetes 환경에서 Istio 서비스 메시를 설치하고 구성하는 방법을 초급 개발자도 쉽게 이해할 수 있도록 실무 스토리와 비유로 풀어낸 가이드입니다. istioctl 설치부터 사이드카 주입까지 단계별로 학습합니다.

서비스 메시 완벽 가이드

마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.

Helm 마이크로서비스 패키징 완벽 가이드

Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.

관찰 가능한 마이크로서비스 완벽 가이드

마이크로서비스 환경에서 시스템의 상태를 실시간으로 관찰하고 모니터링하는 방법을 배웁니다. Resilience4j, Zipkin, Prometheus, Grafana, EFK 스택을 활용하여 안정적이고 관찰 가능한 시스템을 구축하는 실전 가이드입니다.

Prometheus 메트릭 수집 완벽 가이드

Spring Boot 애플리케이션의 메트릭을 Prometheus로 수집하고 모니터링하는 방법을 배웁니다. Actuator 설정부터 PromQL 쿼리까지 실무에 필요한 모든 내용을 다룹니다.