本文作者:陈刚,叩丁狼高级讲师。原创文章,转载请注明出处。
本文章会通过断点跟踪的方式来解读 Ribbon 源码 ,希望同学们把文章看完之后不仅能够了解 Ribbon的实现原理,还能掌握源码解读的方式和技巧(重要)。
回顾
回顾一下我们的 Ribbon部分内容我们当时使用TestTemplate + LoadBalanced 做了这样的一个案例
叩丁狼教育.png
当时我们在配置类中做了如下Bean的定义去开启了RestTemplate的负载均衡功能
//通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。
//RestTemplate是spring内置的http请求封装
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
然后在Consumer中去请求Producer服务(当然会同时开启多个Producer服务)
//访问 PRODUCER 服务
String result =
restTemplate.getForObject("http://PRODUCER/provide?name="+name,String.class);
而我们需要达到的效果是该请求多次调用会从不同的Producer服务获取到结果(根据负载均衡规则),然而我们发的请求始终只会有一个呀,那么它要如何才能实现服务之间的切换调用呢?那么猜想一下,Ribbon是不是需要需要先拦截到我们的请求,然后根据我们定义的负载均衡算法,然后从服务清单中去选择合适的服务实例,然后完成调用呢???(思考一下)
那么接下来我们就来对这样的一个请求进行源码追踪分析。
@LoadBalanced
我们先来研究一下@LoadBalanced是一个什么东西,查看他的源码如下
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
注释 “Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient” 告诉我们:@LoadBalanced标签是用来给RestTemplate标记,以使用LoadBalancerClient(负载均衡的客户端)来配置它。
我们继续追踪 LoadBalancerClient的源码
/**
* Represents a client side load balancer
* @author Spencer Gibb
*/
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
* execute request using a ServiceInstance from the LoadBalancer for the specified
* service
* @param serviceId the service id to look up the LoadBalancer
* @param request allows implementations to execute pre and post actions such as
* incrementing metrics
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* execute request using a ServiceInstance from the LoadBalancer for the specified
* service
* @param serviceId the service id to look up the LoadBalancer
* @param serviceInstance the service to execute the request to
* @param request allows implementations to execute pre and post actions such as
* incrementing metrics
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
/**
* Create a proper URI with a real host and port for systems to utilize.
* Some systems use a URI with the logical serivce name as the host,
* such as http://myservice/path/to/service. This will replace the
* service name with the host:port from the ServiceInstance.
* @param instance
* @param original a URI with the host as a logical service name
* @return a reconstructed URI
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
注释“Represents a client side load balancer”表示该接口它是一个客户端负载均衡器 ,提供了几个方法,翻译方法上的注释得知他们的作用大致如下
execute : 使用LoadBalancer中的ServiceInstance为指定的服务执行请求,说白了就是通过它来实现服务的请求调用。
reconstructURI:使用真实主机和端口创建适当的URI以供系统使用,获取要调用的服务的主机和端口
并且该接口它继承ServiceInstanceChooser接口
/**
* Implemented by classes which use a load balancer to choose a server to
* send a request to.
*
* @author Ryan Baxter
*/
public interface ServiceInstanceChooser {
/**
* Choose a ServiceInstance from the LoadBalancer for the specified service
* @param serviceId the service id to look up the LoadBalancer
* @return a ServiceInstance that matches the serviceId
*/
ServiceInstance choose(String serviceId);
}
翻译接口上的注释“ Implemented by classes which use a load balancer to choose a server to send a request to.”大致意思为: 使用负载均衡器选择一个服务,然后去发起请求,而 choose方法的大致作用为:从LoadBalancer负载均衡器中为指定的服务(serviceId)选择一个服务实例(ServiceInstance) ,其实到这里我们大致已经明白了LoadBalancerClient的目的,就是通过choose方法选择要调用的服务实例,通过reconstructURI获取服务和主机和端口,然后通过execute执行服务的调用,而 RibbonLoadBalancerClient是对 LoadBalancerClient 的实现 ,他们的层级关系如下(idea中按ctrl+alt+u查看):
image.png
那么LoadBalancer到底是怎么让RestTtemplate 实现负载均衡的呢?要揭露这个答案我们必须得跟踪RestTemplate的的请求,断点一下RestTemplate服务调用的代码,然后去浏览器请求该方法触发断点,看看底层是如何实现调用的
@RequestMapping("/consumer")
public String consumer(@RequestParam("name") String name){
//访问 PRODUCER 服务
String result = restTemplate.getForObject("http://PRODUCER/provide?name="+name,String.class);
return result;
}
跟踪 getForObject 方法进入
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
这里继续调用了RestTemplate.execute 方法,并且把调用的服务地址传入进去,然后使用HttpMethod.GET方式进行调用,继续跟踪下去
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNul