之前学习了eureka源码,现在来看一下ribbon,ribbon主要用于分布式系统的负载均衡调用
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
就是用RestTemplate来访问其他一个服务,RestTemplate是http组件,本身没负载均衡功能,这里用了@LoadBalanced注解,底层就用了ribbon的负载均衡
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
注解看不出啥东西,spring boot和Spring cloud相关的先看XxxAutoConfiguration这种的。那来看下同包下的LoadBalancerAutoConfiguration.class 发现有个restTemplates,loadBalancedRestTemplateInitializerDeprecated()应该就是个初始化方法
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(
required = false
)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
public LoadBalancerAutoConfiguration() {
}
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> {
restTemplateCustomizers.ifAvailable((customizers) -> {
Iterator var2 = this.restTemplates.iterator();
while(var2.hasNext()) {
RestTemplate restTemplate = (RestTemplate)var2.next();
Iterator var4 = customizers.iterator();
while(var4.hasNext()) {
RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
customizer.customize(restTemplate);
}
}
});
};
}
来看下RestTemplateCustomizer,给restTemplate加了拦截器。
LoadBalancerInterceptor:originalUri就是restTemplate.getForObject()里面的url=http://ServiceA/sayHello,serviceName=ServiceA。
restTemplate.getForObject("http://ServiceA/sayHello",String.class);LoadBalancerInterceptor会拦截掉restTemplate的所有请求,有LoadBalancerClient.execute()去执行,传入的服务名serviceName应该会替换成对应的ip:port,例如 192.168.1.110:8080,最后的url肯定会是http://192.168.1.110:8080/sayHello
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
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));
}
下面来看看LoadBalancerClient,实现类RibbonLoadBalancerClient。
Server server = this.getServer(loadBalancer, hint);应该就是从server list中挑选出一个server
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
Server server = this.getServer(loadBalancer, hint);
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);
}
}
先来看看这个ILoadBalancer,是从服务ServiceA对应的实例中获取的,在RibbonClientConfiguration.class中创建,用的是ZoneAwareLoadBalancer这个实现类
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}
看看ZoneAwareLoadBalancer,在他父类DynamicServerListLoadBalancer的restOfInit()方法中找到了点东西:updateListOfServers(),更新server list。server list想必是从eureka client上获取,那ribbon是如何与eureka整合的呢?
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
this.setEnablePrimingConnections(false);
this.enableAndInitLearnNewServersFeature();
this.updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections().primeConnections(this.getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
带着疑问继续看下去,发现servers = this.serverListImpl.getUpdatedListOfServers(); 这个应该就是从eureka那边获取server list
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList();
if (this.serverListImpl != null) {
servers = this.serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
if (this.filter != null) {
servers = this.filter.getFilteredListOfServers((List)servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
}
}
this.updateAllServerList((List)servers);
}
在org.springframework.cloud.netflix.ribbon.eureka包下的EurekaRibbonClientConfiguration.class中找到,其实就是调用了DiscoveryEnabledNIWSServerList.getUpdatedListOfServers()这个方法
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
if (this.propertiesFactory.isSet(ServerList.class, this.serviceId)) {
return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.serviceId);
} else {
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
}
在DiscoveryEnabledNIWSServerList的obtainServersViaDiscovery()中终于找到了从eureka拉取server list的代码
EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
Iterator var8 = listOfInstanceInfo.iterator();
那么server list的更新是怎么做的呢?回头看看restOfInit中的this.enableAndInitLearnNewServersFeature();
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", this.serverListUpdater.getClass().getSimpleName());
this.serverListUpdater.start(this.updateAction);
}
在PollingServerListUpdater可以看到,创建了一个Runnable线程,执行UpdateAction(其实就是执行updateListOfServers()方法)在延迟一定的时间过后,每隔一定的时间就执行一下那个Runnable线程,就会执行UpdateAction中的操作来刷新注册表,从eureka client中获取注册表,然后刷新到LoadBalancer中去。。initialDelayMs=1s,refreshIntervalMs=30s,就是默认1s后第一次执行,后面每隔30s执行更新。
现在从eureka那边拉取server list和定时更新已经看完了,那拿到server list后应该就是从里面挑选出一个server了,也就是负载均衡的具体算法了
public synchronized void start(final UpdateAction updateAction) {
if (this.isActive.compareAndSet(false, true)) {
Runnable wrapperRunnable = new Runnable() {
public void run() {
if (!PollingServerListUpdater.this.isActive.get()) {
if (PollingServerListUpdater.this.scheduledFuture != null) {
PollingServerListUpdater.this.scheduledFuture.cancel(true);
}
} else {
try {
updateAction.doUpdate();
PollingServerListUpdater.this.lastUpdated = System.currentTimeMillis();
} catch (Exception var2) {
PollingServerListUpdater.logger.warn("Failed one update cycle", var2);
}
}
}
};
this.scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(wrapperRunnable, this.initialDelayMs, this.refreshIntervalMs, TimeUnit.MILLISECONDS);
} else {
logger.info("Already active, no-op");
}
}
public void doUpdate() {
DynamicServerListLoadBalancer.this.updateListOfServers();
}
ZoneAwareLoadBalancer.chooseServer()方法挑选出server,其实用的是rule.choose(),rule用的是RibbonClientConfiguraiton中实例化的一个ZoneAvoidanceRule,调用了他的choose()方法来选择一个server,其实是用的父类,PredicateBasedRule.choose()方法,先执行过滤规则,过滤掉一批server,根据你自己指定的filter规则,然后用round robin轮询算法,依次获取下一个server
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextIndex.get();
next = (current + 1) % modulo;
} while(!this.nextIndex.compareAndSet(current, next) || current >= modulo);
return current;
}
已经选出了server,下面的操作应该就是替换生成url,执行http请求
回到LoadBalancerInterceptor中,寻找request,最终找到了ServiceRequestWrapper(将LoadBalancerRequest和server再次封装成了一个Wrapper)HttpRequestServiceRequestWrapper里面的getURI()方法重写了,基于自己的逻辑重写了,这个里面,调用了RibbonLoadBalancerClient的reconstructURI()方法,基于选择出来的server的地址,重构了请求URI,然后调用底层的http组件执行请求
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer);
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance, this.getRequest().getURI());
return uri;
}