Server-side Discovery#
Server-side Discovery는 클라이언트가 서비스의 위치를 직접 찾지 않고, 중간에 위치한 로드 밸런서나 프록시 서버가 서비스 위치를 찾아 요청을 라우팅하는 방식이다.
Server-side Discovery는 클라이언트를 단순화하고 중앙 집중식 관리를 가능하게 하는 장점이 있지만, 추가 인프라와 관리가 필요한 단점도 있다. 프로젝트의 요구사항과 팀의 역량을 고려하여 적절히 선택해야 한다.
작동 원리#
- 서비스 등록: 각 서비스 인스턴스는 시작 시 자신의 정보를 서비스 레지스트리에 등록한다.
- 클라이언트 요청: 클라이언트는 서비스의 실제 위치를 모르고, 로드 밸런서에 요청을 보낸다.
- 서비스 조회: 로드 밸런서는 서비스 레지스트리에서 해당 서비스의 가용한 인스턴스 정보를 조회한다.
- 요청 라우팅: 로드 밸런서는 적절한 서비스 인스턴스를 선택하여 요청을 전달한다.
- 응답 반환: 서비스의 응답은 로드 밸런서를 통해 클라이언트에게 전달된다.
- 클라이언트 단순화: 클라이언트는 서비스 디스커버리 로직을 구현할 필요가 없어 단순해진다.
- 언어 중립성: 클라이언트 측 구현이 필요 없어 다양한 프로그래밍 언어로 개발된 서비스들을 쉽게 통합할 수 있다.
- 보안 강화: 로드 밸런서 수준에서 추가적인 보안 계층을 구현할 수 있다.
- 중앙 집중식 관리: 서비스 디스커버리와 로드 밸런싱을 중앙에서 관리할 수 있다.
- 추가 인프라 필요: 로드 밸런서나 프록시 서버와 같은 추가 인프라가 필요하다.
- 단일 실패 지점: 로드 밸런서가 단일 실패 지점이 될 수 있어 고가용성 설계가 중요하다.
- 복잡성 증가: 전체 시스템의 복잡성이 증가할 수 있다.
구현 예시#
- AWS Elastic Load Balancer (ELB): 클라이언트는 ELB의 DNS 이름을 통해 요청을 보내며, ELB는 등록된 EC2 인스턴스나 ECS 컨테이너 사이에서 부하를 분산한다.
- Kubernetes의 kube-proxy: Kubernetes에서는 각 노드에서 실행되는 kube-proxy가 서비스 디스커버리와 로드 밸런싱을 담당하며, 클러스터 내의 서비스 요청을 적절한 파드(Pod)로 전달한다.
- Node.js를 사용한 Server-side Discovery 구현 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| // 서버 사이드 디스커버리 라우터 구현
class ServiceRouter {
constructor(options = {}) {
this.registryUrl = options.registryUrl || 'http://service-registry:8500';
this.serviceCache = new Map();
this.cacheTimeout = options.cacheTimeout || 30000; // 30초
this.loadBalancer = new LoadBalancer();
}
async handleRequest(req, res) {
const serviceName = this.extractServiceName(req);
try {
// 서비스 인스턴스 찾기
const serviceInstance = await this.findServiceInstance(serviceName);
// 요청 전달
const response = await this.forwardRequest(req, serviceInstance);
// 응답 전달
this.sendResponse(res, response);
} catch (error) {
this.handleError(res, error);
}
}
async findServiceInstance(serviceName) {
// 캐시된 서비스 확인
const cachedInstances = this.getCachedInstances(serviceName);
if (cachedInstances && cachedInstances.length > 0) {
return this.loadBalancer.selectInstance(cachedInstances);
}
// 서비스 레지스트리 조회
const instances = await this.queryRegistry(serviceName);
this.updateCache(serviceName, instances);
return this.loadBalancer.selectInstance(instances);
}
async forwardRequest(req, serviceInstance) {
const targetUrl = this.buildTargetUrl(serviceInstance, req.path);
return await fetch(targetUrl, {
method: req.method,
headers: req.headers,
body: req.body,
timeout: 5000
});
}
}
|
참고 및 출처#