Ribbon源碼分析(二)-- 服務列表的獲取和負載均衡算法分析


上一篇博客(https://www.cnblogs.com/yangxiaohui227/p/12614343.html)分享了ribbon如何實現對http://product/info/這個鏈接重構為http://ip:端口/info/的過程

本次來分析如何通過服務名稱獲取服務列表以及通過服務列表如何負載均衡獲取一個服務的過程

 

一.服務列表獲取過程調試:

 

 

 

 

 

 

 

 小結:通過SpringClientFactory類的獲取ILoadBalancer的方法跟蹤,發現最終調用了起父類的getInstance的方法,詳細流程如下:

 接着我們跟進看看是如何通過服務名稱獲spring容器的:

 

 

 

map集合的結構如下:

 

 

 下面我們看看spring容器是如何創建的:

 

 我們回顧下,沒學springboot之前,我們啟動一個spring項目的寫法:

 

 由此可見,ribbon中創建spring容器和我們之前創建spring容器的方式都是一樣的:

問題:SpringClientFactory這個類是什么時候被初始化的?容器中注冊的類是哪些,服務名稱存到了環境變量中,那么屬性key是啥?

SpringClientFactory初始化時機:

上篇ribbon源碼提到springboot的自動裝配機制會實例化META-INF\spring.factories文件中配置的類

 

 

 

 初始化SpringClientFactory會調用構造方法:

 

 調用了父類的初始化方法:

 

 此時我們再回來看看spring容器創建的方法:

    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //省略部分代碼
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType); //這里注冊的類是:RibbonClientConfiguration.class 
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,//ribbon
                Collections.<String, Object>singletonMap(this.propertyName, name))); //屬性名稱為:ribbon.client.name
        //省略部分代碼
        context.refresh();
        return context;
    }

 從上面分析可以看到:每個服務都有一個spring容器,每個容器都會注冊一個RibbonClientConfiguration.class配置類,同時將服務名稱作為存到環境變量中,以便初始化RibbonClientConfiguration配置類相關的bean時,可以獲取相應的環境變量

所以,重點的類就是RibbonClientConfiguration.class:

@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
        RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {

    /**
     * Ribbon client default connect timeout.
     */
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

    /**
     * Ribbon client default read timeout.
     */
    public static final int DEFAULT_READ_TIMEOUT = 1000;

    /**
     * Ribbon client default Gzip Payload flag.
     */
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;

    @RibbonClientName    //該注解的作用可以在環境變量中獲取到服務名稱,上面提過,創建spring容器時,已經將服務名稱存到環境變量中了
    private String name = "client";

    @Autowired
    private PropertiesFactory propertiesFactory;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
        config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
        config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
        return config;
    }

    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {     //這里就是我們以后要講得負載均衡相關的bean了
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }

    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) { //這個是用來檢測服務列表中的服務還有沒活着
        if (this.propertiesFactory.isSet(IPing.class, name)) {
            return this.propertiesFactory.get(IPing.class, config, name);
        }
        return new DummyPing();
    }

    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("unchecked")
    public ServerList<Server> ribbonServerList(IClientConfig config) { //服務列表相關的類
        if (this.propertiesFactory.isSet(ServerList.class, name)) {
            return this.propertiesFactory.get(ServerList.class, config, name);
        }
        ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
        serverList.initWithNiwsConfig(config);
        return serverList;
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { //這個是更新服務列表相關的bean
        return new PollingServerListUpdater(config);
    }

    @Bean
    @ConditionalOnMissingBean                                           
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config,                             //這個bean就是我們要找的bean了,我們上面提到獲取服務列表就是從這個類中獲取的
            ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
            IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
            return this.propertiesFactory.get(ILoadBalancer.class, config, name);
        }
        return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,     
                serverListFilter, serverListUpdater);
    }

    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("unchecked")
    public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
            return this.propertiesFactory.get(ServerListFilter.class, config, name);
        }
        ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
        filter.initWithNiwsConfig(config);
        return filter;
    }

    @Bean
    @ConditionalOnMissingBean
    public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
            IClientConfig config, RetryHandler retryHandler) {
        return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
    }

    @Bean
    @ConditionalOnMissingBean
    public RetryHandler retryHandler(IClientConfig config) {
        return new DefaultLoadBalancerRetryHandler(config);
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerIntrospector serverIntrospector() {
        return new DefaultServerIntrospector();
    }

    @PostConstruct
    public void preprocess() {
        setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
    }

    static class OverrideRestClient extends RestClient {

        private IClientConfig config;

        private ServerIntrospector serverIntrospector;

        protected OverrideRestClient(IClientConfig config,
                ServerIntrospector serverIntrospector) {
            super();
            this.config = config;
            this.serverIntrospector = serverIntrospector;
            initWithNiwsConfig(this.config);
        }

        @Override
        public URI reconstructURIWithServer(Server server, URI original) {
            URI uri = updateToSecureConnectionIfNeeded(original, this.config,
                    this.serverIntrospector, server);
            return super.reconstructURIWithServer(server, uri);
        }

        @Override
        protected Client apacheHttpClientSpecificInitialization() {
            ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
            apache.getClientHandler().getHttpClient().getParams().setParameter(
                    ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
            return apache;
        }

    }

}

 

 

至此,我們知道了如何通過一個服務名稱獲取服務列表的過程,那么問題來了,服務列表是什么時候存到ZoneAwareLoadBalancer類中的呢?

先看看該類的繼承體系:

 

 

下面我們跟進ZoneAwareLoadBalancer的初始化過程,看看做了些什么事情:

 

 

 

 

 先看super(clientConfig, rule, ping);

 

 

 

 

 

 

 

 

 

 

public void runPinger() throws Exception {
        if (!pingInProgress.compareAndSet(false, true)) {
            return; // Ping in progress - nothing to do
        }
        Server[] allServers = null;
        boolean[] results = null;

        Lock allLock = null;
        Lock upLock = null;
        try {
            allLock = allServerLock.readLock();
            allLock.lock();
            allServers = allServerList.toArray(new Server[allServerList.size()]);
            allLock.unlock();

            int numCandidates = allServers.length;
            results = pingerStrategy.pingServers(ping, allServers); //所有的服務都去ping一下看看有沒存活
            final List<Server> newUpList = new ArrayList<Server>(); //還活着的服務
            final List<Server> changedServers = new ArrayList<Server>(); //有變化服務
            for (int i = 0; i < numCandidates; i++) {
                boolean isAlive = results[i];
                Server svr = allServers[i];
                boolean oldIsAlive = svr.isAlive();
                svr.setAlive(isAlive);
                if (oldIsAlive != isAlive) {
                    changedServers.add(svr);
                    logger.debug("LoadBalancer [{}]:  Server [{}] status changed to {}",
                            name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
                }
                if (isAlive) {
                    newUpList.add(svr);
                }
            }
            upLock = upServerLock.writeLock();
            upLock.lock();
            upServerList = newUpList; //活着的服務賦值給BaseLoadBalancer 的List<Server> upServerList
            upLock.unlock();

            notifyServerStatusChangeListener(changedServers); //通知監聽器處理有變化的服務
        } finally {
            pingInProgress.set(false);
        }
    }

這個Ping服務的作用其實很簡單,就是拿所有的服務列表去查詢下服務是否還在線,將還在線的服務賦給BaseLoadBalancer的upServerList屬性;

但是,我們看到容器中IPing的實現類是DummyPing

 

  看到這個類判斷服務是否存活的方法永遠都返回true,所以這個Ping的功能默認的實現有跟沒有什么區別:

 

 

 

 

 

 看看是如何獲取服務列表的:

DiscoveryEnabledNIWSServerList這個類中有個獲取服務列表的方法(我這里是用Eureka作為注冊中心)

 

 

上面過程總結:

思考: 服務列表保存到了BaseLoadBalancer中,那么,假如一個服務掛了,如何保證BaseLoadBalancer的服務列表會被更新?或者說,如果新增了一個服務,如何保證新增的服務會加到服務列表中

通過上面的講解,我們發現有一個Ping機制會去檢查服務是否還活着,但默認實現判斷是否活着的時候,都是返回true,因此該功能基本沒什么用,是否存在其他機制呢?

我們回到ZoneAwareLoadBalancer這個類的創建:

 

 

 

我們繼續回到ZoneAwareLoadBalancer的創建過程中:

 

 

 

 發現調用了PollingServerListUpdater的start方法

 

 

 

 

 

 至此,Ribbon通過服務名稱獲取服務列表的底層原理分析完畢

代碼跟蹤:Server server = getServer(loadBalancer, hint);方法

 

 

 

 

 

 

 

 

 

 

 

 

 

 至此,負載均衡算法獲取一個服務的底層已經分析完畢;

思考:如果我想不使用默認的負載均衡算法或者非默認的IPing實現類,如何做?

我們回到spring容器注冊的那個配置類RibbonClientConfiguration

 

 因此這個PropertiesFactory類比較重要:

 

public class PropertiesFactory {

    @Autowired
    private Environment environment;

    private Map<Class, String> classToProperty = new HashMap<>();

    public PropertiesFactory() { //這些屬性都是可以配置的
        classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
        classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
        classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
        classToProperty.put(ServerList.class, "NIWSServerListClassName");
        classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
    }

    public boolean isSet(Class clazz, String name) {
        return StringUtils.hasText(getClassName(clazz, name));
    }

    public String getClassName(Class clazz, String name) {
        if (this.classToProperty.containsKey(clazz)) {
            String classNameProperty = this.classToProperty.get(clazz);
            String className = environment
                    .getProperty(name + "." + NAMESPACE + "." + classNameProperty);
            return className;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public <C> C get(Class<C> clazz, IClientConfig config, String name) {  //yml中配置了相應的類,這里會實例化化
        String className = getClassName(clazz, name);
        if (StringUtils.hasText(className)) {
            try {
                Class<?> toInstantiate = Class.forName(className);
                return (C) SpringClientFactory.instantiateWithConfig(toInstantiate,
                        config);
            }
            catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Unknown class to load " + className
                        + " for class " + clazz + " named " + name);
            }
        }
        return null;
    }

}

 

 

 

 

 

 由此可知,要想配置自定義的bean,語法是服務名.ribbon.xxxx,如:

 

 

 

 

 

 

 至此,Ribbon底層分析完成

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM