前言:Eureka的服务发现是基于服务名称获取服务列表,然后在对服务列表做负载均衡。那么,这其中负载均衡的过程原理又是什么呢?
在使用RestTemplate发起请求需要在RestTemplate上添加@LoadBalanced注解,这个注解发起的请求会被Ribbon的拦截器给拦截和处理。这个拦截器为LoadBalancerInterceptor,其实现了ClientHttpRequestInterceptor。
LoadBalancerInterceptor源码如下:
package org.springframework.cloud.client.loadbalancer;
import ...
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
//ClientHttpRequestInterceptor的实现方法
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
而这个ClientHttpRequestInterceptor接口它会拦截客户端HTTP请求。而RestTemplate正好是个发送HTTP请求的客户端,所以就会被ClientHttpRequestInterceptor拦截。注释翻译过来大概意思就是:(可以使用RestTemplate注册此接口的实现,以修改传出的ClientHttpRequest和/或传入的ClientHttPreponse。)
ClientHttpRequestInterceptor源码如下:
package org.springframework.http.client;
import ...
/**
Intercepts client-side HTTP requests.
Implementations of this interface can be registered with the RestTemplate,
as to modify the outgoing ClientHttpRequest and/or the incoming ClientHttpResponse.
*/
@FunctionalInterface
public interface ClientHttpRequestInterceptor {
ClientHttpResponse intercept(HttpRequest var1, byte[] var2, ClientHttpRequestExecution var3) throws IOException;
}
ClientHttpRequestInterceptor是个接口,接口里面定义了一个方法叫interceptor。上文提到过LoadBalancerInterceptor实现了ClientHttpRequestInterceptor,LoadBalancerInterceptor那么一定会实现interceptor方法。
所以当Ribbon收到HTTP请求后就会被LoadBalancerInterceptor的interceptor方法给拦截。
interceptor方法拦截后会获取请求地址→根据刚才获取的地址获取服务名称→判断服务名称是否为空,如果为空则抛出异常;不为空则执行并返回excute方法。
就是怎么个玩意儿↓
//ClientHttpRequestInterceptor的实现方法
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();//获取请求地址
String serviceName = originalUri.getHost();//根据刚才获取的地址获取服务名称
//断言
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);//判断服务名称是否为空,如果为空则抛出异常
//不为空则执行并返回excute方法
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
而这个excute方法为RibbonLoadBalancerClient的方法,执行这个方法会获得一个LoadBalancer对象,这个对象的名字叫DynamicServerListLoadBalancer(动态服务列表负载均衡器)它里边有个allServerList来保存目前拉取到的服务列表(根据服务名称找Eureka获取服务列表)然后根据getServer方法选择一个服务。
package org.springframework.cloud.netflix.ribbon;
import...
public class RibbonLoadBalancerClient implements LoadBalancerClient {
//这上头还有若干个方法
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);//根据服务名称找Eureka获取服务列表
Server server = this.getServer(loadBalancer, hint);//根据getServer方法选择一个服务
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
//下边也是
}
这个getServer也是RibbonLoadBalancerClient的方法,其会执行并返回loadBalancer.chooseServer方法。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
//这上头还有若干个方法
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
//下边也是
}
而这个chooseServer方法为zoneAwareLoadBalancer的方法,它调用了super的chooseServer方法。而BaseLoadBalancer的chooseServer方法调用并返回rule的choose方法选择一个负载均衡算法来挑选服务。默认算法为ZoneAvoidanceRule,即服务消费者会优先选择和自己同一个区域的服务,然后在对这些服务进行轮询。
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnectionListener, IClientConfigAware {
public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
return this.rule.choose(key);//选择一个负载均衡算法
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
}
rule是个IRule类型的对象,IRule顾名思义是个接口,他有很多的实现类。
常见规则详解:
最后,在选择一个算法后通过层层返回,回到RibbonLoadBalancerClient,用拿到服务地址替换原来的服务名称地址。如原来的localhost://userservice/user/1 转换为 localhost://8001/userservice/1
过程图例