Netflix Ribbon 基本架构
作为一款客户端负载均衡工具,要做的事情无非就是两件:第一件事情是获取注册中心中的服务器列表;第二件事情是在这个服务列表中选择一个服务进行调用。
Netflix Ribbon 中的核心类
- ILoadBalancer
public interface ILoadBalancer {
//添加后端服务
//这里抽象成添加服务列表,因为Ribbon是一个工具,可以用来集成不限于Eureka
//1
public void addServers(List<Server> newServers);
//选择一个后端服务
//2
public Server chooseServer(Object key);
//标记一个服务不可用
public void markServerDown(Server server);
//获取当前可用的服务列表
public List<Server> getReachableServers();
//获取所有后端服务列表
public List<Server> getAllServers();
}
- IRule
IRule 接口是对负载均衡策略的一种抽象,可以通过实现这个接口来提供各种适用的负载均衡算法
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
- IPing
IPing 接口判断目标服务是否存活
public interface IPing {
public boolean isAlive(Server server);
}
4.LoadBalancerStats
LoadBalancerStats 类记录负载均衡的实时运行信息,用来作为负载均衡策略的运行时输入
BaseLoadBalancer 中ILoadBalancer 接口中 chooseServer 方法的实现
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//调用Irule接口
return rule.choose(key);
} catch (Exception e) {
return null;
}
}
}
Spring Cloud Netflix Ribbon
对于 Netflix Ribbon 组件而言,我们首先需要明确它提供的只是一个辅助工具,这个辅助工具的目的是让你去集成它,而不是说它自己完成所有的工作。而 Spring Cloud 中的 Spring Cloud Netflix Ribbon 就是就专门针对 Netflix Ribbon 提供了一个独立的集成实现。
如果让我们集成Netflix Ribbon,该怎么样做?
对于集成Eureka来说,可以通过Eureka客户端提供的的服务发现接口获取指定的服务列表,再看看上面的ILoadBalancer 接口,它有一个addServers方法,获取服务列表后就可以采用负载均衡策略调用某一个服务了。
@LoadBalanced 注解
为什么通过 @LoadBalanced 注解创建的 RestTemplate 就能自动具备客户端负载均衡的能力?
事实上,在 Spring Cloud Netflix Ribbon 中存在一个自动配置类——LoadBalancerAutoConfiguration 类。而在该类中,维护着一个被 @LoadBalanced 修饰的 RestTemplate 对象的列表。在初始化的过程中,对于所有被 @LoadBalanced 注解修饰的 RestTemplate,调用 RestTemplateCustomizer 的 customize 方法进行定制化,该定制化的过程就是对目标 RestTemplate 增加拦截器 LoadBalancerInterceptor。
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
重点在这个LoadBalancerInterceptor,
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(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,它在Spring-Cloud-Commons jar包下,是一个Spring Cloud关于负载均衡的抽象
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
其中一个实现类RibbonLoadBalancerClient 位于 spring-cloud-netflix-ribbon 工程中。这样我们的代码流程就从应用程序转入到了 Spring Cloud Netflix Ribbon 中
在 LoadBalancerClient 接口的实现类 RibbonLoadBalancerClient 中,choose 方法最终调用了如下所示的 getServer 方法:
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default");
}
这里的 loadBalancer 对象就是前面介绍的 Netflix Ribbon 中的 ILoadBalancer 接口的实现类。这样,我们就把 Spring Cloud Netflix Ribbon 与 Netflix Ribbon 的整体协作流程串联起来。