Ribbonは、Netflixが開発したオープンソースのロードバランサーコンポーネントで、サービス消費者側にロードバランサーのロジックを組み込むことで、サーバーへのリクエストを分散します。
Ribbonはデフォルトで7つのロードバランシング戦略を提供しています:
- BestAvailableRule:最も少ないリクエストを受けているサーバーを選択
- ClientConfigEnabledRoundRobinRule:ラウンドロビン方式
- RandomRule:ランダムにサーバーを選択
- RoundRobinRule:ラウンドロビン方式
- RetryRule:ラウンドロビン方式でリトライ
- WeightedResponseTimeRule:応答時間が短いほど選択される確率が高い
- ZoneAvoidanceRule:ゾーンと可用性に基づいてラウンドロビン
Ribbonの実装原理
@LoadBalancedアノテーション
Ribbonを使用するには、まず依存関係を追加します。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
次に、RestTemplateインスタンスをインジェクトする際に@LoadBalancedアノテーションを付与します。
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
@LoadBalancedアノテーション自体にはロジックは含まれていませんが、LoadBalancerAutoConfigurationクラスで処理されます。
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
このコードでは、@LoadBalancedアノテーションが付けられたRestTemplateインスタンスを取得し、RestTemplateCustomizerを使ってカスタマイズを行います。
LoadBalancerInterceptor
LoadBalancerInterceptorクラスは、RestTemplateのリクエストをインターセプトしてロードバランシングを行います。
private LoadBalancerClient loadBalancer;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
LoadBalancerClientの実装クラスであるRibbonLoadBalancerClientのexecuteメソッドが呼び出されます。
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
ILoadBalancer
ILoadBalancerインターフェースは、ロードバランシングに関する操作を定義しています。
public interface ILoadBalancer {
void addServers(List<Server> newServers);
Server chooseServer(Object key);
void markServerDown(Server server);
List<Server> getReachableServers();
List<Server> getAllServers();
}
DynamicServerListLoadBalancerクラスは、ILoadBalancerの実装クラスで、Eurekaからサービスリストを取得し、定期的にサーバーの状態をチェックします。
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
サーバーリストが取得されると、IRuleインターフェースの実装クラスによって適切なサーバーを選択します。
public interface IRule {
Server choose(Object key);
void setLoadBalancer(ILoadBalancer lb);
ILoadBalancer getLoadBalancer();
}
ロードバランサーコンポーネントの比較
ロードバランサーはシステムの可用性を向上させる重要なコンポーネントです。主に、独立型とクライアント統合型の2つの実装方式があります。
- 独立型: クライアントがロードバランサーを介してリクエストを送信します。ロードバランサーがリクエストを適切なサーバーにルーティングします。
- クライアント統合型: クライアント自身がロードバランサーのロジックを持ち、ローカルでサーバーリストを維持します。
Ribbonはクライアント統合型のロードバランサーですが、nginxやSLBなども独立型の代表的なロードバランサーです。