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底层分析完成
Ribbon源码分析(二)-- 服务列表的获取和负载均衡算法分析的更多相关文章
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
		4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ... 
- 框架-springmvc源码分析(二)
		框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ... 
- Tomcat源码分析二:先看看Tomcat的整体架构
		Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ... 
- 十、Spring之BeanFactory源码分析(二)
		Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ... 
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
		目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ... 
- Vue源码分析(二) : Vue实例挂载
		Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ... 
- [源码解析] 并行分布式任务队列 Celery 之 负载均衡
		[源码解析] 并行分布式任务队列 Celery 之 负载均衡 目录 [源码解析] 并行分布式任务队列 Celery 之 负载均衡 0x00 摘要 0x01 负载均衡 1.1 哪几个 queue 1.1 ... 
- Ribbon源码分析(一)-- RestTemplate 以及自定义负载均衡算法
		如果只是想看ribbon的自定义负载均衡配置,请查看: https://www.cnblogs.com/yangxiaohui227/p/13186004.html 注意: 1.RestTemplat ... 
- spring源码分析(二)Aop
		创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ... 
随机推荐
- muduo源码解析7-countdownlatch类
			countdownlatch class countdownlatch:noncopyable { }; 作用: countdownlatch和mutex,condition一样,用于线程之间的同步, ... 
- 23种设计模式 - 领域问题(Interpreter)
			其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 领域问题 Interpreter 动机(Motivation) 在软件构建过程中,如果某一特定领域的问题比 ... 
- 洛谷 P4995 跳跳!
			思路 贪心 从大到小排序,然后反复横跳,记录两个指针 \(l=1, r=n\),从 \(1\) 跳到 \(n\),再从 \(n\) 跳到 \(2\),然后从 \(2\) 跳到 \(n - 1\)--, ... 
- android开发中防止刚进入activity时edittext获取焦点,防止自动自动弹出软键盘
			刚进入activity的时候,如果布局组件有edittext的话,往往edittext会获取焦点,自动弹出软键盘,影响整个界面的视觉效果.解决方法如下: 可以在edittext的父布局结构中(例如Li ... 
- 【pytest】(三) pytest运行多个文件
			1.运行多个测试文件 pytest 会运行 test_ 开头 或者 _test 结尾的文件,在当前目录和子目录中 2. 一个类下的多个用例的运行, pytest会找到 test_ 开头的方法 impo ... 
- 有手就行  虚拟机上安装Linux
			VMware上装Linux CentOS 初学一步步来 
- Solon详解(六)- Solon的校验扩展框架使用与扩展
			Solon详解系列文章: Solon详解(一)- 快速入门 Solon详解(二)- Solon的核心 Solon详解(三)- Solon的web开发 Solon详解(四)- Solon的事务传播机制 ... 
- 单线程模式从网易下载A股叁仟捌佰支股票一年的交易数据耗时十四分钟
			代码下载:https://files.cnblogs.com/files/xiandedanteng/StockDataDownloader20200305.rar 压缩包内包含股票代号文件,调整好日 ... 
- python3中抛异常except后面参数
			try: xxx except exception as e: print("给exception取了个别名叫做e") else: ccc 
- 起redis服务时报错Creating Server TCP listening socket *:6379: bind: No such file or directory
