Ribbon的调用流程
Ribbon在Netflix组件是非常重要的一个组件,在Zuul中使用Ribbon做负载均衡,以及Feign组件的结合等。在Spring Cloud 中,作为开发中,做的最多的可能是将RestTemplate和Ribbon相结合,你可能会这样写:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* @author WGR
* @create 2021/9/27 -- 17:01
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/save")
public String save(int productId) {
String str = restTemplate.getForObject("http://serviceA/product/find?id="+productId, String.class);
System.out.println(str);
return str;
}
}
当开始请求的时候,会发现2个serviceA的服务被轮流请求到,这个就让人很奇怪,到时是什么导致出现这种现状,只有可能是@LoadBalanced,点进去看一下,发现这个注解没有什么很特别的地方。那就去这个包下面看看,如果是spring cloud或者是spring boot相关的项目,一定会有一个XXAutoConfiguraiton的一个东西。
最终在LoadBalancerAutoConfiguration的类下,发现了一段奇怪的代码,对restTampleate进行了自定义的操作
当点点到自定义类的时候,就来到了下面,发现会把LoadBalancerInterceptor拦截器放到了restTemplate中。
ribbon中对应的实现类是LoadBalancerInterceptor
(不使用spring-retry
的情况下)具体源码如下:
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();
/**
*拦截请求,并调用loadBalancer.execute()方法
*在该方法内部完成server的选取。向选取的server
*发起请求,并获得返回结果。
*/
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
当执行execute的方法的时候,会来到RibbonLoadBalancerClient类中
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
return execute(serviceId, request, null);
}
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
/**
*创建loadBalancer的过程可以理解为组装选取服务的规则(IRule)、
*服务集群的列表(ServerList)、检验服务是否存活(IPing)等特性
*的过程(加载RibbonClientConfiguration这个配置类),需要注意
*的是这个过程并不是在启动时进行的,而是当有请求到来时才会处理。
*/
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
/**
* 根据ILoadBalancer来选取具体的一个Server。
* 选取的过程是根据IRule、IPing、ServerList
* 作为参照。
*/
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));
return execute(serviceId, ribbonServer, request);
}
方法的第一步就是获取ILoadBalancer,那我们就进去getLoadBalancer(serviceId)中看一下,是怎么获取到的。这个ILoadBalancer
是Ribbon的核心类。可以理解成它包含了选取服务的规则(IRule
)、服务集群的列表(ServerList
)、检验服务是否存活(IPing
)等特性,同时它也具有了根据这些特性从服务集群中选取具体一个服务的能力。
会发现是通过SpringClientFactory来获取对应的LoadBalancer,SpringClientFactory不是spring的包下的,是spring cloud与ribbon整合代码的包下的,SpringClientFactory不是spring的包下的,是spring cloud与ribbon整合代码的包下的。
比如说要获取这个ServiceA服务的LoadBalancer,那么就从ServiceCA服务对应的自己的ApplicationContext容器中去获取自己的LoadBalancer即可
如果是另外一个ServiceC服务,那么又是另外的一个SpringApplicationContext,然后从里面获取到的LoadBalancer都是自己的容器里的LoadBalancer
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
但是LoadBalancer只是一个接口,它有如下的实现类,到底默认的是哪一个呢?
如果你要找一个bean,要么就在XXAutoConfiguration里面找,要么就是在XXConfiguration里面找。。。spring cloud或者是spring boot的项目的一个特点,去找bean。
最终发现默认的是ZoneAwareLoadBalancer,下一遍在介绍它是怎么和eureka整合的获取注册表信息的。