분산 시스템 아키텍처에서 서버 부하를 효과적으로 관리하고 가용성을 높이기 위해 다양한 로드 밸런싱(Load Balancing) 알고리즘이 사용됩니다. 본 글에서는 가장 널리 쓰이는 6가지 알고리즘의 작동 원리, 장단점 및 구현 로직을 기술적 관점에서 상세히 살펴보겠습니다.
1. 라운드 로빈 (Round Robin)
라운드 로빈은 가장 직관적이고 단순한 무상태(Stateless) 스케줄링 기법입니다. 서버 목록을 순차적으로 반복하여 요청을 배정합니다. 각 서버의 성능 차이를 고려하지 않으며, 모든 서버가 동일한 처리 능력을 가진다고 가정할 때 적합합니다.
동작 예시: 서버 A, B, C가 있을 경우, 요청은 A → B → C → A ... 순서로 배정됩니다.
// 라운드 로빈 의사 코드
int currentIndex = 0;
List<Server> serverList = getActiveServers();
if (serverList.isEmpty()) {
throw new RuntimeException("No available servers");
}
// 인덱스 초기화 또는 증가
currentIndex = (currentIndex + 1) % serverList.size();
return serverList.get(currentIndex);
특징: 구현이 간단하고 오버헤드가 적지만, 서버 사양이 상이한 환경에서는 특정 서버에 과부하가 걸릴 수 있습니다.
2. 랜덤 (Random)
랜덤 알고리즘은 서버 풀에서 임의로 하나의 서버를 선택하는 방식입니다. 통계학적인 대수의 법칙에 따라 충분한 수의 요청이 발생하면 평균적으로 라운드 로빈과 유사한 분포를 보이게 됩니다.
동작 예시: 요청마다 난수 생성기를 사용하여 서버 인덱스를 결정합니다.
// 랜덤 의사 코드
import java.util.Random;
Random random = new Random();
int selectedIndex = random.nextInt(serverList.size());
return serverList.get(selectedIndex);
특징: 상태 정보를 유지할 필요가 없어 경량적입니다. 다만, 단기적으로는 특정 서버에 요청이 집중될 수 있는 편차가 존재합니다.
3. 소스 IP 해시 (Source IP Hash / Sticky Session)
클라이언트의 소스 IP 주소를 해시 함수에 입력하여 값을 도출하고, 이 해시값을 서버 개수로 나눈 나머지를 통해 서버를 배정합니다. 동일한 클라이언트는 항상 동일한 서버로 연결되므로 세션 지속성(Session Persistence)이 필요한 경우에 유용합니다.
동작 예시:
// 소스 IP 해시 의사 코드
String clientIp = request.getSourceIpAddress();
int hashValue = Math.abs(clientIp.hashCode()); // 음수 방지
int serverIndex = hashValue % serverCount;
return serverPool[serverIndex];
특징: 서버 장애 시 해당 서버에 할당된 클라이언트들은 재연결 실패나 서비스 중단을 겪을 수 있으며, 해시 충돌로 인해 특정 서버에 부하가 쏠리는 '핫 스팟(Hot Spot)' 문제가 발생할 수 있습니다.
4. 가중치 기반 라운드 로빈 (Weighted Round Robin)
서버마다 하드웨어 사양이나 네트워크 대역폭이 다른 현실적인 환경을 반영하기 위해 도입된 알고리즘입니다. 각 서버에 가중치(Weight)를 부여하며, 가중치가 높은 서버일수록 더 많은 요청을 처리하도록 조정합니다.
동작 원리: 가중치를 기반으로 가상 서버 목록을 확장하거나, 카운터 방식을 사용해 비율대로 요청을 분배합니다.
| 서버 주소 | 가중치 | 설명 |
|---|---|---|
| 192.168.0.1 | 10 | 고사양 서버 (요청 많이 받음) |
| 192.168.0.2 | 4 | 중급 사양 서버 |
| 192.168.0.3 | 1 | 저사양 서버 (요청 적게 받음) |
위 설정에서 총 15번의 요청이 들어올 경우, 서버 .1은 약 10회, 서버 .2는 4회, 서버 .3은 1회 정도 요청을 처리하게 됩니다.
5. 가중치 기반 랜덤 (Weighted Random)
랜덤 알고리즘에 가중치를 적용한 변형입니다. 서버를 선택할 확률을 가중치에 비례하게 만듭니다. 즉, 가중치가 큰 서버가 선택될 확률이 높아지지만, 여전히 확률적 요소가 포함되어 있어 완전한 균등 분배보다는 유연한 분산을 허용합니다.
구현 논리: 전체 가중치의 합을 구하고, 그 범위 내에서 난수를 생성하여 누적 가중치와 비교해 서버를 선정합니다.
# 가중치 기반 랜덤 의사 코드 (Python 스타일)
def weighted_random_choice(servers):
total_weight = sum(s.weight for s in servers)
r = random.uniform(0, total_weight)
current_sum = 0
for server in servers:
current_sum += server.weight
if r <= current_sum:
return server
return servers[-1] # Fallback
특징: 라운드 로빈보다 부드러운 트래픽 분산을 제공하며, 급격한 트래픽 변화에 대해 상대적으로 덜 민감합니다.
6. 최소 연결 수 (Least Connections)
현재 각 서버에 연결되어 있는 활성 커넥션(Active Connection)의 수를 실시간으로 추적하여, 가장 적은 수의 연결을 가진 서버로 새 요청을 배정합니다. 이는 서버의 현재 부하 상태를 직접적으로 반영하므로 동적 디스트리뷰션(Dynamic Distribution)에 매우 효과적입니다.
동작 원리: 요청 수신 시 연결 수 증가, 응답 완료/타임아웃 시 연결 수 감소라는 이벤트 기반 카운팅을 사용합니다.
// 최소 연결 수 의사 코드
Server bestServer = null;
int minConnections = Integer.MAX_VALUE;
for (Server server : serverList) {
int currentConn = server.getActiveConnectionCount();
// 가중치를 고려한 정규화된 연결 수 계산 가능
if (currentConn < minConnections) {
minConnections = currentConn;
bestServer = server;
}
}
return bestServer;
특징: 요청 처리 시간이 제각각인 경우(예: 복잡한 쿼리 vs 간단한 조회) 라운드 로빈보다 훨씬 정확한 부하 분산을 제공합니다. 다만, 연결 상태 정보를 중앙에서 관리해야 하므로 동기화 오버헤드가 발생할 수 있습니다.
알고리즘 선택 가이드
- 단순성과 속도 우선: 라운드 로빈 또는 랜덤
- 세션 지속성 필요: 소스 IP 해시
- 서버 사양 불균일: 가중치 기반 라운드 로빈 또는 가중치 기반 랜덤
- 실시간 부하 정밀 제어: 최소 연결 수