在上篇文章中,我们了解了Eureka客户端通过两个定时任务去从Eureka服务上获取服务列表信息和心跳,而且默认30秒进行一次服务列表和心跳检测,如果一次获取列表超时了,就会将默认的30秒扩大一倍,并与扩容上限300(30*10)秒对比,也就是默认心跳时间间隔与最大容许的时间间隔。
获取服务列表之后,Eureka客户端就将配置信息设置到localRegionApps中。
发现是一个原子引用的数据类型。问题是Ribbon是如何调用获取这个localReginApps的?我们先不从这个角度来思考这个问题了,我直接看发起服务调用的@LoadBalance吧
发现里边什么也没有,这只能说明@LoadBalance仅仅是一个标志。这个标志。我们跟随RestTemplate的getForObject方法进入。根据代码逻辑我们知道这里的createRequest就是我们ribbon神出鬼没的地方。
通过上边的分析,我们知道这里的createRequest应该是RibbonClinentHttpReqeustFactory,至于最上边的默认值应该会被set方法覆盖掉。意思说只要我们配置了ribbon就可以调用set方法。我们看看是谁调的set方法。果然是AutoConfig
按照一般规律都是先走虚方法,所以我们先通过虚方法的createRequest看看做了哪些事情。最后找到下图中的代码,发现是个拦截器之类的。
那么显然这个拦截器应该应该已经注入到Spring上下文中了。我们看看AutoConfig类;
咋继续看我们的拦截器的方法。发现最终走的是拦截器的intercept方法。
继续跟踪
就这样进入了Ribbon
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
//通过服务名称获取负载均衡策略
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//通过策略获取一个真正的可用的服务地址
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);
}
@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();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
通过代码跟踪,发现在获取服务的负载均衡策略的时候,是通过服务名称到Spring上下文中获取的,获取不到就通过这个configproperties获取的,拿到这个之后去创建。然后刷新一下容器。意思就是说这里是从配置文件获取的,跟踪了一下。在AutoConfiguration中注入的。具体的注解为@RibbonClients,其在初始化的具有value和configuration的参数,其中value就是你要调用的服务的名称,而configuration则是你要调用服务的一些对应策略的class。默认会添加default前缀。但是如果我们指定了@RibbonClients的value和configuration则不会有前缀。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RibbonClientSpecification implements NamedContextFactory.Specification {
// 命名的上下文工厂,可以理解为可命名的内置容器工厂,默认容器的key为default
private String name;
private Class<?>[] configuration;
}
所以在项目中,如果需要对不同服务设置不同的负载策略可以通过@RibbonClients指定。并要在其configuration中指定配置的class文件。
而在初始化调用的时候也会通过调用的服务名称来和该服务对应的负载配置来注册该服务的ribbon的策略,并注册到contexts中。下次调用的时候直接从这里的contexts中获取。注意这里的contexts是一个小型的Spring上下文。
然后会返回已经初始化的对应的策略。获取该配置的负载策略器。
这里通过Map获取相应的负载均衡器。但是我们并没有设置这个呀,到底是那块设置的。
而在这里的get和IsSet都是采用name+rabbion+类路径获取。然后按照每个微服务的名称+rabbion+对应的策略名称注册到spring容器中。
拿到负载策略器之后就开始获取具体的路由地址了。
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
如下图所示,eureka默认的选择服务的方式。返回的server包括ip端口等信息。getLoadBalancerStats方法用于返回可用的服务列表。
这块就又有疑问了,上次说是获取的服务信息都存储到了Applications中了。他是一个原子安全的Reference。现在是Server,这两个是怎么整合到一起的?这个问题下一次再研究吧!