Spring Cloud Ribbon 全解 (6) – SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

本文基于SpringCloud-Dalston.SR5

前面已经分析了Ribbon各个组件详细的源码,以及整体的流程

SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析:

示例项目

以下项目可以参考:https://github.com/HashZhang/ScanfoldAll/tree/master/Scanfold-SpringCloud/Scanfold-SpringCloud-Ribbon/Scanfold-SpringCloud-RibbonOnly

我们首先在127.0.0.1:8222,127.0.0.1:8221启动两个进程,一个是正常工作的127.0.0.1:8221:

    @RestController
    @SpringBootApplication
    public class TestService {
        @RequestMapping("/test")
        public String test() {
            return "test";
        }
        public static void main(String[] args) {
            SpringApplication.run(TestService.class, args);
        }
    }

令一个是一定会抛出异常(http响应码为500)的127.0.0.1:8222:

    @RestController
    @SpringBootApplication
    public class TestService {
        @RequestMapping("/test")
        public String test() {
            throw new IllegalArgumentException();
        }
        public static void main(String[] args) {
            SpringApplication.run(TestService.class, args);
        }
    }

之后在我们的项目中引入依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
    </dependency>

    <!--以下依赖是启动Spring cloud应用需要-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

定义一个RibbonClient:

    @Configuration
    @RibbonClient(name = "default-test")
    public class DefaultConfiguration {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }

在这个Configuration中,我们定义了一个名为“default-test”的RibbonClient(就是IClient),一个负载均衡的RestTemplate(因为@LoadBalanced注解的缘故,普通的Restemplate变成了可以负载均衡的RestTemplate,之后我们会分析这个注解的作用)

再定义一个Service,让它定时调用default-test这个微服务:

    @Service
    public class DefaultService {
        @Autowired
        private RestTemplate restTemplate;

        @Scheduled(fixedDelay = 5000)
        public void testDefaultRibbon() {
            String forObject = restTemplate.getForObject("http://default-test/test", String.class);

            System.out.println("**********************");
            System.out.println(forObject);
        }
    }

编写application.properties:

    default-test.ribbon.listOfServers=127.0.0.1:8222,127.0.0.1:8221

    # ribbon连接超时
    default-test.ribbon.ConnectTimeout=500
    # ribbon读超时
    default-test.ribbon.ReadTimeout=8000

    ######## management ########
    management.security.enabled=false
    endpoints.health.sensitive=false

启动:

    @EnableScheduling
    @SpringBootApplication
    public class Main {
        public static void main(String[] args) {
            SpringApplication.run(Main.class, args);
        }
    }

我们可以观察到127.0.0.1:8222,127.0.0.1:8221被依次调用。

示例项目分析

@LoadBalanced注解的源码:

    @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Qualifier
    public @interface LoadBalanced {
    }

注意有@Qualifier这个注解,这个注解用在另外一个注解上(这里是LoadBalanced)时,代表所有含有@LoadBalanced注解的bean都会被标记起来,如果这时有:

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

代表所有被LoadBalanced修饰的restTemplates会被装载到这里

在我们的应用启动时,LoadBalancerAutoConfiguration会给所有被@LoadBalanced注解修饰的RestTemplate增加一个LoadBalanceInterceptor:

    @Configuration
    @ConditionalOnClass(RestTemplate.class)
    @ConditionalOnBean(LoadBalancerClient.class)
    @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
    public class LoadBalancerAutoConfiguration {

        @LoadBalanced
        @Autowired(required = false)
        //所有被LoadBalanced修饰的restTemplates会被装载到这里
        private List<RestTemplate> restTemplates = Collections.emptyList();

        //SmartInitializingSingleton代表所有非lazy单例Bean实例化完成后的回调方法,即所有被LoadBalanced修饰的restTemplates会被初始化并装载到这里之后,这个afterSingletonsInstantiated会被回调
        @Bean
        public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
                final List<RestTemplateCustomizer> customizers) {
            return new SmartInitializingSingleton() {
                @Override
                public void afterSingletonsInstantiated() {
                    //所有restTemplate被customizers处理
                    for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                        for (RestTemplateCustomizer customizer : customizers) {
                            customizer.customize(restTemplate);
                        }
                    }
                }
            };
        }

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

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancerRequestFactory loadBalancerRequestFactory(
                LoadBalancerClient loadBalancerClient) {
            return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
        }

        //在没有包含spring-retry这个依赖时,以下会被初始化,我们上面的项目就是没有加入spring-retry这个依赖
        @Configuration
        @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
        static class LoadBalancerInterceptorConfig {

            //初始化LoadBalancerInterceptor
            @Bean
            public LoadBalancerInterceptor ribbonInterceptor(
                    LoadBalancerClient loadBalancerClient,
                    LoadBalancerRequestFactory requestFactory) {
                return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
            }

            //这个RestTemplateCustomizer就是为restTemplate添加一个LoadBalancerInterceptor
            @Bean
            @ConditionalOnMissingBean
            public RestTemplateCustomizer restTemplateCustomizer(
                    final LoadBalancerInterceptor loadBalancerInterceptor) {
                return new RestTemplateCustomizer() {
                    @Override
                    public void customize(RestTemplate restTemplate) {
                        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                                restTemplate.getInterceptors());
                        list.add(loadBalancerInterceptor);
                        restTemplate.setInterceptors(list);
                    }
                };
            }
        }

        //在包含spring-retry这个依赖时,以下会被初始化,之后我们会讲这个重试
        @Configuration
        @ConditionalOnClass(RetryTemplate.class)
        public static class RetryAutoConfiguration {
            @Bean
            public RetryTemplate retryTemplate() {
                RetryTemplate template =  new RetryTemplate();
                template.setThrowLastExceptionOnExhausted(true);
                return template;
            }

            @Bean
            @ConditionalOnMissingBean
            public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
                return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
            }
        }

    }

在执行请求(restTemplate.getForObject("http://default-test/test", String.class);)时,会先经过所有的Interceptor,其中这个LoadBalanceInterceptor,其实就是利用loadBalancer将原请求转化成一个负载均衡请求并执行:

LoadBalancerInterceptor.java

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        //利用loadBalancer将原请求转化成一个负载均衡请求并执行
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }

RibbonLoadBalancerClient是SpringCloud对于Ribbon的封装,在这里会初始化Ribbon的配置,所以其实Ribbon的配置是懒加载的:

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        //根据serviceId(这里是default-test)获取loadBalancer,如果不存在就创建
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        //从LoadBalancer中获取一个Server
        Server server = getServer(loadBalancer);
        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);
    }

其实这里我们就能看出,Ribbon在这里只是提供一个Server,之后的执行请求并不是Ribbon的IClient负责的。所以,我们之前讨论的Ribbon8大元素,在SpringCloud的环境下,其实只用到了其中七个。SpringCloud实现了自己的负载均衡器RibbonLoadBalancerClient。

总结下,流程大概就是如下图所示:
2019102020024\_1.png

这里getLoadBalancer是初始化RibbonClientConfiguration这个懒加载的Configuration:

    @SuppressWarnings("deprecation")
    @Configuration
    @EnableConfigurationProperties
    //Order is important here, last should be the default, first should be optional
    // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
    @Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
    public class RibbonClientConfiguration {

        //Ribbonclient名称,这里是default-test
        @Value("${ribbon.client.name}")
        private String name = "client";

        @Autowired
        private PropertiesFactory propertiesFactory;

        //从配置文件(application.properties)中读取default-test这个RibbonClient的配置
        @Bean
        @ConditionalOnMissingBean
        public IClientConfig ribbonClientConfig() {
            DefaultClientConfigImpl config = new DefaultClientConfigImpl();
            config.loadProperties(this.name);
            return config;
        }

        //如果没设置,IRule默认为ZoneAvoidanceRule
        @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;
        }
        //如果没设置,IPing默认为DummyPing
        @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();
        }
        //如果没设置,ServerList默认为ConfigurationBasedServerList
        @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;
        }

        //ServerListUpdater是PollingServerListUpdater,这个只能通过Bean替换,不能通过配置文件配置
        @Bean
        @ConditionalOnMissingBean
        public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
            return new PollingServerListUpdater(config);
        }

        //如果没设置,ILoadBalancer默认为ZoneAwareLoadBalancer
        @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);
        }

        //如果没设置,ServerListFilter默认为ZonePreferenceServerListFilter
        @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 = updateToHttpsIfNeeded(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;
            }

        }

    }

SpringCloud环境下纯Ribbon配置分析

总结起来,在Spring Cloud环境下Ribbon默认的实现类如下所示:

  1. 服务实例列表维护机制实现的接口ServerList: ConfigurationBasedServerList
  2. 负责选取Server的接口ILoadBalancer:ZoneAwareLoadBalancer
  3. 负载均衡选取规则实现的接口IRule:ZoneAvoidanceRule
  4. 检查实例是否存活实现的接口IPing:DummyPing
  5. 服务实例列表更新机制实现的接口ServerListUpdater:PollingServerListUpdater
  6. 服务实例列表过滤机制ServerListFilter:ZonePreferenceServerListFilter

Ribbon的配置有很多,例如上面我们用到的ribbon配置:

    # ribbon连接超时
    default-test.ribbon.ConnectTimeout=500
    # ribbon读超时
    default-test.ribbon.ReadTimeout=8000

这些配置是基于Archaius的,我们可以看DefaultClientConfigImpl这个类:

    public void loadProperties(String restClientName){
        enableDynamicProperties = true;
        setClientName(restClientName);
        //载入默认值
        loadDefaultValues();
        //载入配置值,包括“微服务名.ribbon.配置”和“ribbon.配置”这两种的所有配置
        Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
        for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
            String key = keys.next();
            String prop = key;
            try {
                if (prop.startsWith(getNameSpace())){
                    prop = prop.substring(getNameSpace().length() + 1);
                }
                setPropertyInternal(prop, getStringValue(props, key));
            } catch (Exception ex) {
                throw new RuntimeException(String.format("Property %s is invalid", prop));
            }
        }
    }

像这种配置,是基于Archaius的,可以是微服务名.ribbon.配置这么去配置,或者ribbon.配置这么去配置。

但是像默认实现类的配置,只能通过微服务名.ribbon.配置这么去配(通过PropertiesFactory配置的),或者通过自定义同类型Bean去替换:

PropertiesFactory.java:

    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) {
            String className = getClassName(clazz, name);
            if (StringUtils.hasText(className)) {
                try {
                    Class<?> toInstantiate = Class.forName(className);
                    return (C) instantiateWithConfig(toInstantiate, config);
                } catch (ClassNotFoundException e) {
                    throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name);
                }
            }
            return null;
        }
    }

修改这些默认实现类Ribbon的配置我们可以通过在自己的configuration添加对应的Bean,或者通过配置文件中添加如下配置实现:


    #配置ILoadBalancer
    default-test.ribbon.NFLoadBalancerClassName=
    #配置IPing
    default-test.ribbon.NFLoadBalancerPingClassName=
    #配置IRule
    default-test.ribbon.NFLoadBalancerRuleClassName=
    #配置ServerList
    default-test.ribbon.NIWSServerListClassName=
    #配置ServerListFilter
    default-test.ribbon.NIWSServerListFilterClassName=
赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Cloud Ribbon 全解 (6) – SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析

评论 抢沙发

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

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏