ストーリー
サービスディスカバリとは
動的にサービスのネットワーク位置(IP・ポート)を発見する仕組みです。
問題: サービスのインスタンスは動的に変化する
Order Service
├─ Instance 1: 10.0.1.5:3000 ← 起動中
├─ Instance 2: 10.0.1.8:3000 ← 新規追加
└─ Instance 3: 10.0.1.3:3000 ← ダウン中
→ Payment ServiceはどのIPに接続すべきか?
クライアントサイドディスカバリ
// クライアントサイドディスカバリ
// サービスが直接レジストリに問い合わせる
class ServiceRegistry {
private services: Map<string, ServiceInstance[]> = new Map();
async register(name: string, instance: ServiceInstance): Promise<void> {
const instances = this.services.get(name) || [];
instances.push(instance);
this.services.set(name, instances);
}
async discover(name: string): Promise<ServiceInstance[]> {
return this.services.get(name) || [];
}
async deregister(name: string, instanceId: string): Promise<void> {
const instances = this.services.get(name) || [];
this.services.set(name, instances.filter(i => i.id !== instanceId));
}
}
// 利用側
async function callOrderService(orderId: string) {
const instances = await registry.discover("order-service");
const healthy = instances.filter(i => i.status === "healthy");
const target = loadBalancer.select(healthy); // クライアント側でLB
return fetch(`http://${target.host}:${target.port}/orders/${orderId}`);
}
サーバーサイドディスカバリ
サーバーサイドディスカバリ(ロードバランサー経由):
Payment ──→ [Load Balancer] ──→ Order Instance 1
Service (DNS名で解決) ──→ Order Instance 2
──→ Order Instance 3
→ クライアントはLBのアドレスだけ知ればよい
→ Kubernetes Service がこの方式
比較
| 観点 | クライアントサイド | サーバーサイド |
|---|---|---|
| ロードバランシング | クライアント側で実装 | LB/プロキシが担当 |
| クライアント複雑度 | 高い(LBロジック必要) | 低い(DNS名だけ) |
| ネットワークホップ | 少ない(直接通信) | 多い(LB経由) |
| 代表例 | Netflix Eureka + Ribbon | Kubernetes Service, AWS ALB |
サービスレジストリ
サービスの登録情報を管理する中央のデータストアです。
// サービスレジストリのデータ構造
interface ServiceInstance {
id: string;
name: string;
host: string;
port: number;
status: "healthy" | "unhealthy" | "draining";
metadata: {
version: string;
region: string;
weight: number;
};
lastHeartbeat: Date;
}
// ヘルスチェックの仕組み
class HealthChecker {
// 定期的にヘルスチェック(TTLベース)
async checkHealth(instance: ServiceInstance): Promise<boolean> {
try {
const response = await fetch(
`http://${instance.host}:${instance.port}/health`,
{ signal: AbortSignal.timeout(3000) }
);
return response.ok;
} catch {
return false;
}
}
// 一定時間ヘルスチェック失敗 → 自動登録解除
async evictUnhealthy(registry: ServiceRegistry): Promise<void> {
const allServices = await registry.listAll();
for (const instance of allServices) {
const elapsed = Date.now() - instance.lastHeartbeat.getTime();
if (elapsed > 30_000) { // 30秒間応答なし
await registry.deregister(instance.name, instance.id);
}
}
}
}
主要なサービスレジストリ
| ツール | 特徴 | 一貫性 |
|---|---|---|
| Consul | ヘルスチェック内蔵、KV Store | CP |
| etcd | Kubernetes標準、高信頼 | CP |
| ZooKeeper | 実績豊富、複雑 | CP |
| Eureka | Netflix製、AP型 | AP |
| Kubernetes DNS | K8s標準、追加インストール不要 | - |
ロードバランシング
複数のサービスインスタンスにトラフィックを分散する仕組みです。
// ロードバランシングアルゴリズム
interface LoadBalancer {
select(instances: ServiceInstance[]): ServiceInstance;
}
// ラウンドロビン: 順番に振り分け
class RoundRobinLB implements LoadBalancer {
private index = 0;
select(instances: ServiceInstance[]): ServiceInstance {
const instance = instances[this.index % instances.length];
this.index++;
return instance;
}
}
// 重み付きラウンドロビン: 性能に応じて比率を変更
class WeightedRoundRobinLB implements LoadBalancer {
select(instances: ServiceInstance[]): ServiceInstance {
const totalWeight = instances.reduce((sum, i) => sum + i.metadata.weight, 0);
let random = Math.random() * totalWeight;
for (const instance of instances) {
random -= instance.metadata.weight;
if (random <= 0) return instance;
}
return instances[0];
}
}
// 最小接続数: 接続が少ないインスタンスを優先
class LeastConnectionsLB implements LoadBalancer {
private connections: Map<string, number> = new Map();
select(instances: ServiceInstance[]): ServiceInstance {
return instances.reduce((min, inst) => {
const count = this.connections.get(inst.id) || 0;
const minCount = this.connections.get(min.id) || 0;
return count < minCount ? inst : min;
});
}
}
アルゴリズム比較
| アルゴリズム | 特徴 | 適用場面 |
|---|---|---|
| ラウンドロビン | 均等分散、シンプル | 同一スペックのインスタンス |
| 重み付きラウンドロビン | 性能に応じた分散 | 異なるスペック混在 |
| 最小接続数 | 負荷に応じた動的分散 | 処理時間が不均一 |
| IPハッシュ | 同一クライアント→同一サーバー | セッション維持 |
まとめ
| ポイント | 内容 |
|---|---|
| サービスディスカバリ | 動的にサービスの位置を発見 |
| 2つの方式 | クライアントサイド / サーバーサイド |
| サービスレジストリ | 登録情報の中央管理 + ヘルスチェック |
| ロードバランシング | ラウンドロビン、重み付き、最小接続数 |
チェックリスト
- サービスディスカバリの必要性を説明できる
- クライアントサイドとサーバーサイドの違いを理解した
- サービスレジストリの仕組みを説明できる
- 3つ以上のロードバランシングアルゴリズムを挙げられる
次のステップへ
次は分散設定管理を学びます。数十のサービスの設定をどう一元管理し、安全に変更するかを理解しましょう。
推定読了時間: 30分