• 欢迎来到Compiler网站,如果对网站内容感兴趣或者帮助到你,请为文章点赞,谢谢!

spring clound负载均衡之Ribbon(三)- 工作原理

Spring Cloud 奔跑的蜗牛 4周前 (09-25) 37次浏览 0个评论
文章目录[隐藏]

在之前文章spring clound负载均衡之Ribbon(二)- 自动装配 中,介绍了ribbon自动装配的原理,这篇文章中将主要介绍在ribbon启动过程中,涉及到的重要类型。这些类型在ribbon中充当比较重要的作用。下面我们就一起来看看吧。

RibbonClientSpecification

该类作为ribbon配置类型存在,主要由spring-cloud提供,我们看下该类型的源码:

/**
 * Specification with name and configuration.
 */
public interface Specification {

    String getName();

    Class<?>[] getConfiguration();

}

public class RibbonClientSpecification implements NamedContextFactory.Specification {

    ....
}

在spring实现中,实现类Speicification接口,该接口中主要包含了两个属性:nameconfiguration两个。该类型主要用于保存所有的配置类型相关。

在Spring-cloud-netflix默认配置中,是有两个RibbonClientSpecification的指定,这两个指定,也是为后面使用RestTemplate做了默认配置的设定,在开始看这两个配置之前,优先查看下两个比较重要的注解:

  • @RibbonClients
  • @RibbonClient

@RibbonClients

这个注解可以包含多个@RibbonClient注解,同时当我们默认的configuration配置的时候,则可以通过defaultConfiguration进行设置,具体查看源码如下:

/**
 * Convenience annotation that allows user to combine multiple <code>@RibbonClient</code>
 * annotations on a single class (including in Java 7).
 *
 * @author Dave Syer
 */
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {

    RibbonClient[] value() default {};

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

}

在这个源码中,通过@Import引入了RibbonClientConfigurationRegistrar类型,对@RibbonClients注解进行单独的处理。

@RibbonClient

该注解主要对单个服务进行ribbon项的设置,具体源码如下:

package org.springframework.cloud.netflix.ribbon;


/**
 * Declarative configuration for a ribbon client. Add this annotation to any
 * <code>@Configuration</code> and then inject a {@link SpringClientFactory} to access the
 * client that is created.
 *
 * @author Dave Syer
 */
@Configuration
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {

    /**
     * Synonym for name (the name of the client).
     *
     * @see #name()
     * @return name of the Ribbon client
     */
    String value() default "";

    /**
     * The name of the ribbon client, uniquely identifying a set of client resources,
     * including a load balancer.
     * @return name of the Ribbon client
     */
    String name() default "";

    /**
     * A custom <code>@Configuration</code> for the ribbon client. Can contain override
     * <code>@Bean</code> definition for the pieces that make up the client, for instance
     * {@link ILoadBalancer}, {@link ServerListFilter}, {@link IRule}.
     *
     * @see RibbonClientConfiguration for the defaults
     * @return the custom Ribbon client configuration
     */
    Class<?>[] configuration() default {};

}

这个注解主要为ribbon配置指定名称,以及configuration配置信息。同时这个注解在使用的时候,也通过@Import的方式引入了RibbonClientConfigurationRegistrar对注解的处理,下面我查看下该类的源码处理:

RibbonClientConfigurationRegistrar

这个类是ImportBeanDefinitionRegistrar的实现类,用于实现动态bean的实现,下面是该类的实现:

package org.springframework.cloud.netflix.ribbon;

/**
 * @author Dave Syer
 */
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 获取元数据的RibbonClients注解
        Map<String, Object> attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true);

        // 包含了value属性
        if (attrs != null && attrs.containsKey("value")) {
            AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
            for (AnnotationAttributes client : clients) {
                registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
            }
        }
        
        // 是否包含defaultConfiguration配置
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            String name;
            // 在配置名称前加入default配置项信息
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }

            // 注册客户端配置信息
            registerClientConfiguration(registry, name,
                    attrs.get("defaultConfiguration"));
        }

        // 获取RibbonClient配置列表
        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) {

        // 获取RibbonClientSpecification类的元数据
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        // 注册BeanDefinition
        registry.registerBeanDefinition(name + ".RibbonClientSpecification", builder.getBeanDefinition());
    }

}

SpringClientFactory

该类作为客户端工厂存在,主要用于保存Specification相关配置类信息。同时该类根据名称进行隔离,保证了bean之间不会相互影响。下面为SpringClientFactory的类图.

spring clound负载均衡之Ribbon(三)- 工作原理

从类图上可以看出,该类继承了NamedContextFactory类型,我们看下该类的说明文档:

/**
 * Creates a set of child contexts that allows a set of Specifications to define the beans
 * in each child context.
 *
 * Ported from spring-cloud-netflix FeignClientFactory and SpringClientFactory
 *
 * @param <C> specification
 * @author Spencer Gibb
 * @author Dave Syer
 */
// TODO: add javadoc
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {
}

通过注解可以得知,该类主要用于创建关于Specifications的子容器。在每个子容器中,对应的Specificationbean是相互隔离的。

我们在查看SpringClientFactory创建源码:

/**
 * A factory that creates client, load balancer and client configuration instances. It
 * creates a Spring ApplicationContext per client name, and extracts the beans that it
 * needs from there.
 *
 * @author Spencer Gibb
 * @author Dave Syer
 */
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

    static final String NAMESPACE = "ribbon";

    /**
     * 创建SpringClientFactory对象,处理的类型为RibbonClientConfiguration, sourceType为ribbon, propertyName为ribbon.client.name
     */
    public SpringClientFactory() {
        super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
    }

    /**
     * Get the rest client associated with the name.
     * @param name name to search by
     * @param clientClass the class of the client bean
     * @param <C> {@link IClient} subtype
     * @return {@link IClient} instance
     * @throws RuntimeException if any error occurs
     */
    public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
        return getInstance(name, clientClass);
    }

    /**
     * Get the load balancer associated with the name.
     * @param name name to search by
     * @return {@link ILoadBalancer} instance
     * @throws RuntimeException if any error occurs
     */
    public ILoadBalancer getLoadBalancer(String name) {
        return getInstance(name, ILoadBalancer.class);
    }

    /**
     * Get the client config associated with the name.
     * @param name name to search by
     * @return {@link IClientConfig} instance
     * @throws RuntimeException if any error occurs
     */
    public IClientConfig getClientConfig(String name) {
        return getInstance(name, IClientConfig.class);
    }

    /**
     * Get the load balancer context associated with the name.
     * @param serviceId id of the service to search by
     * @return {@link RibbonLoadBalancerContext} instance
     * @throws RuntimeException if any error occurs
     */
    public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
        return getInstance(serviceId, RibbonLoadBalancerContext.class);
    }

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

    // 实例化IClientConfig对象
    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) {
        // 通过NameContextFactory获取instance对象
        C instance = super.getInstance(name, type);
        if (instance != null) {
            return instance;
        }
        // 如果获取失败,则创建instance实例
        IClientConfig config = getInstance(name, IClientConfig.class);
        // 实例化config对象
        return instantiateWithConfig(getContext(name), type, config);
    }

    @Override
    protected AnnotationConfigApplicationContext getContext(String name) {
        return super.getContext(name);
    }

}

通过以上源码可以看出,该SpringClientFactory对象主要用于创建ribbon客户端必须的对象,其中包括IClientILoadBalancer, IClientConfig, RibbonLoadBalancerContext. 因此该类就是一个工厂类型,创建所需要的组件对象。

在该类中,最为重要的一个方法就是getInstance()方法,该方法会根据名称以及type获取对应的instance对象,因此我们主要看下该方法中的执行流程:

NamedContextFactory#getInstance()

我们直接上该类中源码信息:

public <T> T getInstance(String name, Class<T> type) {
        // 根据name获取绑定的子context
        AnnotationConfigApplicationContext context = getContext(name);

        // 判断context中是否包含了需要type的bean, 如果包含,则从context中获取
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
            return context.getBean(type);
        }
        return null;
    }

protected AnnotationConfigApplicationContext getContext(String name) {
        // 判断contexts缓存中是否包含了指定name的context对象
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    // 如果不包含,则创建上下文对象
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }

// 创建ApplicaionContext对象
protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 判断configurations中是否包含了指定name的配置信息,如果包含,将configuration注册到context上下文中
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
                context.register(configuration);
            }
        }
        // 如果configurations中包含default的配置信息,则将默认的confuration注入
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));
        
        // 判断是否包含了parent context, 如果包含,则为当前context设置parent
        if (this.parent != null) {
            // Uses Environment from parent as well as beans
            context.setParent(this.parent);
            // jdk11 issue
            // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
            context.setClassLoader(this.parent.getClassLoader());
        }
        context.setDisplayName(generateDisplayName(name));
        // 刷新当前容器
        context.refresh();
        return context;
    }

通过以上源码可以看出,在ribbon中做了LoadBalancerClient的配置隔离的,隔离的方式是通过定义子容器的方式来实现。因此,当我们有负载均衡个性化配置时,则可以通过单独为某一个server单独配置的方式,实现个性化。

SpringClientFactory#getInstance()

上面谈到了在获取父类的时候,实际上父类getInstance()方法只是从parentApplicaionContext中获取对应的实例对象,当parent中虎丘实例对象失败时,此时SpringClientFactory则会自己处理特殊情况,具体代码如下:

IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);

可以看出,这里其实是一个递归的调用,会再从parent的ApplicaiontContext中获取IClientConfig的实现Bean。紧接着就会调用instantiateWithConfig方法,我们看下该方法的源码实现:

static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
            Class<C> clazz, IClientConfig config) {
        C result = null;

        try {
            // 这里其实有个注意的点,在所有传入需要获取实例的class对象,都要求必须包含IClientConfig构造函数
            Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
            result = constructor.newInstance(config);
        }
        catch (Throwable e) {
            // Ignored
        }

        if (result == null) {
            // 当不包含IClientConfig构造函数的时候, 则使用默认的构造函数创建对象
            result = BeanUtils.instantiate(clazz);

            // 如果该class对象为IClientConfigAware的实现, 则调用initWithNiwsConfig(config)方法,做类型的初始化
            if (result instanceof IClientConfigAware) {
                ((IClientConfigAware) result).initWithNiwsConfig(config);
            }

            // 当上线问存在时, 执行autowireBean对象
            if (context != null) {
                context.getAutowireCapableBeanFactory().autowireBean(result);
            }
        }

        return result;
    }

RibbonBalancerClient

该类主要是ribbon客户端具体实现,首先看下该类的一个设计结构:

spring clound负载均衡之Ribbon(三)- 工作原理

RibbonBalancerClient中,最重要的依赖就是SpringClientFactory对象,上面我们也提到了,SpringClientFactory中负责创建于Ribbon相关的主要对象。下面我们主要看下RibbonBalancerClient中比较重要的几个方法:

/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.ribbon;

public class RibbonLoadBalancerClient implements LoadBalancerClient {

    private SpringClientFactory clientFactory;

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

        // 重新组装URI信息,该方法会传入instance信息,以及请求的uri信息, 然后组装成为新的URI并返回
    @Override
    public URI reconstructURI(ServiceInstance instance, URI original) {
        Assert.notNull(instance, "instance can not be null");
        // 获取serviceId
        String serviceId = instance.getServiceId();

        // 通过serviceId创建LoadBalancerContext上下文对象
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);

        URI uri;
        Server server;
        // 判断instance是否为RibbonServer
        if (instance instanceof RibbonServer) {
            RibbonServer ribbonServer = (RibbonServer) instance;
            // 从ribbon server中获取服务信息
            server = ribbonServer.getServer();
            // 根据需要将http请求转换为https请求
            uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
        }
        else {
            // 不是RibbonServer对象,则走该逻辑,测试创建Server对象
            server = new Server(instance.getScheme(), instance.getHost(),instance.getPort());

            // 通过SpringClientFactory对象创建ClientConfig对象
            IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);

            // 根据serviceId获取ServerIntrospector对象
            ServerIntrospector serverIntrospector = serverIntrospector(serviceId);

            // 根据需要将Http请求转换为https请求
            uri = updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
        }
       
        // 重新组装URI信息
        return context.reconstructURIWithServer(server, uri);
    }

    // 该方法用于选择合适的ServiceInstance信息
    @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) {

        // 根据serviceId获取Server对象,该对象根据LoadBalancer获取
        Server server = getServer(getLoadBalancer(serviceId), hint);
        if (server == null) {
            return null;
        }

        // 组装成为RibbonServer对象
        return new RibbonServer(serviceId, server, isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));
    }

    // 执行请求
    @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 {

        // 获取LoadBalancer对象
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

        // 获取Server对象
        Server server = getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }

        // 组装成为RibbonServer对象
        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);
        }

        // 获取Ribbon LoadBalancer上下文对象
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
        // Ribbon 状态记录器
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        try {
            // 执行请求
            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;
    }

    // 创建ServerIntrospector对象
    private ServerIntrospector serverIntrospector(String serviceId) {
        // 通过SpringClientFactory创建ServerIntrospector对象
        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);
    }

    // 获取需要调用的Server对象,对象通过LoadBalancer进行选择
    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");
    }

    // 获取LoadBalancer对象
    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();
        }

    }

}

通过分析我们可以知道,其实在RibbonLoadBalancerClient中,在执行请求时,主要通过ILoadBalance选择合适的ServerInstance,然后执行远程请求。

PropertiesFactory

该类是作为配置类型的存在,当对单个serviceId有特殊需求的时候,此时我们将会用到这里面对的配置项,首先我们直接上源码:

/*
 * Copyright 2016-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.ribbon;

/**
 * @author Spencer Gibb
 */
public class PropertiesFactory {

    @Autowired
    private Environment environment;

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

    // class到配置之间的映射关系
    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) {
        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;
    }

}

RibbonClientHttpRequestFactory

该类需要配和RestTemplate来说会用的类,主要用于创建HttpRequest对象,我们查看源码:

public class RibbonClientHttpRequestFactory implements ClientHttpRequestFactory {

    private final SpringClientFactory clientFactory;

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

    @Override
    @SuppressWarnings("deprecation")
    public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod)
            throws IOException {
        // 此处需要注意,当我们通过LoadBalancer客户端调用的时候,host所代表的其实是对应的serviceId信息
        String serviceId = originalUri.getHost();
        if (serviceId == null) {
            throw new IOException(
                    "Invalid hostname in the URI [" + originalUri.toASCIIString() + "]");
        }

        // 获取客户端配置信息
        IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
        // 创建RestClient对象
        RestClient client = this.clientFactory.getClient(serviceId, RestClient.class);

        // 创建Verb对象, 其实这里的Verb对应的就是http method的枚举值
        HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());

        // 创建RibbonHttpRequest对象
        return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
    }

}

因此该类主要是用于创建RibbonHttpRequest对象,处理http请求信息.

RestTemplateCusomizer

该类是作为RestTemplate的重要扩展使用,在Ribbon的默认启动中,会配合RibbonClientHttpRequestFactory来使用。具体可以看下配置方法:

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

LoadBalancerAutoConfiguration

这个类是一个配置类型,这里单独拿出来,是因为上面的RestTemplateCustomizer需要在该类中进行使用,具体源码如下:

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

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

在该源码中,有几个需要注意的点:

  • 并不是所有的RestTemplate都会被方法执行,因为RestTemplate必须要被@LoadBalanced派生注解
  • SmartInitializingSingleton是一个特殊的类型,该类型会在bean的声明周期中,pre-instantiation之后执行的语义

LoadBalancerRequestFactory

该类主要用于创建LoadBalancerRequest对象,其中设计到对请求参数transformer操作,具体源码如下:

public class LoadBalancerRequestFactory {

    private LoadBalancerClient loadBalancer;

    private List<LoadBalancerRequestTransformer> transformers;

    public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer,
            List<LoadBalancerRequestTransformer> transformers) {
        this.loadBalancer = loadBalancer;
        this.transformers = transformers;
    }

    public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public LoadBalancerRequest<ClientHttpResponse> createRequest(
            final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) {
        return instance -> {
            HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer);
            if (this.transformers != null) {
                for (LoadBalancerRequestTransformer transformer : this.transformers) {
                    // 通过transformRequest进行处理
                    serviceRequest = transformer.transformRequest(serviceRequest,instance);
                }
            }
            return execution.execute(serviceRequest, body);
        };
    }

}

RibbonEurekaClientConfiguration

该类也是很重要的一个类,为什么这么说呢,其实重要的一点,在于该类定义了默认的Ribbon在工作时,所需要的IPing, IRule, IConfig的配置bean信息,具体源码如下:

package org.springframework.cloud.netflix.ribbon.eureka;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;

/**
 * Spring configuration for configuring Ribbon defaults to be Eureka based if Eureka
 * client is enabled.
 *
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {

}

s可以看到,该配置类型被@RibbonClients定义,而从上面的解析可以得知,RibbonClients最终会被定义为RibbonClientSpecification类型的bean, 因此,在defaultConfiguration的配置中,会默认加载EurekaRibbonClientConfiguration中的配置到NamedContextFactory的子context中,因此我们看下对应的配置类型。

EurekaRibbonClientConfiguration

package org.springframework.cloud.netflix.ribbon.eureka;


/**
 * Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as:
 * <code>@zone</code>, NIWSServerListClassName, DeploymentContextBasedVipAddresses,
 * NFLoadBalancerRuleClassName, NIWSServerListFilterClassName and more
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 */
@Configuration
public class EurekaRibbonClientConfiguration {

    private static final Log log = LogFactory.getLog(EurekaRibbonClientConfiguration.class);

    @Value("${ribbon.eureka.approximateZoneFromHostname:false}")
    private boolean approximateZoneFromHostname = false;

    @RibbonClientName
    private String serviceId = "client";

    @Autowired(required = false)
    private EurekaClientConfig clientConfig;

    @Autowired(required = false)
    private EurekaInstanceConfig eurekaConfig;

    @Autowired
    private PropertiesFactory propertiesFactory;

    public EurekaRibbonClientConfiguration() {
    }

    public EurekaRibbonClientConfiguration(EurekaClientConfig clientConfig,
            String serviceId, EurekaInstanceConfig eurekaConfig,
            boolean approximateZoneFromHostname) {
        this.clientConfig = clientConfig;
        this.serviceId = serviceId;
        this.eurekaConfig = eurekaConfig;
        this.approximateZoneFromHostname = approximateZoneFromHostname;
    }

    // 当IPing的bean确实的时候,则使用NIWSDiscoveryPing()代替
    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
            return this.propertiesFactory.get(IPing.class, config, serviceId);
        }
        NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
        ping.initWithNiwsConfig(config);
        return ping;
    }

    // 当ServerList的bean在缺失的时候,则使用ServerList代替
    @Bean
    @ConditionalOnMissingBean
    public ServerList<?> ribbonServerList(IClientConfig config,
            Provider<EurekaClient> eurekaClientProvider) {
        if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
            return this.propertiesFactory.get(ServerList.class, config, serviceId);
        }
        DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
                config, eurekaClientProvider);
        DomainExtractingServerList serverList = new DomainExtractingServerList(
                discoveryServerList, config, this.approximateZoneFromHostname);
        return serverList;
    }

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

    @PostConstruct
    public void preprocess() {
        String zone = ConfigurationManager.getDeploymentContext()
                .getValue(ContextKey.zone);
        if (this.clientConfig != null && StringUtils.isEmpty(zone)) {
            if (this.approximateZoneFromHostname && this.eurekaConfig != null) {
                String approxZone = ZoneUtils
                        .extractApproximateZone(this.eurekaConfig.getHostName(false));
                log.debug("Setting Zone To " + approxZone);
                ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone,
                        approxZone);
            }
            else {
                String availabilityZone = this.eurekaConfig == null ? null
                        : this.eurekaConfig.getMetadataMap().get("zone");
                if (availabilityZone == null) {
                    String[] zones = this.clientConfig
                            .getAvailabilityZones(this.clientConfig.getRegion());
                    // Pick the first one from the regions we want to connect to
                    availabilityZone = zones != null && zones.length > 0 ? zones[0]
                            : null;
                }
                if (availabilityZone != null) {
                    // You can set this with archaius.deployment.* (maybe requires
                    // custom deployment context)?
                    ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone,
                            availabilityZone);
                }
            }
        }
        RibbonUtils.initializeRibbonDefaults(serviceId);
    }

}

s在以上代码中,我们可以知道对于部分的代码,默认的使用类型:

 

 

 

 

配置 默认值 加载位置
IPing NIWSDiscoveryPing EurekaRibbonClientConfiguration
ServerList DomainExtractingServerList EurekaRibbonClientConfiguration
ILoadBalancer ZoneAwareLoadBalancer RibbonClientAutoConfiguration
IRule ZoneAvoidanceRule RibbonClientAutoConfiguration
ServerListUpdater PollingServerListUpdater RibbonClientAutoConfiguration
ServerListFileter ZonePreferenceServerListFilter RibbonClientAutoConfiguration

以上就是Ribbon重要类型的工作原理,后面我们将讲述RestTemplate如何配合Ribbon组件进行负载均衡,更好的实现负载均衡功能。


Compiler编程笔记 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:spring clound负载均衡之Ribbon(三)- 工作原理
喜欢 (2)
[阳光路上]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址