前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud Ribbon 全解 (6) - SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析

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

作者头像
干货满满张哈希
发布2021-04-12 15:05:18
5330
发布2021-04-12 15:05:18
举报
文章被收录于专栏:干货满满张哈希

本文基于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:

代码语言:javascript
复制
@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:

代码语言:javascript
复制
@RestController
@SpringBootApplication
public class TestService {
    @RequestMapping("/test")
    public String test() {
        throw new IllegalArgumentException();
    }
    public static void main(String[] args) {
        SpringApplication.run(TestService.class, args);
    }
}

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

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-ribbonartifactId>
dependency>



<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-actuatorartifactId>
dependency>

定义一个RibbonClient:

代码语言:javascript
复制
@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这个微服务:

代码语言:javascript
复制
@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:

代码语言:javascript
复制
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

启动:

代码语言:javascript
复制
@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注解的源码:

代码语言:javascript
复制
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

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

代码语言:javascript
复制
@LoadBalanced
@Autowired(required = false)
private List restTemplates = Collections.emptyList();

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

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

代码语言:javascript
复制
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

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

    //SmartInitializingSingleton代表所有非lazy单例Bean实例化完成后的回调方法,即所有被LoadBalanced修饰的restTemplates会被初始化并装载到这里之后,这个afterSingletonsInstantiated会被回调
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
            final List 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 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 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

代码语言:javascript
复制
@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的配置是懒加载的:

代码语言:javascript
复制
public  T execute(String serviceId, LoadBalancerRequest 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。

总结下,流程大概就是如下图所示:

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

代码语言:javascript
复制
@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 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 serverList, ServerListFilter 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 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配置:

代码语言:javascript
复制
# ribbon连接超时
default-test.ribbon.ConnectTimeout=500
# ribbon读超时
default-test.ribbon.ReadTimeout=8000

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

代码语言:javascript
复制
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:

代码语言:javascript
复制
public class PropertiesFactory {
    @Autowired
    private Environment environment;

    private Map 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 get(Class 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,或者通过配置文件中添加如下配置实现:

代码语言:javascript
复制
#配置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=
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/05/16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析:
    • 示例项目
      • 示例项目分析
        • SpringCloud环境下纯Ribbon配置分析
        相关产品与服务
        负载均衡
        负载均衡(Cloud Load Balancer,CLB)提供安全快捷的四七层流量分发服务,访问流量经由 CLB 可以自动分配到多台后端服务器上,扩展系统的服务能力并消除单点故障。轻松应对大流量访问场景。 网关负载均衡(Gateway Load Balancer,GWLB)是运行在网络层的负载均衡。通过 GWLB 可以帮助客户部署、扩展和管理第三方虚拟设备,操作简单,安全性强。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档