图片来自网络
Spring cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它是基于Netflix的Ribbon实现的。Ribbon是客户端负载均衡器,这有别语例如Nginx服务端负载均衡器。Ribbon本身提供了不通负载均衡策略使用不通的应用场景。
Ribbon 源代码分析
批注:测试用例可以参考前面的文章 Spring Cloud Eureka 概念与示例, 可以将断点设置到本文中提到的类的方法中
1.RestTemplate
1.1 @LoadBalanced注解
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* 为RestTemplate 声明一个标记,用于使用负载均衡的客户端LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
从@LoadBalanced 代码的注解可以知道,这个注解用来给RestTemplate 做一个标记,用于使用负载均衡的客户端LoadBalancerClient ,查看LoadBalancerClient源码,LoadBalancerClient接口定义继承接口ServiceInstanceChooser共有4个方法
- ServiceInstance choose(String serviceId);根据传入的服务名serviceId,从负载均衡器中挑选一个对应服务的实例
- <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;根据serviceId使用从负载均衡器中挑选出的服务实例来执行请求内容
- <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;根据serviceId使用从负载均衡器中挑选出的服务实例来执行请求内容
- URI reconstructURI(ServiceInstance instance, URI original);构建一个合适的host:port形式的URI,在分布式系统中,使用逻辑上的服务u名称作为host来构建URI进行请求
1.2 LoadBalancerAutoConfiguration对象
LoadBalancerAutoConfiguration 是为了实现客户端负载均衡器的自动化配置类,查看源码会知道其创建了一个LoadBalanceInterceptor的Bea,用于实现对客户端发起请求时拦截,拦截时实现客户端负载均衡
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
//1.获取已有拦截器
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
//2.向拦截器结合中增加负载均衡拦截器
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
1.3 RestTemplate调用流程图
RestTemplate调用LoadBalanceInterceptor 实现负载的调用流程程如下图所示
2. LoadBalanceIntercepter
2.1 LoadBalanceIntercepter对象
下面,再看LoadBalanceIntercepter对象,对象中LoadBalanceClient是一个抽象的负载均衡接口,此接口只有一个实现类RibbonLoadBalancerClient,可见是调用RibbonLoadBalancerClient#execute实现的负载均衡
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
//略
@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);
//RibbonLoadBalancerClient 实现了loadBalancer接口
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
2.2 RibbonLoadBalancerClient
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
//略
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
//通过serviceId 例如 MS-CUSTOMER 获取ZoneAwareLoadBalancer
//ZoneAwareLoadBalancer是接口ILoadBalancer 的实现类
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//再通过ZoneAwareLoadBalancer获取负载均衡的服务
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));
// 最后调用同名重载方法实现request.apply
return execute(serviceId, ribbonServer, request);
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
//...
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
//。。。
}
}
RibbonLoadBalancerClient处理的流程如下
第一步:先通过通过serviceId 获取负载均衡器,ILoadBalancer接口定义了负载均衡器操作, 通过RibbonClientConfiguration配置类我们知道,整合Ribbon的时候默认使用的ILoadBalancer接口的实现是ZoneAwareLoadBalancer
第二步:再通过ZoneAwareLoadBalancer获取负载均衡后的服务
第三步: 将服务等信息封装成RibbonServer对象,RibbonServer对象是serviceInstance接口的实现,serviceInstance表示发现的系统中的某个服务实例其定义了服务智力系统中serviceId,host,port等信息。然后调用重载方法excute方法执行request.apply,apply方法传入的参数serviceInstance即上面封装的的RibbonServer对象
2.3 ILoadBalancer接口
该接口定义了负载均衡器的抽象操作,当前有六中操作,详细信息如下。通过
// ILoadBalancer 接口定义
public interface ILoadBalancer {
//向负载均衡器维护的实例表中增加一组服务
public void addServers(List<Server> newServers);
//根据传入的策略,从负载均衡器中挑选出一个具体的服务实例
public Server chooseServer(Object key);
//通知指定的服务已经停止
public void markServerDown(Server server);
//获取有效的服务列表,
//当参数=true时候,获取所有有效服务等同getReachableServers
//当参数=false时候,获取所有服务等同getAllServers
@deprecated 2016-01-20此方法已被弃用
public List<Server> getServerList(boolean availableOnly);
//获取当前正常的服务列表
public List<Server> getReachableServers();
//获取已知道的全部服务包括正常状态的服务和停止状态的服务
public List<Server> getAllServers();
}
2.4 ZoneAwareLoadBalancer
下面看看ZoneAwareLoadBalancer是如何实现的负载均衡
- 当只有1个有效Zones的时候通过ZoneAvoidanceRule规则该规则根据区域和可用性规则来获取服务
- 当有多个Zone的时候,通过RoundRobinRule规则获取
public Server chooseServer(Object key) {
//1. 当只有1个有效Zones的时候通过ZoneAvoidanceRule规则
//该规则根据区域和可用性规则来获取服务
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
//2. 通过RoundRobinRule规则获取
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
2.5 LoadBalancerRequest.apply回调方法
LoadBalancerRequest 对象是LoadBalanceIntercepter#intercept处理时候,通过requestFactory构建并返回的对象, apply函数处理的时候,将httpRequest对象封装成了ServiceRequestWapper对象,ServiceRequestWapper 对象重写了getURI方法
public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution execution) {
return new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(final ServiceInstance instance)
throws Exception {
//将HttpRequest和ServiceInstance封装成新对象
//ServiceRequestWrapper重写getURI方法,通过LoadBalanceClient获取推荐的URI
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
//略部分代码
return execution.execute(serviceRequest, body);
}
};
}
public class ServiceRequestWrapper extends HttpRequestWrapper {
//略部分代码
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(
this.instance, getRequest().getURI());
return uri;
}
}
2.6 负载处理流程图
拦截器处理流程图如下
文章总结
当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求的时候,会被LoadBalanceerIntercepor类拦截。由于我们在使用RestTemplate时采用了服务名为host,所以直接从HttpRequest的URI对象中getHost拿到服务名称,然后通过RibbonLoadBalancerClient的 execute函数实现负载调用客户端
负载均衡器RibbonLoadBalancerClient处理的时候, 先根据服务名和规则获取到满足条件的客户端服务信息ServiceInstance,再通过LoadBalancerRequest.apply(ServiceInstance instance)回调方法将HttpRequest请求封装为ServiceRequestWrapper对象, ServiceRequestWrapper重写getURI方法得到负载均衡器LoadBalancerClient推荐ServiceInstance指定的客户端地址