【深入理解SpringCloud微服务】Ribbon源码解析
Ribbon的原理
RestTemplate中的拦截器链
在深入理解Ribbon之前,我们先要研究一下RestTemplate。RestTemplate是Spring提供的一个用于发送http请求的工具类。而在RestTemplate的父类InterceptingHttpAccessor里面有这么个东西:
public abstract class InterceptingHttpAccessor extends HttpAccessor {
private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
...
}
interceptors是RestTemplate里面的拦截器,拦截器类型是ClientHttpRequestInterceptor。当我们使用RestTemplate时,在RestTemplate真正发出http请求之前,请求会经过interceptors这个拦截器链处理。于是,我们可以通过给RestTemplate添加拦截器,对RestTemplate即将发送的请求做各种拦截处理。
Ribbon的拦截器
比如Ribbon的拦截器做的处理就是url重构,url中的域名部分原先是某微服务的服务名,重构后替换成真正的ip端口。
当然完整的流程应该是先通过负载均衡策略选出一个服务实例,然后再根据该实例的ip和port重构url。
如何将拦截器放入到RestTemplate中
在RestTemplate的父类InterceptingHttpAccessor中有一个getInterceptors() 方法:
public List<ClientHttpRequestInterceptor> getInterceptors() {
return this.interceptors;
}
可以直接获取到它的拦截器链,于是我们可以调用restTemplate.getInterceptors()获取到拦截器链,然后调用list.add(interceptor)把我们的拦截器interceptor添加到RestTemplate的拦截器链中。
经过上面这一套动作,就可以把我们的拦截器添加到RestTemplate,但是这一套需要有一个时机以及地点去触发它。而Ribbon就利用了Spring提供的SmartInitializingSingleton这个扩展点去触发。
public interface SmartInitializingSingleton {
void afterSingletonsInstantiated();
}
Spring会在完成了所有非懒加载单例bean的初始化之后调用注册到Spring容器中的所有SmartInitializingSingleton的afterSingletonsInstantiated()方法。于是Ribbon实现了一个SmartInitializingSingleton并注册到Spring中,在afterSingletonsInstantiated()方法中把Ribbon自己的拦截器设置到RestTemplate中。
Ribbon中的核心类
Ribbon中的核心类一共就以下那么几个:
LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration是Ribbon的自动配置类,会被SpringBoot的自动装配机制加载。LoadBalancerAutoConfiguration利用Spring的扩展点SmartInitializingSingleton设置Ribbon自己的拦截器LoadBalancerInterceptor到RestTemplate的拦截器链中。
LoadBalancerInterceptor
LoadBalancerInterceptor是Ribbon的拦截器,会被添加到RestTemplate的拦截器链中。当调用RestTemplate发起http请求时,请求会经过该拦截器进行负载均衡以及url重写的处理。
LoadBalancerClient
LoadBalancerClient是Ribbon的负载均衡客户端,会被LoadBalancerInterceptor调用,负载均衡以及url重写的工作其实是由LoadBalancerClient完成的。
当LoadBalancerInterceptor拿到原始url时,会从url中取出服务名serviceName(也就是域名部分),然后把serviceName传给LoadBalancerClient。LoadBalancerClient根据serviceName取得对应的ILoadBalancer,调用ILoadBalancer进行负载均衡处理,ILoadBalancer返回负载均衡选出的一个服务实例并返回。LoadBalancerClient拿到返回的服务实例,会把url中的域名(也就是服务名serviceName)改写为该实例的ip和port。
ILoadBalancer
ILoadBalancer是Ribbon的负载均衡器,ILoadBalancer是与服务名一一对应的,也就是每一个服务名对应一个ILoadBalancer,不同服务间的ILoadBalancer互不相干。LoadBalancerClient会通过服务名作为key从loadBalancerMap中获取对应的ILoadBalancer负载均衡器,通过ILoadBalancer的负载均衡选出一个服务实例。
在ILoadBalancer的实现类BaseLoadBalancer有四个比较重要的字段:
- List<Server> allServerList:所有的服务实例
- List<Server> upServerList:在线的服务实例
- IRule rule:负载均衡策略
- IPing ping:用于ping一个服务实例以检查是否存活的工具
ILoadBalancer使用IRule负载均衡策略从upServerList选出一个服务实例Server。然后ILoadBalancer内部有个定时任务通过IPing去ping在allServerList中的每个服务实例,得出所有在线的Server,然后更新upServerList。
然后在BaseLoadBalancer的子类DynamicServerListLoadBalancer中还有一个重要的字段:
- ServerList<T> serverListImpl:向注册中心拉取服务实例列表的工具
DynamicServerListLoadBalancer有一个定时任务从调用serverListImpl拉取最新的服务实例列表,更新到allServerList中。
于是这ILoadBalancer中的这五者就是以下这样的关系:
ServerList
ServerList是一个用于从注册中心拉取服务实例列表的接口,不同的注册中心客户端会有不同的ServerList实现,比如nacos的NacosServerList就是通过NamingService去拉取。
IRule
IRule是Ribbon的负载均衡规则接口,不同的负载均衡策略有不同的IRule实现。
- RandomRule:随机选择一个Server
- RoundRobinRule:轮询选择
- WeightedResponseTimeRule:根据响应时间加权的轮询策略,响应时间越长,权重越小
- BestAvailableRule:最小并发优先策略,在众多Server中选取并发数最小的
- ZoneAvoidanceRule:默认的负载均衡策略,在有区域的环境下,会复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,就是轮询,因此默认的负载均衡策略就是轮询
IPing
Iping接口就是用于判断一个Server是否可用的接口,有一个boolean isAlive(Server server)方法需要实现,判断当前这个server是否可用。
Ribbon核心源码解析
LoadBalancerAutoConfiguration
@Configuration(...)
...
public class LoadBalancerAutoConfiguration {
// 把被@LoadBalanced注解修饰的RestTemplate通过Spring自动注入的方式收集到这里
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
...
// 通过Spring提供的SmartInitializingSingleton扩展点,
// 调用RestTemplateCustomizer把LoadBalancerInterceptor放入RestTemplate的拦截器链中
@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);
}
}
});
}
...
// Ribbon的负载均衡拦截器,会被放入到RestTemplate的拦截器链中
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// Ribbon通过RestTemplateCustomizer
// 把LoadBalancerInterceptor放入到RestTemplate的拦截器链中
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
...
}
LoadBalancerAutoConfiguration中配置了许多bean,但是重点就是以上三个。LoadBalancerAutoConfiguration通过Spring的扩展点SmartInitializingSingleton调用RestTemplateCustomizer,通过RestTemplateCustomizer把LoadBalancerInterceptor放入到RestTemplate的拦截器链中。
LoadBalancerInterceptor
RestTemplate的拦截器链中有了LoadBalancerInterceptor之后,当我们使用RestTemplate发起http请求时,就会经过LoadBalancerInterceptor的拦截处理。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
// 负载均衡客户端
private LoadBalancerClient loadBalancer;
// Request对象工厂
private LoadBalancerRequestFactory requestFactory;
...
// RestTemplate发起http请求,会经过LoadBalancerInterceptor的intercept方法拦截处理
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
// LoadBalancerInterceptor的intercept方法,调用LoadBalancerClient的execute方法。
// 传入了服务名serviceName,requestFactory创建的Request对象
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
RestTemplate发起的http请求会被LoadBalancerInterceptor的intercept方法拦截,LoadBalancerInterceptor的intercept方法从url中取出域名部分作为服务名serviceName,然后调用LoadBalancerClient的execute方法。
RibbonLoadBalancerClient
LoadBalancerClient接口的实现类是RibbonLoadBalancerClient。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
...
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 根据服务名(serviceId就是服务名),取得对应的ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// getServer方法里面通过ILoadBalancer选出一个服务实例Server
Server server = getServer(loadBalancer, hint);
...
// Server包装为RibbonServer
RibbonServer ribbonServer = new RibbonServer(serviceId, server, ...);
// 调用execute方法(重写url,发起请求)
return execute(serviceId, ribbonServer, request);
}
...
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
...
// 调用LoadBalancerRequest的apply方法(重写url,发起请求)
T returnVal = request.apply(serviceInstance);
...
}
...
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
// 调用ILoadBalancer的chooseServer方法选取一个服务实例
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
...
}
RibbonLoadBalancerClient首先调用getLoadBalancer(serviceId),根据服务名(serviceId就是服务名),取得对应的ILoadBalancer。然后通过ILoadBalancer的chooseServer方法选取一个服务实例Server。最后选出的服务实例作为参数,调用LoadBalancerRequest的apply方法进行下一步操作(重写url,发起请求)
ILoadBalancer
我们看看ILoadBalancer中的chooseServer方法是如何选出实例的。默认的ILoadBalancer就是ZoneAwareLoadBalancer,由于我们一般不会配置zone(区域),所以最后还是会进入父类BaseLoadBalancer的chooseServer方法。
public Server chooseServer(Object key) {
...
return rule.choose(key);
...
}
就是调用IRule的choose方法,默认就是轮询策略。
request.apply(serviceInstance)
我们再看看上面request.apply(serviceInstance)这行代码里面的逻辑。
这里的request是LoadBalancerRequestFactory的createRequest创建的。
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
// 以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
...
return execution.execute(serviceRequest, body);
};
}
可以看到LoadBalancerRequestFactory返回的request对象的apply方法里面,以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象。然后调用execution.execute(serviceRequest, body)方法。
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
...
// request.getURI()调用到ServiceRequestWrapper的getURI()方法,里面会重写url,然后返回一个重写了url的URI对象
// 然后这里的requestFactory(ClientHttpRequestFactory类型)拿着重写后的URI对象,创建一个新的ClientHttpRequest对象
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
...
// 调用ClientHttpRequest的execute()方法真正发起请求,返回ClientHttpResponse(响应结果)
return delegate.execute();
}
我们看一下ServiceRequestWrapper的getURI()方法。
@Override
public URI getURI() {
// 调用LoadBalancerClient的reconstructURI方法(这里的loadBalancer是LoadBalancerClient类型)
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
所以总结起来request.apply(serviceInstance)里面就是调用LoadBalancerClient的reconstructURI方法重写url,然后调用ClientHttpRequestFactory的createRequest方法重新创建一个Request对象,然后调用该Request对象的execute()方法真正发起http请求。
标签:...,拦截器,ILoadBalancer,SpringCloud,RestTemplate,源码,LoadBalancerInterceptor,Ribbon From: https://blog.csdn.net/weixin_43889578/article/details/136691829