Spring RSocket 마스터 — 로드 밸런싱·확장

2026-05-03확률과 통계 마스터 노트

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.requests
  • rsocket.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 = 최선

시리즈 다른 편

공식 문서: RSocket Loadbalancer 에서 더 깊이.

다음 글(8편)에서는 테스트 — Embedded Server, MockRSocketRequester, StepVerifier, 통합 테스트 패턴까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!