로드 밸런서는 네트워크 트래픽을 여러 서버 또는 리소스에 균등하게 분산시켜주는 기능을 합니다. 주로 높은 가용성과 성능 향상을 위해 사용됩니다.
로드 밸런서는 대개 서버 측에서 구성되며, 클라이언트는 로드 밸런서의 IP 주소 또는 호스트명을 통해 요청을 전송합니다. 로드 밸런서는 이 요청을 받아서 여러 서버로 분산시키는 역할을 수행합니다. 이를 통해 특정 서버에 너무 많은 부하가 걸리는 것을 방지하고, 여러 서버 간의 부하를 균형있게 분산시켜줍니다.
게이트웨이와 로드 밸런서는 비슷한 역할을 수행하는 것처럼 보일 수 있지만, 주요한 차이점이 있습니다. 게이트웨이는 주로 마이크로서비스 아키텍처에서 서비스 간의 통신을 관리하고, 보안, 라우팅, 프로토콜 변환 등을 수행하는 데 중점을 둡니다. 반면 로드 밸런서는 트래픽을 여러 서버로 분산시켜 성능을 최적화하는 데 중점을 둡니다.
서킷 브레이커는 다르게 작동합니다. 이는 주로 장애나 지연된 서비스에서 요청을 차단하여 전체 시스템의 성능을 보호하는 데 사용됩니다. 서킷 브레이커는 일시적으로 서비스에 대한 요청을 차단하고, 대신에 대체 동작이나 에러를 반환하여 네트워크 문제로부터 보호합니다. 로드 밸런서는 트래픽을 균등하게 분산시켜 성능을 최적화하는 반면, 서킷 브레이커는 서비스의 안정성과 신뢰성을 유지하는 데 집중합니다.
Spring Cloud LoadBalancer를 사용하여 다른 마이크로서비스 호출 시 클라이언트 측 로드 밸런싱을 제공하는 마이크로서비스 애플리케이션을 구축합니다.
이 가이드에서는 두 개의 프로젝트를 빌드하는 과정을 안내하며, 그 중 하나는 다른 프로젝트에 종속됩니다. 따라서 루트 프로젝트 아래에 두 개의 하위 프로젝트를 만들어야 합니다. 먼저 최상위 수준에서 빌드 구성을 만듭니다.
Gradle의 경우 동일한 디렉터리를 포함하는 settings.gradle
이 필요합니다.
rootProject.name = 'spring-cloud-lodadbalancer'
include 'say-hello'
include 'user'
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'guide'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'guide'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2023.0.0")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
우리의 "서버" 서비스는 Say Hello
라고 합니다. /greeting
에서 액세스할 수 있는 엔드포인트에서 무작위 인사말(3개의 static 목록에서 선택)을 반환합니다.
src/main/java/hello
에서 SayHelloApplication.java
파일을 생성합니다.
다음 목록은 say-hello/src/main/java/hello/SayHelloApplication.java
의 내용을 보여줍니다.
이는 /greeting
에 대한 @RequestMapping
메소드와 루트 경로 /
에 대한 @RequestMapping
메소드가 있는 간단한 @RestController
입니다.
우리는 클라이언트 서비스 애플리케이션과 함께 이 애플리케이션의 여러 인스턴스를 로컬로 실행할 것입니다. 시작하려면:
src/main/resources
디렉터리를 만듭니다.
디렉터리 내에 application.yml
파일을 만듭니다.
해당 파일에서 server.port
의 기본값을 설정하십시오.
(우리는 응용 프로그램의 다른 인스턴스가 다른 포트에서 실행되도록 지시하여 해당 응용 프로그램을 실행할 때 Say Hello
인스턴스 중 어느 것도 클라이언트와 충돌하지 않도록 할 것입니다.) 이 파일에 있는 동안 서비스에 대한 spring.application.name
도 설정할 수 있습니다.
다음 목록은 say-hello/src/main/resources/application.yml
의 내용을 보여줍니다.
spring:
application:
name: say-hello
server:
port: 8090
HTTP 요청마다 별도의 스레드 또는 프로세스를 사용하는 것과 여러 개의 애플리케이션 인스턴스를 실행하는 것 사이에는 몇 가지 구조적인 차이가 있습니다.
단일 인스턴스 내에서 스레드 또는 프로세스 처리:
여러 인스턴스 간 부하 분산:
단일 인스턴스 내에서 다중 스레드 또는 비동기 방식으로 요청을 처리하는 것과 여러 인스턴스를 실행하여 부하를 분산하는 것은 각각의 장단점이 있습니다.
단일 인스턴스 내에서 다중 스레드 또는 비동기 처리의 장점:
여러 인스턴스 실행의 장점:
따라서, 각각의 방식은 서로 다른 상황에서 유용할 수 있습니다. 요청마다 별도의 스레드 또는 프로세스를 사용하여 처리할 때도 로드 밸런싱과 같은 방식을 통해 부하를 분산할 수 있지만, 여러 인스턴스를 실행함으로써 이를 더 쉽게 관리하고 효과적으로 확장할 수 있습니다.
사용자에게는 User
애플리케이션이 표시됩니다. 인사말을 받기 위해 Say Hello
애플리케이션을 호출(call)한 다음 사용자가 /hi
및 /hello
의 엔드포인트를 방문할 때 해당 인사말을 사용자에게 보냅니다.
사용자 애플리케이션 디렉터리의 src/main/java/hello
아래에 UserApplication.java
파일을 추가합니다.
다음 목록은 user/src/main/java/hello/UserApplication.java
의 내용을 보여줍니다.
@SpringBootApplication
@RestController
public class UserApplication {
private final WebClient.Builder loadBalancedWebClientBuilder;
private final ReactorLoadBalancerExchangeFilterFunction lbFunction;
public UserApplication(WebClient.Builder webClientBuilder,
ReactorLoadBalancerExchangeFilterFunction lbFunction) {
this.loadBalancedWebClientBuilder = webClientBuilder;
this.lbFunction = lbFunction;
}
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
@RequestMapping("/hi")
public Mono<String> hi(@RequestParam(value = "name", defaultValue = "Mary") String name) {
return loadBalancedWebClientBuilder.build().get().uri("http://say-hello/greeting")
.retrieve().bodyToMono(String.class)
.map(greeting -> String.format("%s, %s!", greeting, name));
}
@RequestMapping("/hello")
public Mono<String> hello(@RequestParam(value = "name", defaultValue = "John") String name) {
return WebClient.builder()
.filter(lbFunction)
.build().get().uri("http://say-hello/greeting")
.retrieve().bodyToMono(String.class)
.map(greeting -> String.format("%s, %s!", greeting, name));
}
}
1.1 Mono:
1.2 Flux:
WebClient.Builder:
ReactorLoadBalancerExchangeFilterFunction:
hi
메서드와 hello
메서드에서 모두 부하 분산 기능이 설정되어 있습니다. WebClient.Builder
는 이미 부하 분산이 설정된 WebClient를 빌드하기 위한 빌더이고, WebClient.builder()
는 새로운 WebClient를 생성하고 거기에 부하 분산 기능을 추가하는 것입니다.
WebClient.Builder:
WebClient.Builder
를 사용하여 부하 분산이 설정되면, 이는 Ribbon
과 같은 라이브러리를 이용하여 서비스 디스커버리와 부하 분산 기능을 활용하고 있는 것으로 이해할 수 있습니다.Ribbon
을 통합하여 부하 분산을 수행할 수 있도록 지원하며, 이를 위해 @LoadBalanced
어노테이션이나 ReactorLoadBalancerExchangeFilterFunction
과 같은 구성을 제공합니다.WebClient.builder():
WebClient.builder()
는 새로운 WebClient를 생성하기 위한 정적 팩토리 메서드입니다. 이 메서드를 통해 새로운 WebClient를 만들 수 있으며, 해당 WebClient에는 특정한 구성이 없습니다.filter(lbFunction)
을 사용하여 여기에서도 부하 분산 기능을 추가하고 있습니다. 이것이 부하 분산 설정을 하는 부분입니다.또한 로드 밸런싱된 WebClient.Builder
인스턴스를 설정하는 @Configuration
클래스도 필요합니다. 다음 목록은 user/src/main/java/hello/WebClientConfig.java
의 내용을 보여줍니다.
package hello;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
@LoadBalancerClient(name = "say-hello", configuration = SayHelloConfiguration.class)
public class WebClientConfig {
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
구성(configuration)은 누군가 UserApplication.java
의 hi
엔드포인트에 도달할 때 사용하는 @LoadBalanced WebClient.Builder
인스턴스를 제공합니다. hi
엔드포인트에 도달하면 이 빌더를 사용하여 Say Hello
서비스의 URL에 대한 HTTP GET
요청을 만들고 결과를 String
로 제공하는 WebClient
인스턴스를 생성합니다.
UserApplication.java
에는 동일한 작업을 수행하는 /hello
엔드포인트도 추가했습니다. 그러나 @LoadBalanced
주석을 사용하는 대신 프로그래밍 방식으로 구축한 WebClient
인스턴스에 filter()
메서드를 사용하여 전달하는 @Autowired
로드 밸런서 교환 필터 함수(lbFunction
)를 사용합니다.
두 엔드포인트에 대해 로드 밸런싱된
WebClient
인스턴스를 약간 다르게 설정하더라도 두 엔드포인트의 최종 동작은 완전히 동일합니다. Spring Cloud LoadBalancer는Say Hello
서비스의 적절한 인스턴스를 선택하는 데 사용됩니다.
spring.application.name
및 server.port
속성을 src/main/resources/application.properties
또는 src/main/resources/application.yml
에 추가합니다.
다음 목록은 user/src/main/resources/application.yml
내용을 보여줍니다.
spring:
application:
name: user
server:
port: 8888
package hello;
import java.util.Arrays;
import java.util.List;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* @author Olga Maciaszek-Sharma
*/
public class SayHelloConfiguration {
@Bean
@Primary
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new DemoServiceInstanceListSuppler("say-hello");
}
}
class DemoServiceInstanceListSuppler implements ServiceInstanceListSupplier {
private final String serviceId;
DemoServiceInstanceListSuppler(String serviceId) {
this.serviceId = serviceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays
.asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 8090, false),
new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 9092, false),
new DefaultServiceInstance(serviceId + "3", serviceId, "localhost", 9999, false)));
}
}
해당 클래스에서는 Say Hello
서비스를 호출하는 동안 Spring Cloud LoadBalancer가 선택하는 세 개의 하드 코딩된 인스턴스가 있는 사용자 정의 ServiceInstanceListSupplier
를 제공합니다.
이 단계는 Spring Cloud LoadBalancer에 사용자 정의 구성을 전달하는 방법을 설명하기 위해 추가되었습니다. 그러나
@LoadBalancerClient
주석을 사용하고 LoadBalancer에 대한 자체 구성을 생성할 필요는 없습니다. 가장 일반적인 방법은 서비스 검색(discovery)과 함께 Spring Cloud LoadBalancer를 사용하는 것입니다. classpath에DiscoveryClient
가 있는 경우 기본 Spring Cloud LoadBalancer 구성은 이를 사용하여 서비스 인스턴스를 확인합니다. 결과적으로 실행 중인 인스턴스 중에서만 선택하게 됩니다.