SpringCloud解析三:Ribbon源码分析(下)

一:简要回顾

先来回顾下上个章节讲到的sping cloud提供的负载均衡逻辑。spring-cloud-commons中对负载均衡做了几个抽象,

1,LoadBalancerInterceptor类

负载均衡拦截器,用于添加到RestTemplate的拦截器链中从而拦截请求。其intercept方法会执行LoadBalancerClient的execute方法。

对于一般的拦截器来说,在拦截器的intercept方法中经过处理后就直接调用ClientHttpRequestExecution的execute方法迭代执行完所有的拦截器,并将最后个拦截器的执行结果返回。而在这里却会先将其执行代码封装到LoadBalancerRequest类型的对象中,等到确认了要访问的服务实例后再执行【在LoadBalancerClient中】。

2,LoadBalancerClient接口

定义了负载均衡客户端所需要实现的方法。继承自ServiceInstanceChooser接口实现从负载均衡器中筛选出服务实例的功能;同时定义了一个供 LoadBalancerInterceptor 调用的execute方法,该方法接收服务id(serviceId)与LoadBalancerRequest类型实例作为参数,返回ClientHttpResponse 类型的响应结果。

3,LoadBalancerRequest接口

定义LoadBalancerClient接口中execute方法执行的参数类型,该接口中只有一个接收ServiceInstance参数的apply方法。该接口的目的是封装拦截器的执行逻辑,等到后面确认服务实例后再调用。 该类型的实例由LoadBalancerRequestFactory创建返回。

4,LoadBalancerRequestFactory类

只是一个普通的工具类工厂,目的是创建并返回LoadBalancerRequest类型实例对象。默认的实现是在createRequest方法中将ServiceInstance、HttpRequest 、LoadBalancerClient 封装到ServiceRequestWrapper包装器对象中,最后将包装器对象作为参数继续调用ClientHttpRequestExecution 的execute方法从而继续调用其它的拦截器对象直到请求完毕并返回。

ServiceRequestWrapper主要是重写了getURI方法使用LoadBalancerClient的reconstructURI方法得到真正的服务实例访问地址。

整个的过程大致是

  1. LoadBalancerInterceptor拦截请求执行intercept方法
  2. 使用intercept方法中调用LoadBalancerRequestFactory来创建封装了拦截器处理逻辑的LoadBalancerRequest类型实例对象
  3. 使用serviceId和LoadBalancerRequest作为参数调用LoadBalancerClient实例的execute方法,在得到访问的服务实例后执行LoadBalancerRequest的apply方法并返回

二,SpringCloud Ribbon源码分析

针对spring-cloud-netflix-ribbon-2.2.0.RELEASE的源码

我们继续分析Ribbon是怎么让RestTemplate达到负载均衡的目的的。
在其/META-INF/spring.factories文件中对启动类进行了如下的配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

由上个章节所知,只要在IOC中具备LoadBalancerClient的实现类即可让RestTemplate实现负载均衡,Ribbon也正是这么做的,先看RibbonAutoConfiguration的里面都做了些啥

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
//对Ribbon客户端进行配置
@RibbonClients
//在EurekaClientAutoConfiguration配置类执行后执行
@AutoConfigureAfter(
        name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
//在LoadBalancerAutoConfiguration与AsyncLoadBalancerAutoConfiguration前执行
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
//读取配置数据
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
    
    //收集IOC中所有的RibbonClient的配置信息
    //【通过上面的@RibbonClients注解生成的RibbonClientSpecification对象】
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();

    //读取“ribbon.eager-load.enabled”以及“ribbon.eager-load.clients”的配置
    //用于判断是否启动“饥饿加载”【应用启动后立即创建指定的Ribbon客户端】
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    @Bean
    public HasFeatures ribbonFeature() {
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }

    //创建Ribbon使用的SpringClientFactory对象,该对象会读取Ribbon客户端的配置信息,
    //并为每个客户端创建一个AnnotationConfigApplicationContext上下文【首次调用某个客户端的时候才创建,
    //可以使用“饥饿加载”让指定的服务一开始就创建】。
    //内部使用org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
    //作为默认的配置配置项
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }

    //创建LoadBalancerClient 类型的RibbonLoadBalancerClient对象【这里是最关键的地方】,
    //当这个实例存在后,就可以使用RestTemplate进行客户端负载均衡了
    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

    //创建可以进行“重试”的LoadBalancedRetryFactory类型对象
    @Bean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
            final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }

    //立马执行“饥饿加载”
    @Bean
    @ConditionalOnProperty("ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(HttpRequest.class)
    //当ribbon.restclient.enabled参数设置为ture时执行以下配置【如果这里为true,那么拦截器将不能被设置】
    @ConditionalOnRibbonRestClient
    protected static class RibbonClientHttpRequestFactoryConfiguration {

        @Autowired
        private SpringClientFactory springClientFactory;

        @Bean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
            return restTemplate -> restTemplate
                    .setRequestFactory(ribbonClientHttpRequestFactory);
        }

        @Bean
        public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
            return new RibbonClientHttpRequestFactory(this.springClientFactory);
        }
    }
}

从上可知,@RibbonClients注解会首先执行,然后如果有EurekaClientAutoConfiguration配置类(Eureka Starter中)存在则会等待该配置类结束后再执行RibbonAutoConfiguration,最后执行Spring Cloud Commos里面的LoadBalancerAutoConfiguration与AsyncLoadBalancerAutoConfiguration配置。如下图所示:

注解和配置类的执行顺序

小结:从上面的代码可知,该配置类的目的就是创建LoadBalancerClient 的实例对象RibbonLoadBalancerClient。
首先得到每个客户端的配置(RibbonClientSpecification),然后将这些配置传递给SpringClientFactory 来创建每个Ribbon客户端,接着使用SpringClientFactory作为构造参数创建LoadBalancerClient类型的RibbonLoadBalancerClient实例,从而便与RestTemplate集成在了一起【SprigClientFactory中还会读取配置文件中的Ribbon客户端配置】。


1,@RibbonClients

@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
//这里是关键的地方
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {

    RibbonClient[] value() default {};

    Class<?>[] defaultConfiguration() default {};
}

其中的RibbonClient注解源码如下

@Configuration(proxyBeanMethods = false)
//同@RibbonClients 注解一样引入同一个类
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
    /**
     *  Ribbon client 的名字,同name属性
     */
    String value() default "";
    /**
     * Ribbon client 的名字 
     */
    String name() default "";
    /**
     * 自定义的@Configuration类,用于配置该Ribbon 客户端
     * 可以配置ILoadBalancer,ServerListFilter,IRule等类型的bean,将会覆盖默认的配置 (RibbonClientConfiguration )
     */
    Class<?>[] configuration() default {};
}

RibbonClientConfigurationRegistrar
该类的目的是加载在注解中配置的Ribbon客户端的配置信息,默认情况下由于并没有设置任何值,所以并不会加载任何的配置到IOC中。但我们完全可以自定义@Configuration来定制Ribbon客户端的行为。

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

    //该方法被调用对注解进行解析,然后将解析结果封装成RibbonClientSpecification实例对象加入IOC中
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(RibbonClients.class.getName(), true);
        if (attrs != null && attrs.containsKey("value")) {
            //解析各个设置到这里的@RibbonClient注解
            AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
            for (AnnotationAttributes client : clients) {
                //将解析出的每个注解注册到IOC中【服务名 - 服务对应的配置】
                registerClientConfiguration(registry, getClientName(client),
                        client.get("configuration"));
            }
        }
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            //添加默认的配置【如果有设置】
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            //注册客户端的配置信息
            registerClientConfiguration(registry, name,
                    attrs.get("defaultConfiguration"));
        }
        Map<String, Object> client = metadata
                .getAnnotationAttributes(RibbonClient.class.getName(), true);
        String name = getClientName(client);
        if (name != null) {
            registerClientConfiguration(registry, name, client.get("configuration"));
        }
    }

    private String getClientName(Map<String, Object> client) {
        if (client == null) {
            return null;
        }
        String value = (String) client.get("value");
        if (!StringUtils.hasText(value)) {
            value = (String) client.get("name");
        }
        if (StringUtils.hasText(value)) {
            return value;
        }
        throw new IllegalStateException(
                "Either 'name' or 'value' must be provided in @RibbonClient");
    }

    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        //Ribbon客户端名字【RestTemplate中通过使用该名字替换URL中的主机地址进行请求】
        builder.addConstructorArgValue(name);
        //Ribbon客户端使用的配置类(用于覆盖全局默认配置)
        builder.addConstructorArgValue(configuration);
        //将RibbonClientSpecification实例定义信息注册到Spring中
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }
}

如果在代码中我们通过@RibbonClients或@RibbonClient注解对Ribbon客户端进行了配置(也可在配置文件中配置),那么这些配置都会被封装到RibbonClientSpecification类型的并添加到IOC,从而供后面的注入所使用。

完全可以在其它地方再次使用@RibbonClients来定义其全局默认配置,从而影响使用Ribbon客户端发起请求时的行为,如与服务发现集成。


2,SpringClientFactory

该类是Ribbon的关键工厂类,它主要负责 创建客户端(Client)、负载平衡器(ILoadBalancer)和客户端配置实例(IClientConfig)。它为每个Ribbon 客户端 创建一个Spring ApplicationContext,并从中提取它所需的bean。

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

    static final String NAMESPACE = "ribbon";

    public SpringClientFactory() {
        //重点:加载默认的配置,该配置的内容在后面说明
        //三个参数分别为:默认配置类,属性源(指定读取什么开头的配置),属性名
        super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
    }

    /**
     * Get the rest client associated with the name.
     */
    public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
        return getInstance(name, clientClass);
    }

    /**
     * Get the load balancer associated with the name.
     */
    public ILoadBalancer getLoadBalancer(String name) {
        return getInstance(name, ILoadBalancer.class);
    }

    /**
     * Get the client config associated with the name.
     */
    public IClientConfig getClientConfig(String name) {
        return getInstance(name, IClientConfig.class);
    }

    /**
     * Get the load balancer context associated with the name.
     */
    public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
        return getInstance(serviceId, RibbonLoadBalancerContext.class);
    }

    static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
        return instantiateWithConfig(null, clazz, config);
    }

    static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
            Class<C> clazz, IClientConfig config) {
        C result = null;
        try {
            Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
            result = constructor.newInstance(config);
        }
        catch (Throwable e) {
            // Ignored
        }

        if (result == null) {
            result = BeanUtils.instantiate(clazz);
            if (result instanceof IClientConfigAware) {
                ((IClientConfigAware) result).initWithNiwsConfig(config);
            }
            if (context != null) {
                context.getAutowireCapableBeanFactory().autowireBean(result);
            }
        }
        return result;
    }

    @Override
    public <C> C getInstance(String name, Class<C> type) {
        C instance = super.getInstance(name, type);
        if (instance != null) {
            return instance;
        }
        IClientConfig config = getInstance(name, IClientConfig.class);
        //实例化对象
        return instantiateWithConfig(getContext(name), type, config);
    }

    //判断缓存中是否有ApplicationContext,如果游就直接返回,
    //否则创建AnnotationConfigApplicationContext,同时初始化再缓存【在创建ApplicationContext的过程中将会读取配置数据、加载默认的配置等,具体过程在父类中】
    @Override
    protected AnnotationConfigApplicationContext getContext(String name) {
        return super.getContext(name);
    }
}

由于上面getContext(String name)方法很关键,所以特将父类中的代码摘出来

    protected AnnotationConfigApplicationContext getContext(String name) {
        //判断缓存中是否有指定名的ApplicationContext,如果有就直接取得并返回,否则创建
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    //创建ApplicationContext并缓存
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }

    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //configurations就是RibbonAutoConfiguration配置类中收集的所有通过注解配置的客户端配置(RibbonClientSpecification)
        if (this.configurations.containsKey(name)) {
            //如果有针对该客户端的配置,则注册到ApplicationContext中
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            //查找默认配置项并加载到IOC中
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        //将PropertyPlaceholderAutoConfiguration以及默认配置RibbonClientConfiguration注册到IOC中
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType);
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,
                Collections.<String, Object>singletonMap(this.propertyName, name)));
        if (this.parent != null) {
            // Uses Environment from parent as well as beans
            context.setParent(this.parent);
            context.setClassLoader(this.parent.getClassLoader());
        }
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }

接下来看下其默认的配置类RibbonClientConfiguration
该类中在Bean创建的时候都添加了@ConditionalOnMissingBean注解,说明我们可以将其默认的Bean给替换掉

@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
// 导入客户端的配置,默认使用ApacheHttpClient创建RibbonLoadBalancingHttpClient客户端
@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;

    //配置文件中的 ribbon.client.name
    @RibbonClientName
    private String name = "client";

     /*
      * 通过该对象可以读取配置文件中定义的实例对象,包含以下几个类型
      *  ILoadBalancer、IPing、IRule、ServerList、ServerListFilter
      *  对应的添加以下配置即可替换默认的
      *  {ribbon.client.name}.ribbon.NFLoadBalancerClassName
      *  {ribbon.client.name}.ribbon.NFLoadBalancerPingClassName
      *  {ribbon.client.name}.ribbon.NFLoadBalancerRuleClassName
      *  {ribbon.client.name}.ribbon.NIWSServerListClassName
      *  {ribbon.client.name}.ribbon.NIWSServerListFilterClassName
      */
    @Autowired
    private PropertiesFactory propertiesFactory;

    //用于解析ribbon客户端的配置数据
    @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;
    }
    //Ribbon负载均衡策略。默认的策略能够在多区域环境下选出最佳区域的实例进行访问
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }
    //Ribbon实例检查策略。默认 这实际上并不会检查实例是否可用而始终返回true。
    @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) {
        return new PollingServerListUpdater(config);
    }
    //负载均衡器。默认的具备了区域感知的能力
    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
            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;
        }
    }
}

3,RibbonLoadBalancerClient

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient 的源码如下

public class RibbonLoadBalancerClient implements LoadBalancerClient {

    private SpringClientFactory clientFactory;

    public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

    @Override
    public URI reconstructURI(ServiceInstance instance, URI original) {
        Assert.notNull(instance, "instance can not be null");
        String serviceId = instance.getServiceId();
        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);

        URI uri;
        Server server;
        if (instance instanceof RibbonServer) {
            RibbonServer ribbonServer = (RibbonServer) instance;
            server = ribbonServer.getServer();
            uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
        }
        else {
            server = new Server(instance.getScheme(), instance.getHost(),
                    instance.getPort());
            IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
            ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
            uri = updateToSecureConnectionIfNeeded(original, clientConfig,
                    serverIntrospector, server);
        }
        return context.reconstructURIWithServer(server, uri);
    }

    @Override
    public ServiceInstance choose(String serviceId) {
        return choose(serviceId, null);
    }

    /**
     * New: Select a server using a 'key'.
     * @param serviceId of the service to choose an instance for
     * @param hint to specify the service instance
     * @return the selected {@link ServiceInstance}
     */
    public ServiceInstance choose(String serviceId, Object hint) {
        Server server = getServer(getLoadBalancer(serviceId), hint);
        if (server == null) {
            return null;
        }
        return new RibbonServer(serviceId, server, isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));
    }

    //在拦截器中被调用的方法。通过该方法调用Ribbon的负载均衡器得到需要访问的服务实例,然后执行LoadBalancerRequest的apply方法并将最后的结果返回。
    @Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
            throws IOException {
        return execute(serviceId, request, null);
    }

    /**
     * New: Execute a request by selecting server using a 'key'. The hint will have to be
     * the last parameter to not mess with the `execute(serviceId, ServiceInstance,
     * request)` method. This somewhat breaks the fluent coding style when using a lambda
     * to define the LoadBalancerRequest.
     * @param <T> returned request execution result type
     * @param serviceId id of the service to execute the request to
     * @param request to be executed
     * @param hint used to choose appropriate {@link Server} instance
     * @return request execution result
     * @throws IOException executing the request may result in an {@link IOException}
     */
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
            throws IOException {
        //获取Ribbon的LoadBalancer实例对象
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        //从LoadBalancer中得到选出的服务实例
        Server server = getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        //将服务实例做简单的封装
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));
        //继续调用方法并返回
        return execute(serviceId, ribbonServer, request);
    }

    @Override
    public <T> T execute(String serviceId, ServiceInstance serviceInstance,
            LoadBalancerRequest<T> request) throws IOException {
        Server server = null;
        if (serviceInstance instanceof RibbonServer) {
            server = ((RibbonServer) serviceInstance).getServer();
        }
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }

        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        try {
            //调用LoadBalanceClient的apply方法【内部会执行】
            T returnVal = request.apply(serviceInstance);
            statsRecorder.recordStats(returnVal);
            return returnVal;
        }
        // catch IOException and rethrow so RestTemplate behaves correctly
        catch (IOException ex) {
            statsRecorder.recordStats(ex);
            throw ex;
        }
        catch (Exception ex) {
            statsRecorder.recordStats(ex);
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }

    private ServerIntrospector serverIntrospector(String serviceId) {
        ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
                ServerIntrospector.class);
        if (serverIntrospector == null) {
            serverIntrospector = new DefaultServerIntrospector();
        }
        return serverIntrospector;
    }

    private boolean isSecure(Server server, String serviceId) {
        IClientConfig config = this.clientFactory.getClientConfig(serviceId);
        ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
        return RibbonUtils.isSecure(config, serverIntrospector, server);
    }

    // Note: This method could be removed?
    protected Server getServer(String serviceId) {
        return getServer(getLoadBalancer(serviceId), null);
    }

    protected Server getServer(ILoadBalancer loadBalancer) {
        return getServer(loadBalancer, null);
    }

    protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
        if (loadBalancer == null) {
            return null;
        }
        // Use 'default' on a null hint, or just pass it on?
        return loadBalancer.chooseServer(hint != null ? hint : "default");
    }

    protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
    }

    /**
     * Ribbon-server-specific {@link ServiceInstance} implementation.
     */
    public static class RibbonServer implements ServiceInstance {

        private final String serviceId;

        private final Server server;

        private final boolean secure;

        private Map<String, String> metadata;

        public RibbonServer(String serviceId, Server server) {
            this(serviceId, server, false, Collections.emptyMap());
        }

        public RibbonServer(String serviceId, Server server, boolean secure,
                Map<String, String> metadata) {
            this.serviceId = serviceId;
            this.server = server;
            this.secure = secure;
            this.metadata = metadata;
        }

        @Override
        public String getInstanceId() {
            return this.server.getId();
        }

        @Override
        public String getServiceId() {
            return this.serviceId;
        }

        @Override
        public String getHost() {
            return this.server.getHost();
        }

        @Override
        public int getPort() {
            return this.server.getPort();
        }

        @Override
        public boolean isSecure() {
            return this.secure;
        }

        @Override
        public URI getUri() {
            return DefaultServiceInstance.getUri(this);
        }

        @Override
        public Map<String, String> getMetadata() {
            return this.metadata;
        }

        public Server getServer() {
            return this.server;
        }

        @Override
        public String getScheme() {
            return this.server.getScheme();
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("RibbonServer{");
            sb.append("serviceId='").append(serviceId).append('\'');
            sb.append(", server=").append(server);
            sb.append(", secure=").append(secure);
            sb.append(", metadata=").append(metadata);
            sb.append('}');
            return sb.toString();
        }
    }
}

至此,Spring cloud Ribbon的解析基本告一段落。总的原理就是Spring cloud coomms已经提供了让RestTemplate进行负载均衡调用的“规范”,Spring cloud Ribbion基于该规范实现了LoadBalancerClient接口提供一个负载均衡的客户端,然后就可以使用netfix的Ribbon进行一系列的操作从而达到负载均衡的目的。 后面章节还将继续分析Ribbon与Eureka等服务发现组件配合使用的原理。

Ribbon提供的负载均衡策略
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容