16. Client 端负载均衡器:功能区

Ribbon 是 Client 端负载平衡器,可让您对 HTTP 和 TCPClient 端的行为进行大量控制。 Feign 已经使用了 Ribbon,因此,如果您使用@FeignClient,则此部分也适用。

Ribbon 中的中心概念是指定 Client 端的概念。每个负载均衡器都是组件的一部分,这些组件可以一起工作以按需联系远程服务器,并且该组件具有您作为应用程序开发人员提供的名称(例如,使用@FeignClient注解)。根据需要,Spring Cloud 通过使用RibbonClientConfiguration为每个命名的 Client 端创建一个新的集合作为ApplicationContext。其中包含ILoadBalancerRestClientServerListFilter

16.1 如何包括功能区

要将 Ribbon 包含在您的项目中,请使用组 ID 为org.springframework.cloud且工件 ID 为spring-cloud-starter-netflix-ribbon的启动器。有关使用当前 Spring Cloud Release Train 设置构建系统的详细信息,请参见Spring Cloud Project 页面

16.2 自定义功能区 Client 端

您可以使用<client>.ribbon.*中的外部属性来配置 RibbonClient 端的某些位,这与本机使用 Netflix API 相似,不同之处在于可以使用 Spring Boot 配置文件。可以将本地选项作为CommonClientConfigKey(功能区核心的一部分)中的静态字段进行检查。

Spring Cloud 还允许您通过使用@RibbonClient声明其他配置(在RibbonClientConfiguration之上)来完全控制 Client 端,如以下示例所示:

@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}

在这种情况下,Client 端由RibbonClientConfiguration中已经存在的组件以及CustomConfiguration中的任何组件组成(其中后者通常会覆盖前者)。

Warning

CustomConfiguration clas 必须是@Configuration类,但请注意,对于主应用程序上下文,它不在@ComponentScan中。否则,它由所有@RibbonClients共享。如果使用@ComponentScan(或@SpringBootApplication),则需要采取措施避免将其包括在内(例如,可以将其放在单独的,不重叠的程序包中,或指定要在@ComponentScan中显式扫描的程序包)。

下表显示了 Spring Cloud Netflix 默认为 Ribbon 提供的 bean:

Bean TypeBean NameClass Name
IClientConfigribbonClientConfigDefaultClientConfigImpl
IRuleribbonRuleZoneAvoidanceRule
IPingribbonPingDummyPing
ServerList<Server>ribbonServerListConfigurationBasedServerList
ServerListFilter<Server>ribbonServerListFilterZonePreferenceServerListFilter
ILoadBalancerribbonLoadBalancerZoneAwareLoadBalancer
ServerListUpdaterribbonServerListUpdaterPollingServerListUpdater

创建其中一种类型的 bean 并将其放置在@RibbonClient配置(例如上面的FooConfiguration)中,可以覆盖所描述的每个 bean,如以下示例所示:

@Configuration
protected static class FooConfiguration {
	@Bean
	public ZonePreferenceServerListFilter serverListFilter() {
		ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
		filter.setZone("myTestZone");
		return filter;
	}

	@Bean
	public IPing ribbonPing() {
		return new PingUrl();
	}
}

前面示例中的 include 语句将NoOpPing替换为PingUrl并提供了自定义serverListFilter

16.3 自定义所有功能区 Client 端的默认设置

通过使用@RibbonClientsComments 并注册默认配置,可以为所有功能区 Client 端提供默认配置,如以下示例所示:

@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
public class RibbonClientDefaultConfigurationTestsConfig {

	public static class BazServiceList extends ConfigurationBasedServerList {
		public BazServiceList(IClientConfig config) {
			super.initWithNiwsConfig(config);
		}
	}
}

@Configuration
class DefaultRibbonConfig {

	@Bean
	public IRule ribbonRule() {
		return new BestAvailableRule();
	}

	@Bean
	public IPing ribbonPing() {
		return new PingUrl();
	}

	@Bean
	public ServerList<Server> ribbonServerList(IClientConfig config) {
		return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
	}

	@Bean
	public ServerListSubsetFilter serverListFilter() {
		ServerListSubsetFilter filter = new ServerListSubsetFilter();
		return filter;
	}

}

16.4 通过设置属性来自定义功能区 Client 端

从 1.2.0 版开始,Spring Cloud Netflix 现在支持通过将属性设置为与Ribbon documentation兼容来自定义 RibbonClient 程序。

这使您可以在启动时在不同环境中更改行为。

以下列表显示了受支持的属性>:

  • <clientName>.ribbon.NFLoadBalancerClassName:应实施ILoadBalancer

  • <clientName>.ribbon.NFLoadBalancerRuleClassName:应实施IRule

  • <clientName>.ribbon.NFLoadBalancerPingClassName:应实施IPing

  • <clientName>.ribbon.NIWSServerListClassName:应实施ServerList

  • <clientName>.ribbon.NIWSServerListFilterClassName:应实施ServerListFilter

Note

这些属性中定义的类优先于使用@RibbonClient(configuration=MyRibbonConfig.class)定义的 bean 和 Spring Cloud Netflix 提供的默认值。

要为名为users的服务名称设置IRule,可以设置以下属性:

application.yml.

users:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

有关功能区提供的实现,请参见Ribbon documentation

16.5 将功能区与 Eureka 一起使用

当 Eureka 与 Ribbon 结合使用时(也就是说,两者都在 Classpath 上),则ribbonServerList被 extensionsDiscoveryEnabledNIWSServerList覆盖,该 extensions 填充了 Eureka 中的服务器列表。它还用NIWSDiscoveryPing替换了IPing接口,该接口委托 Eureka 确定服务器是否启动。默认情况下安装的ServerListDomainExtractingServerList。其目的是在不使用 AWS AMI 元数据(Netflix 依靠它)的情况下使元数据可用于负载平衡器。默认情况下,服务器列表是使用实例元数据中提供的“区域”信息构造的(因此,在远程 Client 端上,设置eureka.instance.metadataMap.zone)。如果缺少该字段,并且设置了approximateZoneFromHostname标志,则它可以使用服务器主机名中的域名作为该区域的代理。一旦区域信息可用,就可以在ServerListFilter中使用它。默认情况下,它用于将服务器定位在与 Client 端相同的区域中,因为默认值为ZonePreferenceServerListFilter。默认情况下,以与远程实例相同的方式(即,通过eureka.instance.metadataMap.zone)确定 Client 端的区域。

Note

设置 Client 端区域的传统“ archaius”方法是通过名为“ @zone”的配置属性。如果可用,Spring Cloud 会优先使用所有其他设置(请注意,密钥必须在 YAML 配置中用引号引起来)。

Note

如果没有其他区域数据源,则根据 Client 端配置(而不是实例配置)进行猜测。我们采用eureka.client.availabilityZones(这是从区域名称到区域列表的 Map),然后为实例自己的区域拉出第一个区域(即eureka.client.region(默认为“ us-east-1”,以与本机兼容) Netflix)。

16.6 示例:如何在没有 Eureka 的情况下使用 Ribbon

Eureka 是一种抽象发现远程服务器的便捷方法,因此您不必在 Client 端中对它们的 URL 进行硬编码。但是,如果您不想使用 Eureka,Ribbon 和 Feign 也可以使用。假设您已经为“Store”声明了@RibbonClient,并且 Eureka 未被使用(甚至不在 Classpath 上)。功能区 Client 端默认为配置的服务器列表。您可以提供以下配置:

application.yml.

stores:
  ribbon:
    listOfServers: example.com,google.com

16.7 示例:在功能区中禁用 Eureka 使用

ribbon.eureka.enabled属性设置为false会显式禁用功能区中的 Eureka,如以下示例所示:

application.yml.

ribbon:
  eureka:
   enabled: false

16.8 直接使用 Ribbon API

您也可以直接使用LoadBalancerClient,如以下示例所示:

public class MyClass {
    @Autowired
    private LoadBalancerClient loadBalancer;

    public void doStuff() {
        ServiceInstance instance = loadBalancer.choose("stores");
        URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
        // ... do something with the URI
    }
}

16.9 缓存功能区配置

每个命名为 Ribbon 的 Client 端都有一个 Spring Cloud 维护的对应子应用程序上下文。该应用程序上下文在对命名 Client 端的第一个请求上延迟加载。通过指定功能区 Client 端的名称,可以更改此延迟加载行为,以代替在启动时急于加载这些子应用程序上下文,如以下示例所示:

application.yml.

ribbon:
  eager-load:
    enabled: true
    clients: client1, client2, client3

16.10 如何配置 Hystrix 线程池

如果将zuul.ribbonIsolationStrategy更改为THREAD,则 Hystrix 的线程隔离策略将用于所有路由。在这种情况下,HystrixThreadPoolKey默认设置为RibbonCommand。这意味着所有路由的 HystrixCommands 在同一个 Hystrix 线程池中执行。可以使用以下配置更改此行为:

application.yml.

zuul:
  threadPool:
    useSeparateThreadPools: true

前面的示例导致在 Hystrix 线程池中为每个路由执行 HystrixCommands。

在这种情况下,默认的HystrixThreadPoolKey与每个路由的服务 ID 相同。要将前缀添加到HystrixThreadPoolKey,请将zuul.threadPool.threadPoolKeyPrefix设置为要添加的值,如以下示例所示:

application.yml.

zuul:
  threadPool:
    useSeparateThreadPools: true
    threadPoolKeyPrefix: zuulgw

16.11 如何为功能区的 IRule 提供密钥

如果您需要提供自己的IRule实现来处理特殊的路由要求(例如“ canary”测试),请将一些信息传递给IRulechoose方法。

com.netflix.loadbalancer.IRule.java.

public interface IRule{
    public Server choose(Object key);
         :

您可以提供一些信息,供IRule实现用于选择目标服务器,如以下示例所示:

RequestContext.getCurrentContext()
              .set(FilterConstants.LOAD_BALANCER_KEY, "canary-test");

如果您使用FilterConstants.LOAD_BALANCER_KEY键将任何对象放入RequestContext,则该对象将传递到IRule实现的choose方法。上例中显示的代码必须在执行RibbonRoutingFilter之前执行。 Zuul 的前置filter是执行此操作的最佳位置。您可以通过Pre filter中的RequestContext访问 HTTPHeaders 和查询参数,因此可以用来确定传递到功能区的LOAD_BALANCER_KEY。如果您没有在RequestContext中使用LOAD_BALANCER_KEY放置任何值,则将 NULL 作为choose方法的参数传递。