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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: Spring Cloud Ribbon 全解 (6) - SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析

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

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


微信图片_20220624113452.jpg


这里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=
相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
Cloud Native Java 对象存储
面向未来的架构设计:Spring Cloud和Netflix OSS在云原生环境下的发展趋势
展望未来,随着5G、边缘计算等新技术的兴起,微服务架构的设计理念将会更加深入人心,Spring Cloud和Netflix OSS也将继续引领技术潮流,为企业带来更为高效、灵活且强大的解决方案。无论是对于初创公司还是大型企业而言,掌握这些前沿技术都将是在激烈市场竞争中脱颖而出的关键所在。
64 0
|
16天前
|
负载均衡 监控 网络协议
SpringCloud之Ribbon使用
通过以上步骤,就可以在Spring Cloud项目中有效地使用Ribbon来实现服务调用的负载均衡,提高系统的可靠性和性能。在实际应用中,根据具体的业务场景和需求选择合适的负载均衡策略,并进行相应的配置和优化,以确保系统的稳定运行。
40 15
|
12天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
24 2
|
16天前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
39 5
|
1月前
|
Cloud Native Java 对象存储
面向未来的架构设计:Spring Cloud和Netflix OSS在云原生环境下的发展趋势
面向未来的架构设计:Spring Cloud和Netflix OSS在云原生环境下的发展趋势
48 1
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
113 5
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
3月前
|
负载均衡 算法 Java
SpringCloud之Ribbon使用
通过 Ribbon,可以非常便捷的在微服务架构中实现请求负载均衡,提升系统的高可用性和伸缩性。在实际使用中,需要根据实际场景选择合适的负载均衡策略,并对其进行适当配置,以达到更佳的负载均衡效果。
71 13
|
5月前
|
负载均衡 算法 Java
Spring Cloud Netflix 之 Ribbon
Spring Cloud Netflix Ribbon是客户端负载均衡器,用于在微服务架构中分发请求。它与RestTemplate结合,自动在服务发现(如Eureka)注册的服务之间进行调用。配置包括在pom.xml中添加依赖,设置application.yml以连接Eureka服务器,并在配置类中创建@LoadBalanced的RestTemplate。通过这种方式,当调用如`/user/userInfoList`的接口时,Ribbon会自动处理到多个可用服务实例的负载均衡。
|
6月前
|
JSON 负载均衡 Java
Spring Cloud Ribbon:负载均衡的服务调用
Spring Cloud Ribbon:负载均衡的服务调用
104 0
下一篇
无影云桌面