16. Client Side Load Balancer:Ribbon

Ribbon 是一个 client-side 负载均衡器,可以让您对 HTTP 和 TCP 客户端的行为进行大量控制。 Feign 已使用 Ribbon,因此,如果您使用@FeignClient,则此部分也适用。

Ribbon 中的一个核心概念是名为 client 的概念。每个负载均衡器都是一组组件的一部分,这些组件一起工作以按需联系 remote 服务器,并且整体具有 name,您可以将其作为 application 开发人员提供(对于 example,使用@FeignClient annotation)。根据需要,Spring Cloud 通过使用RibbonClientConfiguration为每个命名的 client 创建一个新的集合ApplicationContext。这包含(除其他外)ILoadBalancerRestClientServerListFilter

16.1 如何包含 Ribbon

要在项目中包含 Ribbon,请使用 group ID 为org.springframework.cloud且 artifact ID 为spring-cloud-starter-netflix-ribbon的 starter。有关使用当前 Spring Cloud Release Train 设置 build 系统的详细信息,请参阅Spring Cloud 项目页面

16.2 自定义 Ribbon Client

您可以使用<client>.ribbon.*中的外部 properties 配置 Ribbon client 的某些位,这类似于本机使用 Netflix API,但您可以使用 Spring Boot configuration files。可以在CommonClientConfigKey(ribbon-core 的一部分)中将本机选项作为静态字段进行检查。

Spring Cloud 还允许您通过使用@RibbonClient声明其他 configuration(在RibbonClientConfiguration之上)来完全控制 client,如下面的示例所示:

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

在这种情况下,client 由RibbonClientConfiguration中的组件和CustomConfiguration中的任何组件组成(后者通常会覆盖前者)。

CustomConfiguration clas 必须是@Configuration class,但要注意它不在主 application context 的@ComponentScan中。否则,所有@RibbonClients共享它。如果使用@ComponentScan(或@SpringBootApplication),则需要采取措施以避免包含它(例如,您可以将其放在单独的 non-overlapping 包中,或指定要在@ComponentScan中显式扫描的包)。

以下 table 显示 Spring Cloud Netflix 默认为 Ribbon 提供的 beans:

Bean 类型Bean Name班级名称
IClientConfigribbonClientConfigDefaultClientConfigImpl
IRuleribbonRuleZoneAvoidanceRule
IPingribbonPingDummyPing
ServerList<Server>ribbonServerListConfigurationBasedServerList
ServerListFilter<Server>ribbonServerListFilterZonePreferenceServerListFilter
ILoadBalancerribbonLoadBalancerZoneAwareLoadBalancer
ServerListUpdaterribbonServerListUpdaterPollingServerListUpdater

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

@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();
	}
}

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

16.3 自定义所有 Ribbon Clients 的默认值

可以使用@RibbonClients annotation 并注册默认 configuration 为所有 Ribbon Clients 提供默认的 configuration,如下面的示例所示:

@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 通过设置 Properties 自定义 Ribbon Client

从 version 1.2.0 开始,Spring Cloud Netflix 现在支持通过将 properties 设置为与Ribbon 文档兼容来自定义 Ribbon clients。

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

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

  • <clientName>.ribbon.NFLoadBalancerClassName:应该实现ILoadBalancer

  • <clientName>.ribbon.NFLoadBalancerRuleClassName:应该实现IRule

  • <clientName>.ribbon.NFLoadBalancerPingClassName:应该实现IPing

  • <clientName>.ribbon.NIWSServerListClassName:应该实现ServerList

  • <clientName>.ribbon.NIWSServerListFilterClassName:应该实现ServerListFilter

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

要为名为users的服务 name 设置IRule,可以设置以下 properties:

application.yml.

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

有关 Ribbon 提供的 implementations,请参阅Ribbon 文档

16.5 在 Eureka 中使用 Ribbon

当 Eureka 与 Ribbon 一起使用时(也就是两者都在 classpath 上)时,ribbonServerList被覆盖,扩展名为DiscoveryEnabledNIWSServerList,从 Eureka 填充服务器列表。它还将IPing接口替换为NIWSDiscoveryPing,它委托 Eureka 来确定服务器是否已启动。默认情况下安装的ServerListDomainExtractingServerList。其目的是在不使用 AWS AMI 元数据的情况下使负载均衡器可以使用元数据(这是 Netflix 所依赖的)。默认情况下,服务器列表使用“zone”信息构造,如实例元数据中所提供的(因此,在 remote clients 上设置eureka.instance.metadataMap.zone)。如果缺少,并且设置了approximateZoneFromHostname flag,则可以使用服务器主机名中的域 name 作为 zone 的代理。一旦 zone 信息可用,它就可以在ServerListFilter中使用。默认情况下,它用于在与 client 相同的 zone 中定位服务器,因为默认值为ZonePreferenceServerListFilter。默认情况下,client 的 zone 的确定方式与 remote 实例相同(即通过eureka.instance.metadataMap.zone)。

设置 client zone 的正统“archaius”方法是通过名为“@zone”的 configuration property。如果可用,Spring Cloud 优先于所有其他设置使用它(请注意,必须在 YAML configuration 中引用 key)。

如果没有其他 zone 数据源,则根据 client configuration(而不是实例 configuration)进行猜测。我们将eureka.client.availabilityZones(区域 name 中的 map)带到区域列表中,然后拉出实例自己区域的第一个 zone(即eureka.client.region,默认为“us-east-1”,以便与本机 Netflix 兼容)。

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

Eureka 是一种抽象 remote 服务器发现的便捷方式,因此您无需在 clients 中对其 URL 进行硬编码。但是,如果您不想使用 Eureka,Ribbon 和 Feign 也可以使用。假设您已为“stores”声明了@RibbonClient,并且 Eureka 未被使用(甚至在 classpath 上也没有)。 Ribbon client 默认为已配置的服务器列表。您可以按如下方式提供 configuration:

application.yml.

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

16.7 示例:在 Ribbon 中禁用 Eureka 使用

ribbon.eureka.enabled property 设置为false显式禁用 Ribbon 中 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 Configuration

每个名为 client 的 Ribbon 都有一个 Spring Cloud 维护的相应的子 application Context。此 application context 在对指定 client 的第一个请求上被延迟加载。通过指定 Ribbon clients 的名称,可以将此惰性 loading 行为更改为在启动时急切加载这些子 application 上下文,如下面的示例所示:

application.yml.

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

16.10 如何配置 Hystrix 线程池

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

application.yml.

zuul:
  threadPool:
    useSeparateThreadPools: true

前面的 example 导致 HystrixCommands 在 Hystrix 线程池中为每个 route 执行。

在这种情况下,默认HystrixThreadPoolKey与每个 route 的服务 ID 相同。要为HystrixThreadPoolKey添加前缀,请将zuul.threadPool.threadPoolKeyPrefix设置为要添加的 value,如下面的 example 所示:

application.yml.

zuul:
  threadPool:
    useSeparateThreadPools: true
    threadPoolKeyPrefix: zuulgw

16.11 如何为 Ribbon 的 IRule 提供 Key

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

com.netflix.loadbalancer.IRule.java.

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

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

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

如果使用的 key 将任何 object 放入RequestContext,它将被传递给IRule implementation 的choose方法。必须在执行RibbonRoutingFilter之前执行上一个 example 中显示的 code。 Zuul 的预过滤器是最好的选择。您可以通过预过滤器中的RequestContext访问 HTTP headers 和查询参数,因此可以用它来确定传递给 Ribbon 的LOAD_BALANCER_KEY。如果在RequestContext中没有将_val与LOAD_BALANCER_KEY放在一起,则将 null 作为choose方法的参数传递。