Ribbon源码学习

110 阅读4分钟

之前学习了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;
    }