Spring RSocket 마스터 노트 시리즈 7편. RSocket이 단일 영구 연결이라 일반 L4 LB와 안 맞는 이유, 클라이언트 사이드 로드 밸런싱이 표준이 된 배경, RSocketLoadbalanceTarget·LoadbalanceStrategy 종류(RoundRobin·Weighted), 헬스 체크와 자동 페일오버, Service Discovery 통합(Consul·Eureka), 연결 풀과 재연결 전략까지.
이 글은 Spring RSocket 마스터 노트 시리즈의 일곱 번째 편입니다. 1~6편이 단일 서버였다면, 이번엔 여러 서버로 확장 — 로드 밸런싱.
RSocket은 단일 영구 연결 모델이라 일반 L4 LB(NGINX·HAProxy)와 맞지 않습니다. 클라이언트 사이드 LB가 표준. 이 차이를 이해하는 게 출발점.
처음 LB가 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, "왜 클라이언트 사이드 LB" 막연합니다. NGINX 앞에 두면 안 되나? 둘째, 헬스 체크와 페일오버가 막연합니다.
해결법은 한 가지예요. "RSocket = 영구 연결 = NGINX와 안 맞음" 한 줄. 한 번 연결되면 같은 서버에 영원히. 부하 분산 X. 그래서 클라이언트가 여러 서버 목록을 직접 관리. 이 차이가 핵심.
L4 LB의 한계
[Client] → [NGINX/HAProxy/ELB] → [Server 1, 2, 3]
↑
한 번 연결되면 같은 서버 유지 (TCP 영구)
부하 분산 효과 X
HTTP는 매 요청마다 다른 서버 OK (stateless). RSocket은 영구 연결.
여기서 정말 중요한 시험 함정 — L4 LB + RSocket = 부하 분산 X. 다중 클라이언트가 같은 서버에 몰릴 수 있음. 클라이언트 사이드 LB가 답.
클라이언트 사이드 LB
[Client]
├── 서버 목록 [server-1, server-2, server-3]
├── 헬스 체크
└── 메시지마다 또는 연결마다 다른 서버 선택
각 클라이언트가 직접 LB. 헬스 체크·페일오버 모두 클라이언트 책임.
Reactor Netty / RSocket Loadbalancer
import io.rsocket.loadbalance.LoadbalanceRSocketClient;
import io.rsocket.loadbalance.LoadbalanceTarget;
List<LoadbalanceTarget> targets = List.of(
LoadbalanceTarget.from("server-1", TcpClientTransport.create("server-1", 7000)),
LoadbalanceTarget.from("server-2", TcpClientTransport.create("server-2", 7000)),
LoadbalanceTarget.from("server-3", TcpClientTransport.create("server-3", 7000))
);
LoadbalanceRSocketClient client = LoadbalanceRSocketClient.create(
LoadbalanceStrategy.roundRobin(),
Flux.just(targets)
);
RSocketRequester requester = RSocketRequester.builder()
.rsocketClient(client);
여러 서버에 분산. RoundRobin·Weighted 등 전략 선택.
LoadbalanceStrategy 종류
| Strategy | 설명 |
|---|---|
| RoundRobin | 순차 분배 |
| Weighted | 가중치 기반 |
| WeightedReactiveLoadbalanceStrategy | 동적 가중치 (응답 시간 기반) |
| 커스텀 | LoadbalanceStrategy 인터페이스 구현 |
RoundRobin
LoadbalanceStrategy.roundRobin()
가장 단순. 서버 부하 무관·균등 분배.
Weighted
WeightedLoadbalanceStrategy weighted = new WeightedLoadbalanceStrategy();
응답 시간 빠른 서버에 더 많이.
여기서 시험 함정이 하나 있어요. WeightedReactiveLoadbalanceStrategy가 권장. 동적 가중치·응답 시간 기반. 정적 weight는 단순하지만 부정확.
Service Discovery 통합
서버 목록이 동적 변경 (스케일링·다운). Service Discovery에서 가져오기.
Consul
@Autowired
private DiscoveryClient discoveryClient;
public Flux<List<LoadbalanceTarget>> serverList() {
return Flux.interval(Duration.ofSeconds(10))
.map(i -> discoveryClient.getInstances("user-service"))
.map(instances -> instances.stream()
.map(inst -> LoadbalanceTarget.from(
inst.getInstanceId(),
TcpClientTransport.create(inst.getHost(), inst.getPort())
))
.toList()
);
}
Eureka·Kubernetes Service
비슷하게 통합 가능. 매니지드 환경 (K8s) = Service DNS 사용도 가능.
헬스 체크
자동 헬스 체크
LoadbalanceRSocketClient client = LoadbalanceRSocketClient
.builder(serverListFlux)
.strategy(LoadbalanceStrategy.roundRobin())
.build();
내부적으로 연결 실패한 서버는 자동 제외.
명시적 헬스 체크
Flux<List<LoadbalanceTarget>> healthyServers = Flux.interval(Duration.ofSeconds(5))
.flatMap(i -> Flux.fromIterable(allServers)
.flatMap(this::checkHealth)
.filter(t -> t.isHealthy())
.map(this::toTarget)
.collectList()
);
LoadbalanceRSocketClient.create(strategy, healthyServers);
여기서 정말 중요한 시험 함정 — 헬스 체크 주기. 너무 자주 = 오버헤드, 너무 가끔 = 페일오버 늦음. 5~30초가 일반적.
페일오버
// 한 서버 다운 → 자동 다른 서버
requester.route("user.{id}", id)
.retrieveMono(User.class)
.retryWhen(Retry.fixedDelay(3, Duration.ofMillis(100)))
// 다음 retry 시 다른 서버 선택
.subscribe();
LoadbalanceRSocketClient가 자동 다음 서버 시도.
Sticky Routing — 같은 서버 유지
특정 사용자 = 같은 서버 (예: 세션 데이터):
// 라우팅 메타데이터에 user-id 포함
// 서버에서 user-id 기반 분배
또는 클라이언트 측에서 user-id로 hash → 서버 선택.
여기서 시험 함정이 하나 있어요. Sticky는 stateful 상황만. Stateless 마이크로서비스는 일반 LB. Sticky 시 한 서버 다운 = 그 사용자 영향.
연결 풀
1 클라이언트 → 1 LoadbalanceRSocketClient → N 서버 연결 (Pool)
각 서버에 연결 1개. 단일 연결 멀티플렉싱 (Lettuce 비슷).
// 풀 크기는 서버 수 (자동)
// 각 연결 = RSocket 단일 영구
Spring Cloud Gateway 통합
spring:
cloud:
gateway:
routes:
- id: user-service
uri: rsocket://user-service:7000
predicates:
- Path=/users/**
Gateway에서 RSocket 백엔드로 LB. Spring Cloud LB 통합.
Resumability + LB
// Resume 활성화 + 다중 서버
// 단점 — Resume Token은 한 서버만 유효
// 다른 서버로 페일오버 시 새 연결
여기서 시험 함정이 하나 있어요. Resume은 같은 서버 가정. LB 환경에선 Resume 효과 X. 둘 중 하나 선택.
모니터링
Micrometer
@Bean
public MeterRegistryCustomizer<MeterRegistry> rsocketMetrics() {
return registry -> registry.config().commonTags("application", "user-client");
}
자동 메트릭:
rsocket.client.requestsrsocket.client.duration- 서버별 분리 (LoadbalanceTarget 이름)
Spring Boot Actuator
management:
metrics:
enable:
rsocket: true
endpoints:
web:
exposure:
include: metrics,rsocket-mappings
운영 체크리스트
✓ 클라이언트 사이드 LB (L4 X)
✓ Service Discovery 통합 (Consul·Eureka·K8s)
✓ 헬스 체크 5~30초
✓ WeightedReactive 전략 권장
✓ 자동 페일오버
✓ 연결 풀 (LoadbalanceRSocketClient)
✓ 재시도 + 타임아웃
✓ Sticky는 stateful만
✓ Resume vs LB — 둘 중 하나
✓ Micrometer 모니터링
✓ 서버별 메트릭 분리
클러스터링·HA 패턴
Active-Active
[LB Client] → [Server 1, 2, 3 (모두 active)]
모든 서버에 트래픽. 일반.
Active-Passive
[LB Client] → Primary [Server 1]
Standby [Server 2 (대기)]
↓ Server 1 다운 시
Server 2 active
특수 (예: 레거시 API).
처리량·지연 비교
| 방식 | 처리량 | 지연 |
|---|---|---|
| 단일 서버 | 낮음 | 낮음 |
| L4 LB + RSocket | 분산 X | 중간 |
| 클라이언트 LB + RoundRobin | 높음 | 낮음 |
| 클라이언트 LB + Weighted | 가장 높음 | 가장 낮음 |
여기서 정말 중요한 시험 함정 — 클라이언트 LB가 최선. RSocket의 영구 연결 특성을 살리면서 부하 분산.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 7편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- RSocket = 단일 영구 연결
- L4 LB와 안 맞음 (한 서버 유지·분산 X)
- 클라이언트 사이드 LB가 표준
LoadbalanceRSocketClient+LoadbalanceTarget- LoadbalanceStrategy — RoundRobin / Weighted / WeightedReactive
- WeightedReactive 권장 (동적·응답 시간 기반)
- Service Discovery 통합 — Consul·Eureka·Kubernetes
- 동적 서버 목록 → Flux로 갱신
- 헬스 체크 5~30초
- 자동 페일오버 — 다음 서버 자동 선택
- Sticky Routing = stateful 상황만 (일반 X)
- 연결 풀 — 서버당 1 연결 (단일 영구 멀티플렉싱)
- Spring Cloud Gateway 통합 —
uri: rsocket://... - Resume vs LB = 둘 중 하나
- Resume은 같은 서버 가정
- Micrometer 메트릭 — 서버별 분리
- 클러스터 — Active-Active (일반) / Active-Passive (특수)
- 처리량 — 클라이언트 LB + Weighted = 최선
시리즈 다른 편
- 1편 — 기본 개념·프레임
- 2편 — 4 Interaction Models
- 3편 — Spring RSocket 서버
- 4편 — Spring RSocket 클라이언트
- 5편 — 메타데이터·Composite Metadata
- 6편 — 보안·Spring Security RSocket·TLS
- 7편 — 로드 밸런싱·확장 (현재 글)
- 8편 — 테스트
- 9편 — RSocket vs gRPC vs WebSocket
공식 문서: RSocket Loadbalancer 에서 더 깊이.
다음 글(8편)에서는 테스트 — Embedded Server, MockRSocketRequester, StepVerifier, 통합 테스트 패턴까지 풀어 갑니다.