Ribbon源碼解讀和如何覆蓋默認規則

Ribbon源碼解讀

可參考博客:https://blog.csdn.net/forezp/article/details/74820899,寫的很好,可以結合它這篇博客最後的總結來看,就比較清楚那些接口和類之間的關係了

大致梳理下:

ribbon選擇服務器鏈路調用邏輯: 

LoadBalancerClient(LoadBalancerClient) -> ILoadBalancer(ZoneAwareLoadBalancer) -> IRule (ZoneAvoidanceRule)

參考博客的總結:Ribbon的負載均衡,主要通過LoadBalancerClient來實現的,而LoadBalancerClient具體交給了ILoadBalancer來處理,ILoadBalancer通過配置IRule、IPing等信息,並向EurekaClient獲取註冊列表的信息,並默認10秒一次向EurekaClient發送“ping”,進而檢查是否更新服務列表,最後,得到註冊列表後,ILoadBalancer根據IRule的策略進行負載均衡。

而RestTemplate 被@LoadBalance註解後,能過用負載均衡,主要是維護了一個被@LoadBalance註解的RestTemplate列表,並給列表中的RestTemplate添加攔截器,進而交給負載均衡器去處理。
 

源碼中有個地方的解釋:

在LoadBalancerAutoConfiguration配置類中有這麼一段代碼:

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();
   

這段代碼是獲取到所有被@LoadBalanced註解的RestTemplate對象 ,然後我就有個疑問。

這裏爲什麼就能獲取到所有被@LoadBalanced註解的RestTemplate對象,明明就只多了個@LoadBalanced註解 ?

博主是這樣解答的:@LoadBalanced註解包含了@Qualifier註解,其作用可以保證這個List裏維護的就一定是被@LoadBalanced修飾的RestTemplate對象

 

怎麼覆蓋默認規則

比如你可能不想使用它的負載均衡策略,如輪詢等,想自己實現一個負載均衡的規則,那麼可以覆蓋IRule這個接口實現。

在RibbonClientConfiguration這個配置類中對應規則裝載的源碼如下:

    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }

propertiesFactory可以先不管,通過@ConditionalOnMissingBean註解我們就知道了,只有bean中不存在IRule的實現類,纔會使用默認的ZoneAvoidanceRule規則,所以我們可以通過將自己寫的rule添加到bean中來覆蓋默認的規則

 

1.IRule的作用就是定義你想使用的負載均衡規則,在choose方法中可以實現,源碼如下:

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

2. 可以參考IRule的一個已實現的類RandomRule,裏面其實就一個 choose方法通過隨機數來選擇服務器,還有一個空方法initWithNiwsConfig

public class RandomRule extends AbstractLoadBalancerRule {
    public RandomRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }

                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }

                int index = this.chooseRandomInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

3. 自己寫個MyRule的類,只獲取最後一臺服務器

public class MyRule extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public Server choose(Object key) {

        ILoadBalancer loadBalancer = getLoadBalancer();

        //獲取所有可達服務器列表
        List<Server> servers = loadBalancer.getReachableServers();
        if (servers.isEmpty()) {
            return null;
        }

        // 永遠選擇最後一臺可達服務器
        Server targetServer = servers.get(servers.size() - 1);
        return targetServer;
    }

}

4. 添加到Bean中

@Bean
public IRule myRule() {
    return new MyRule();
}

在Camden版本後對RibbonClient自定義配置的優化

上面的方式確實已經可以實現改變負載均衡策略了,但是這種不能夠自定義的爲每個服務指定不同的規則,比如說我消費者中可能調用到了兩個不同應用名的服務提供者,那麼我又想對這兩個服務提供者分別進行個性化的規則定製,那像我們上面實現的這種方式顯然是不行的,所以在Camden版本後對這方面進行了優化

主要是添加了PropertiesFactory這個類,通過查看源碼:

public class PropertiesFactory {
    @Autowired
    private Environment environment;
    private Map<Class, String> classToProperty = new HashMap();

    public PropertiesFactory() {
        this.classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
        this.classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
        this.classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
        this.classToProperty.put(ServerList.class, "NIWSServerListClassName");
        this.classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
    }

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

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

    public <C> C get(Class<C> clazz, IClientConfig config, String name) {
        String className = this.getClassName(clazz, name);
        if (StringUtils.hasText(className)) {
            try {
                Class<?> toInstantiate = Class.forName(className);
                return SpringClientFactory.instantiateWithConfig(toInstantiate, config);
            } catch (ClassNotFoundException var6) {
                throw new IllegalArgumentException("Unknown class to load " + className + " for class " + clazz + " named " + name);
            }
        } else {
            return null;
        }
    }
}

在getClassName方法中是這麼拼接的:服務名.ribbon.classToProperty中的value值,舉個例子,假設我這個client要調用一個名爲user-service的服務器,並且想爲這個服務使用MyRule規則,那麼在application.properties中就按如下配置即可, com.tanfp.ribbon.MyRule是我MyRule類的路徑名

user-service.ribbon.NFLoadBalancerRuleClassName = com.tanfp.ribbon.MyRule

想要修改ping的規則,那麼就如下配置:

user-service.ribbon.NFLoadBalancerPingClassName = com.tanfp.ribbon.MyPing

其他的比如LoadBalancer,ServerList,ServerListFilter等實現都可以直接參考PropertiesFactory中classToProperty該map中對應的value值來進行判斷你配置中的屬性key要怎麼寫。

官方文檔中也有說明:https://cloud.spring.io/spring-cloud-static/Hoxton.SR4/reference/htmlsingle/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章