SpringCloud 源码系列(4)—— 负载均衡 Ribbon
一、负载均衡
1、RestTemplate
在研究 eureka 源码上篇中,我们在 demo-consumer 消费者服务中定义了用 @LoadBalanced 标记的 RestTemplate,然后使用 RestTemplate 通过服务名的形式来调用远程服务 demo-producer,然后请求会轮询到两个 demo-producer 实例上。
RestTemplate 是 Spring Resources 中一个访问第三方 RESTful API 接口的网络请求框架。RestTemplate 是用来消费 REST 服务的,所以 RestTemplate 的主要方法都与 REST 的 Http协议的一些方法紧密相连,例如 HEAD、GET、POST、PUT、DELETE 和 OPTIONS 等方法,这些方法在 RestTemplate 类对应的方法为 headForHeaders()、getForObject()、postForObject()、put() 和 delete() 等。
RestTemplate 本身是不具备负载均衡的能力的,如果 RestTemplate 未使用 @LoadBalanced 标记,就通过服务名的形式来调用,必然会报错。用 @LoadBalanced 标记后,调用 RestTemplate 的 REST 方法就会通过负载均衡的方式通过一定的策略路由到某个服务实例上,底层负责负载均衡的组件就是 Ribbon。后面我们再来看 @LoadBalanced 是如何让 RestTemplate 具备负载均衡的能力的。
1 @SpringBootApplication
2 public class ConsumerApplication {
3
4 @Bean
5 @LoadBalanced
6 public RestTemplate restTemplate() {
7 return new RestTemplate();
8 }
9
10 public static void main(String[] args) {
11 SpringApplication.run(ConsumerApplication.class, args);
12 }
13 }
14
15 @RestController
16 public class DemoController {
17 private final Logger logger = LoggerFactory.getLogger(getClass());
18
19 @Autowired
20 private RestTemplate restTemplate;
21
22 @GetMapping("/v1/id")
23 public ResponseEntity<String> getId() {
24 ResponseEntity<String> result = restTemplate.getForEntity("http://demo-producer/v1/uuid", String.class);
25 String uuid = result.getBody();
26 logger.info("request id: {}", uuid);
27 return ResponseEntity.ok(uuid);
28 }
29 }
2、Ribbon 与负载均衡
① 负载均衡
负载均衡是指将负载分摊到多个执行单元上,负载均衡主要可以分为集中式负载均衡与进程内负载均衡:
- 集中式负载均衡指位于因特网与执行单元之间,并负责把网络请求转发到各个执行单元上,比如 Nginx、F5。集中式负载均衡也可以称为服务端负载均衡。
- 进程内负载均衡是将负载均衡逻辑集成到客户端上,客户端维护了一份服务提供者的实例列表,实例列表一般会从注册中心比如 Eureka 中获取。有了实例列表,就可以通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。进程内负载均衡一般也称为客户端负载均衡。
Ribbon 是一个客户端负载均衡器,可以很好地控制 HTTP 和 TCP 客户端的负载均衡行为。Ribbon 是 Netflix 公司开源的一个负载均衡组件,已经整合到 SpringCloud 生态中,它在 Spring Cloud 生态内是一个不可缺少的组件,少了它,服务便不能横向扩展。
② Ribbon 模块
Ribbon 有很多子模块,官方文档中说明,目前 Netflix 公司主要用于生产环境的 Ribbon 子模块如下:
- ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载均衡器 API。
- ribbon-eureka:Ribbon 结合 Eureka 客户端的 API,为负载均衡器提供动态服务注册列表信息。
- ribbon-core:Ribbon 的核心API。
③ springcloud 与 ribbon 整合
与 eureka 整合到 springcloud 类似,springcloud 提供了对应的 spring-cloud-starter-netflix-eureka-client(server) 依赖包,ribbon 则整合到了 spring-cloud-starter-netflix-ribbon 中。一般也不需要单独引入 ribbon 的依赖包,spring-cloud-starter-netflix-eureka-client 中已经依赖了 spring-cloud-starter-netflix-ribbon。因此我们引入了 spring-cloud-starter-netflix-eureka-client 就可以使用 Ribbon 的功能了。
④ Ribbon 与 RestTemplate 整合使用
在 Spring Cloud 构建的微服务系统中,Ribbon 作为服务消费者的负载均衡器,有两种使用方式,一种是和 RestTemplate 相结合,另一种是和 Feign 相结合。前面已经演示了带有负载均衡的 RestTemplate 的使用,下面用一张图来看看 RestTemplate 基于 Ribbon 的远程调用。
二、RestTemplate 负载均衡
1、@LoadBalanced 注解
以 RestTemplate 为切入点,来看 Ribbon 的负载均衡核心原理。那么首先就要先看看 @LoadBalanced 注解如何让 RestTemplate 具备负载均衡的能力了。
首先看 @LoadBalanced 这个注解的定义,可以得到如下信息:
- 这个注解使用 @Qualifier 标记,其它地方就可以注入 LoadBalanced 注解的 bean 对象。
- 从注释中可以了解到,@LoadBalanced 标记的 RestTemplate 或 WebClient 将使用 LoadBalancerClient 来配置 bean 对象。
1 /**
2 * Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient.
3 * @author Spencer Gibb
4 */
5 @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
6 @Retention(RetentionPolicy.RUNTIME)
7 @Documented
8 @Inherited
9 @Qualifier
10 public @interface LoadBalanced {
11
12 }
注意 @LoadBalanced 是 spring-cloud-commons 模块下 loadbalancer 包下的。
2、RestTemplate 负载均衡自动化配置
在 @LoadBalanced 同包下,有一个 LoadBalancerAutoConfiguration 自动化配置类,从注释也可以看出,这是客户端负载均衡 Ribbon 的自动化配置类。
从这个自动化配置类可以得到如下信息:
- 首先要有 RestTemplate 的依赖和定义了 LoadBalancerClient 对象的前提下才会触发这个自动化配置类,这也对应了前面,RestTemplate 要用 LoadBalancerClient 来配置。
- 接着可以看到这个类注入了带有 @LoadBalanced 标识的 RestTemplate 对象,就是要对这部分对象增加负载均衡的能力。
- 从 SmartInitializingSingleton 的构造中可以看到,就是在 bean 初始化完成后,用 RestTemplateCustomizer 定制化 RestTemplate。
- 再往下可以看到,RestTemplateCustomizer 其实就是向 RestTemplate 中添加了 LoadBalancerInterceptor 这个拦截器。
- 而 LoadBalancerInterceptor 的构建又需要 LoadBalancerClient 和 LoadBalancerRequestFactory,LoadBalancerRequestFactory 则通过 LoadBalancerClient 和 LoadBalancerRequestTransformer 构造完成。
1 /**
2 * Auto-configuration for Ribbon (client-side load balancing).
3 */
4 @Configuration(proxyBeanMethods = false)
5 @ConditionalOnClass(RestTemplate.class) // 有 RestTemplate 的依赖
6 @ConditionalOnBean(LoadBalancerClient.class) // 定义了 LoadBalancerClient 的 bean 对象
7 @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
8 public class LoadBalancerAutoConfiguration {
9
10 // 注入 @LoadBalanced 标记的 RestTemplate 对象
11 @LoadBalanced
12 @Autowired(required = false)
13 private List<RestTemplate> restTemplates = Collections.emptyList();
14
15 @Autowired(required = false)
16 private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
17
18 @Bean
19 public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
20 final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
21 return () -> restTemplateCustomizers.ifAvailable(customizers -> {
22 for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
23 for (RestTemplateCustomizer customizer : customizers) {
24 // 利用 RestTemplateCustomizer 定制化 restTemplate
25 customizer.customize(restTemplate);
26 }
27 }
28 });
29 }
30
31 @Bean
32 @ConditionalOnMissingBean
33 public LoadBalancerRequestFactory loadBalancerRequestFactory(
34 LoadBalancerClient loadBalancerClient) {
35 return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
36 }
37
38 @Configuration(proxyBeanMethods = false)
39 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
40 static class LoadBalancerInterceptorConfig {
41
42 // 创建 LoadBalancerInterceptor 需要 LoadBalancerClient 和 LoadBalancerRequestFactory
43 @Bean
44 public LoadBalancerInterceptor ribbonInterceptor(
45 LoadBalancerClient loadBalancerClient,
46 LoadBalancerRequestFactory requestFactory) {
47 return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
48 }
49
50 @Bean
51 @ConditionalOnMissingBean
52 public RestTemplateCustomizer restTemplateCustomizer(
53 final LoadBalancerInterceptor loadBalancerInterceptor) {
54 return restTemplate -> {
55 List<ClientHttpRequestInterceptor> list = new ArrayList<>(
56 restTemplate.getInterceptors());
57 // 向 restTemplate 添加 LoadBalancerInterceptor 拦截器
58 list.add(loadBalancerInterceptor);
59 restTemplate.setInterceptors(list);
60 };
61 }
62
63 }
64 }
3、RestTemplate 拦截器 LoadBalancerInterceptor
LoadBalancerAutoConfiguration 自动化配置主要就是给 RestTemplate 添加了一个负载均衡拦截器 LoadBalancerInterceptor。从 setInterceptors 的参数可以看出,拦截器的类型是 ClientHttpRequestInterceptor,如果我们想定制化 RestTemplate,就可以实现这个接口来定制化,然后还可以用 @Order 标记拦截器的先后顺序。
1 public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
2 if (this.interceptors != interceptors) {
3 this.interceptors.clear();
4 this.interceptors.addAll(interceptors);
5 // 根据 @Order 注解的顺序排序
6 AnnotationAwareOrderComparator.sort(this.interceptors);
7 }
8 }
interceptors 拦截器是在 RestTemplate 的父类 InterceptingHttpAccessor 中的, RestTemplate 的类结构如下图所示。
从 restTemplate.getForEntity("http://demo-producer/v1/uuid", String.class) 这个GET请求进去看看,是如何使用 LoadBalancerInterceptor 的。一步步进去,可以看到最终是进入到 doExecute 这个方法了。
在 doExecute 方法中,首先根据 url、method 创建一个 ClientHttpRequest,然后利用 ClientHttpRequest 来发起请求。
1 protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
2 @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
3 ClientHttpResponse response = null;
4 try {
5 // 创建一个 ClientHttpRequest
6 ClientHttpRequest request = createRequest(url, method);
7 if (requestCallback != null) {
8 requestCallback.doWithRequest(request);
9 }
10 // 调用 ClientHttpRequest 的 execute() 方法
11 response = request.execute();
12 // 处理返回结果
13 handleResponse(url, method, response);
14 return (responseExtractor != null ? responseExtractor.extractData(response) : null);
15 }
16 catch (IOException ex) {
17 // ...
18 }
19 finally {
20 if (response != null) {
21 response.close();
22 }
23 }
24 }
25
26 //////////////////////////////////////
27
28 protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
29 ClientHttpRequest request = getRequestFactory().createRequest(url, method);
30 initialize(request);
31 if (logger.isDebugEnabled()) {
32 logger.debug("HTTP " + method.name() + " " + url);
33 }
34 return request;
35 }
InterceptingHttpAccessor 中重写了父类 HttpAccessor 的 getRequestFactory 方法,父类默认的 requestFactory 是 SimpleClientHttpRequestFactory。
重写后的 getRequestFactory 方法中,如果拦截器不为空,则基于父类默认的 SimpleClientHttpRequestFactory 和拦截器创建了 InterceptingClientHttpRequestFactory。
1 public ClientHttpRequestFactory getRequestFactory() {
2 List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
3 if (!CollectionUtils.isEmpty(interceptors)) {
4 ClientHttpRequestFactory factory = this.interceptingRequestFactory;
5 if (factory == null) {
6 // 传入 SimpleClientHttpRequestFactory 和 ClientHttpRequestInterceptor 拦截器
7 factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
8 this.interceptingRequestFactory = factory;
9 }
10 return factory;
11 }
12 else {
13 return super.getRequestFactory();
14 }
15 }
也就是说调用了 InterceptingClientHttpRequestFactory 的 createRequest 方法来创建 ClientHttpRequest。进去可以看到,ClientHttpRequest 的实际类型就是 InterceptingClientHttpRequest。
1 protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
2 return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
3 }
InterceptingClientHttpRequest 的类结构如下:
RestTemplate 的 doExecute 中调用 request.execute() 其实是调用了 InterceptingClientHttpRequest 父类 AbstractClientHttpRequest 中的 execute 方法。一步步进去可以发现最终其实是调用了 InterceptingClientHttpRequest 的 executeInternal 方法。
在 InterceptingClientHttpRequest 的 executeInternal 方法中,创建了 InterceptingRequestExecution 来执行请求。在 InterceptingRequestExecution 的 execute 方法中,会先遍历执行所有拦截器,然后通过 ClientHttpRequest 发起真正的 http 请求。
1 protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
2 // 创建 InterceptingRequestExecution
3 InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
4 // 请求调用
5 return requestExecution.execute(this, bufferedOutput);
6 }
7
8 private class InterceptingRequestExecution implements ClientHttpRequestExecution {
9
10 private final Iterator<ClientHttpRequestInterceptor> iterator;
11
12 public InterceptingRequestExecution() {
13 // 拦截器迭代器
14 this.iterator = interceptors.iterator();
15 }
16
17 @Override
18 public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
19 if (this.iterator.hasNext()) {
20 ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
21 // 利用拦截器拦截处理,并传入 InterceptingRequestExecution
22 return nextInterceptor.intercept(request, body, this);
23 }
24 else {
25 // 拦截器遍历完后开始发起真正的 http 请求
26 HttpMethod method = request.getMethod();
27 Assert.state(method != null, "No standard HTTP method");
28 ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
29 request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
30 if (body.length > 0) {
31 if (delegate instanceof StreamingHttpOutputMessage) {
32 StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
33 streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
34 }
35 else {
36 StreamUtils.copy(body, delegate.getBody());
37 }
38 }
39 return delegate.execute();
40 }
41 }
42 }
进入到 LoadBalancerInterceptor 的 intercept 拦截方法内,可以看到从请求的原始地址中获取了服务名称,然后调用了 loadBalancer 的 execute 方法,也就是 LoadBalancerClient。
到这里,其实已经可以想象,loadBalancer.execute 这行代码就是根据服务名称去获取一个具体的实例,然后将原始地址替换为实例的IP地址。那这个 loadBalancer 又是什么呢?
1 public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
2 // 原始地址:http://demo-producer/v1/uuid
3 final URI originalUri = request.getURI();
4 // host 就是服务名:demo-producer
5 String serviceName = originalUri.getHost();
6 Assert.state(serviceName != null,
7 "Request URI does not contain a valid hostname: " + originalUri);
8 return this.loadBalancer.execute(serviceName,
9 this.requestFactory.createRequest(request, body, execution));
10 }
4、负载均衡客户端 LoadBalancerClient
在配置 LoadBalancerInterceptor 时,需要两个参数,LoadBalancerClient 和 LoadBalancerRequestFactory,LoadBalancerRequestFactory前面已经知道是如何创建的了。LoadBalancerClient 又是在哪创建的呢?通过 IDEA 搜索,可以发现是在 spring-cloud-netflix-ribbon 模块下的 RibbonAutoConfiguration 中配置的,可以看到 LoadBalancerClient 的实际类型是 RibbonLoadBalancerClient。
配置类的顺序是 EurekaClientAutoConfiguration、RibbonAutoConfiguration、LoadBalancerAutoConfiguration,因为使 RestTemplate 具备负载均衡的能力需要 LoadBalancerInterceptor 拦截器,创建 LoadBalancerInterceptor 又需要 LoadBalancerClient,而 LoadBalancerClient 底层要根据服务名获取某个实例,肯定又需要一个实例库,比如从配置文件、注册中心获取。从这里就可以看出来,RibbonLoadBalancerClient 默认会从 Eureka 注册中心获取实例。
1 @Configuration
2 @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
3 @RibbonClients
4 // 后于 EurekaClientAutoConfiguration 配置
5 @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
6 // 先于 LoadBalancerAutoConfiguration 配置
7 @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
8 @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })
9 public class RibbonAutoConfiguration {
10
11 @Autowired(required = false)
12 private List<RibbonClientSpecification> configurations = new ArrayList<>();
13
14 @Bean
15 @ConditionalOnMissingBean
16 public SpringClientFactory springClientFactory() {
17 SpringClientFactory factory = new SpringClientFactory();
18 factory.setConfigurations(this.configurations);
19 return factory;
20 }
21
22 @Bean
23 @ConditionalOnMissingBean(LoadBalancerClient.class)
24 public LoadBalancerClient loadBalancerClient() {
25 return new RibbonLoadBalancerClient(springClientFactory());
26 }
27 }
LoadBalancerClient 主要提供了三个接口:
1 public interface LoadBalancerClient extends ServiceInstanceChooser {
2
3 // 从 LoadBalancer 找一个 Server 来发送请求
4 <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
5
6 // 从传入的 ServiceInstance 取 Server 来发送请求
7 <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
8
9 // 对原始 URI 重构
10 URI reconstructURI(ServiceInstance instance, URI original);
11 }
进入到 RibbonLoadBalancerClient 的 execute 方法中可以看到:
- 首先根据服务名获取服务对应的负载均衡器 ILoadBalancer。
- 然后从 ILoadBalancer 中根据一定策略选出一个实例 Server。
- 然后将 server、serviceId 等信息封装到 RibbonServer 中,也就是一个服务实例 ServiceInstance。
- 最后调用了 LoadBalancerRequest 的 apply,并传入 ServiceInstance,将地址中的服务名替换为真实的IP地址。
1 public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
2 return execute(serviceId, request, null);
3 }
4
5 public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
6 throws IOException {
7 // 根据服务名获取一个负载均衡器 ILoadBalancer
8 ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
9 // 利用负载均衡器获取实例 Server
10 Server server = getServer(loadBalancer, hint);
11 if (server == null) {
12 throw new IllegalStateException("No instances available for " + serviceId);
13 }
14 // 封装实例信息:RibbonServer 的父类是 ServiceInstance
15 RibbonServer ribbonServer = new RibbonServer(serviceId, server,
16 isSecure(server, serviceId),
17 serverIntrospector(serviceId).getMetadata(server));
18 return execute(serviceId, ribbonServer, request);
19 }
20
21 @Override
22 public <T> T execute(String serviceId, ServiceInstance serviceInstance,
23 LoadBalancerRequest<T> request) throws IOException {
24 Server server = null;
25 if (serviceInstance instanceof RibbonServer) {
26 server = ((RibbonServer) serviceInstance).getServer();
27 }
28 if (server == null) {
29 throw new IllegalStateException("No instances available for " + serviceId);
30 }
31
32 try {
33 // 处理地址,将服务名替换为真实的IP地址
34 T returnVal = request.apply(serviceInstance);
35 return returnVal;
36 } catch (Exception ex) {
37 // ...
38 }
39 return null;
40 }
这个 LoadBalancerRequest 其实就是 LoadBalancerInterceptor 的 intercept 中创建的一个匿名类,在它的函数式接口内,主要是用装饰器 ServiceRequestWrapper 将 request 包了一层。
1 public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) {
2 return instance -> {
3 // 封装 HttpRequest,ServiceRequestWrapper 重载了 getURI 方法。
4 HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer);
5 if (this.transformers != null) {
6 for (LoadBalancerRequestTransformer transformer : this.transformers) {
7 serviceRequest = transformer.transformRequest(serviceRequest, instance);
8 }
9 }
10 // 继续执行拦截器
11 return execution.execute(serviceRequest, body);
12 };
13 }
ServiceRequestWrapper 主要就是重写了 getURI 方法,在重写的 getURI 方法内,它用 loadBalancer 对 URI 进行了重构,进去可以发现,就是将原始地址中的服务名替换为 Server 的真实IP、端口地址。
1 @Override
2 public URI getURI() {
3 // 重构 URI
4 URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
5 return uri;
6 }
1 public URI reconstructURI(ServiceInstance instance, URI original) {
2 Assert.notNull(instance, "instance can not be null");
3 // 服务名
4 String serviceId = instance.getServiceId();
5 RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
6
7 URI uri;
8 Server server;
9 if (instance instanceof RibbonServer) {
10 RibbonServer ribbonServer = (RibbonServer) instance;
11 server = ribbonServer.getServer();
12 uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
13 }
14 else {
15 server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
16 IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
17 ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
18 uri = updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
19 }
20 // 重构地址
21 return context.reconstructURIWithServer(server, uri);
22 }
reconstructURIWithServer:


1 public URI reconstructURIWithServer(Server server, URI original) {
2 String host = server.getHost();
3 int port = server.getPort();
4 String scheme = server.getScheme();
5
6 if (host.equals(original.getHost())
7 && port == original.getPort()
8 && scheme == original.getScheme()) {
9 return original;
10 }
11 if (scheme == null) {
12 scheme = original.getScheme();
13 }
14 if (scheme == null) {
15 scheme = deriveSchemeAndPortFromPartialUri(original).first();
16 }
17
18 try {
19 StringBuilder sb = new StringBuilder();
20 sb.append(scheme).append("://");
21 if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
22 sb.append(original.getRawUserInfo()).append("@");
23 }
24 sb.append(host);
25 if (port >= 0) {
26 sb.append(":").append(port);
27 }
28 sb.append(original.getRawPath());
29 if (!Strings.isNullOrEmpty(original.getRawQuery())) {
30 sb.append("?").append(original.getRawQuery());
31 }
32 if (!Strings.isNullOrEmpty(original.getRawFragment())) {
33 sb.append("#").append(original.getRawFragment());
34 }
35 URI newURI = new URI(sb.toString());
36 return newURI;
37 } catch (URISyntaxException e) {
38 throw new RuntimeException(e);
39 }
40 }
5、RestTemplate 负载均衡总结
到这里,我们基本就弄清楚了一个简单的 @LoadBalanced 注解如何让 RestTemplate 具备了负载均衡的能力了,这一节来做个小结。
① RestTemplate 如何获得负载均衡的能力
- 1)首先 RestTemplate 是 spring-web 模块下一个访问第三方 RESTful API 接口的网络请求框架
- 2)在 spring cloud 微服务架构中,用 @LoadBalanced 对 RestTemplate 做个标记,就可以使 RestTemplate 具备负载均衡的能力
- 3)使 RestTemplate 具备负载均衡的核心组件就是 LoadBalancerAutoConfiguration 配置类中向其添加的 LoadBalancerInterceptor 负载均衡拦截器
- 4)RestTemplate 在发起 http 调用前,会遍历所有拦截器来对 RestTemplate 定制化,LoadBalancerInterceptor 就是在这时将URI中的服务名替换为实例的真实IP地址。定制完成后,就会发起真正的 http 请求。
- 5)LoadBalancerInterceptor 又主要是使用负载均衡客户端 LoadBalancerClient 来完成URI的重构的,LoadBalancerClient 就可以根据服务名查找一个可用的实例,然后重构URI。
② 核心组件
这里会涉及多个模块,下面是核心组件的所属模块:
spring-web:
- RestTemplate
- InterceptingClientHttpRequest:执行拦截器,并发起最终http调用
spring-cloud-commons:
- @LoadBalanced
- LoadBalancerAutoConfiguration
- LoadBalancerRequestFactory:创建装饰类 ServiceRequestWrapper 替换原来的 HttpRequest,重载 getURI 方法。
- LoadBalancerInterceptor:负载均衡拦截器
- LoadBalancerClient:负载均衡客户端接口
spring-cloud-netflix-ribbon:
- RibbonLoadBalancerClient:LoadBalancerClient 的实现类,Ribbon 的负载均衡客户端
- RibbonAutoConfiguration
ribbon-loadbalancer:
- ILoadBalancer:负载均衡器
- Server:实例
③ 最后再用一张图把 RestTemplate 这块的关系捋一下
三、ILoadBalancer 获取 Server
从前面 RestTemplate 那张图可以看出,使 RestTemplate 具备负载均衡的能力,最重要的一个组件之一就是 ILoadBalancer,因为要用它来获取能调用的 Server,有了 Server 才能对原始带有服务名的 URI 进行重构。这节就来看下 Ribbon 的负载均衡器 ILoadBalancer 是如何创建的以及如何通过它获取 Server。
1、创建负载均衡器 ILoadBalancer
① SpringClientFactory与上下文
ILoadBalancer 是用 SpringClientFactory 的 getLoadBalancer 方法根据服务名获取的,从 getInstance 一步步进去可以发现,每个服务都会创建一个 AnnotationConfigApplicationContext,也就是一个应用上下文 ApplicationContext。相当于就是一个服务绑定一个 ILoadBalancer。
1 public <C> C getInstance(String name, Class<C> type) {
2 C instance = super.getInstance(name, type);
3 if (instance != null) {
4 return instance;
5 }
6 IClientConfig config = getInstance(name, IClientConfig.class);
7 return instantiateWithConfig(getContext(name), type, config);
8 }
1 public <T> T getInstance(String name, Class<T> type) {
2 // 根据名称获取
3 AnnotationConfigApplicationContext context = getContext(name);
4 if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
5 return context.getBean(type);
6 }
7 return null;
8 }
1 protected AnnotationConfigApplicationContext getContext(String name) {
2 // contexts => Map<String, AnnotationConfigApplicationContext>
3 if (!this.contexts.containsKey(name)) {
4 synchronized (this.contexts) {
5 if (!this.contexts.containsKey(name)) {
6 this.contexts.put(name, createContext(name));
7 }
8 }
9 }
10 return this.contexts.get(name);
11 }
调试看下 AnnotationConfigApplicationContext 上下文,可以看到放入了与这个服务绑定的 ILoadBalancer、IClientConfig、RibbonLoadBalancerContext 等。
它这里为什么要每个服务都绑定一个 ApplicationContext 呢?我猜想应该是因为服务实例列表可以有多个来源,比如可以从 eureka 注册中心获取、可以通过代码配置、可以通过配置文件配置,另外每个服务还可以有很多个性化的配置,有默认的配置、定制的全局配置、个别服务的特定配置等,它这样做就便于用户定制每个服务的负载均衡策略。
② Ribbon的饥饿加载
而且这个Ribbon客户端的应用上下文默认是懒加载的,并不是在启动的时候就加载上下文,而是在第一次调用的时候才会去初始化。
如果想服务启动时就初始化,可以指定Ribbon客户端的具体名称,在启动的时候就加载配置项的上下文:
1 ribbon:
2 eager-load:
3 enabled: true
4 clients: demo-producer,demo-xxx
③ RibbonClientConfiguration
ILoadBalancer 的创建在哪呢?看 RibbonClientConfiguration,这个配置类提供了 ILoadBalancer 的默认创建方法,ILoadBalancer 的默认实现类为 ZoneAwareLoadBalancer。
1 public class RibbonClientConfiguration {
2
3 public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
4
5 public static final int DEFAULT_READ_TIMEOUT = 1000;
6
7 public static final boolean DEFAULT_GZIP_PAYLOAD = true;
8
9 @RibbonClientName
10 private String name = "client";
11
12 @Autowired
13 private PropertiesFactory propertiesFactory;
14
15 @Bean
16 @ConditionalOnMissingBean
17 public IClientConfig ribbonClientConfig() {
18 DefaultClientConfigImpl config = new DefaultClientConfigImpl();
19 config.loadProperties(this.name);
20 config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
21 config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
22 config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
23 return config;
24 }
25
26 @Bean
27 @ConditionalOnMissingBean
28 public IRule ribbonRule(IClientConfig config) {
29 if (this.propertiesFactory.isSet(IRule.class, name)) {
30 return this.propertiesFactory.get(IRule.class, config, name);
31 }
32 ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
33 rule.initWithNiwsConfig(config);
34 return rule;
35 }
36
37 @Bean
38 @ConditionalOnMissingBean
39 public IPing ribbonPing(IClientConfig config) {
40 if (this.propertiesFactory.isSet(IPing.class, name)) {
41 return this.propertiesFactory.get(IPing.class, config, name);
42 }
43 return new DummyPing();
44 }
45
46 @Bean
47 @ConditionalOnMissingBean
48 @SuppressWarnings("unchecked")
49 public ServerList<Server> ribbonServerList(IClientConfig config) {
50 if (this.propertiesFactory.isSet(ServerList.class, name)) {
51 return this.propertiesFactory.get(ServerList.class, config, name);
52 }
53 ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
54 serverList.initWithNiwsConfig(config);
55 return serverList;
56 }
57
58 @Bean
59 @ConditionalOnMissingBean
60 public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
61 return new PollingServerListUpdater(config);
62 }
63
64 @Bean
65 @ConditionalOnMissingBean
66 @SuppressWarnings("unchecked")
67 public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
68 if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
69 return this.propertiesFactory.get(ServerListFilter.class, config, name);
70 }
71 ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
72 filter.initWithNiwsConfig(config);
73 return filter;
74 }
75
76 @Bean
77 @ConditionalOnMissingBean
78 public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
79 ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
80 IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
81 // 先判断配置文件中是否配置了负载均衡器
82 if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
83 // 通过反射创建
84 return this.propertiesFactory.get(ILoadBalancer.class, config, name);
85 }
86 return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
87 serverListFilter, serverListUpdater);
88 }
89 }
可以看到创建 ILoadBalancer 需要 IClientConfig、ServerList<Server>、ServerListFilter<Server>、IRule、IPing、ServerListUpdater,其实这6个接口加上 ILoadBalancer 就是 Ribbon 的核心接口,它们共同定义了 Ribbon 的行为特性。
这7个核心接口和默认实现类如下:
2、客户端 Ribbon 定制
可以看到在 RibbonClientConfiguration 中创建 IRule、IPing、ServerList<Server>、ServerListFilter<Server>、ILoadBalancer 时,都先通过 propertiesFactory.isSet 判断是否已配置了对应类型的实现类,没有才使用默认的实现类。
也就是说针对特定的服务,这几个类可以自行定制化,也可以通过配置指定其它的实现类。
① 全局策略配置
如果想要全局更改配置,需要加一个配置类,比如像下面这样:
1 @Configuration
2 public class GlobalRibbonConfiguration {
3
4 @Bean
5 public IRule ribbonRule() {
6 return new RandomRule();
7 }
8
9 @Bean
10 public IPing ribbonPing() {
11 return new NoOpPing();
12 }
13 }
② 基于注解的配置
如果想针对某一个服务定制配置,可以通过 @RibbonClients 来配置特定服务的配置类。
需要先定义一个服务配置类:
1 @Configuration
2 public class ProducerRibbonConfiguration {
3
4 @Bean
5 public IRule ribbonRule() {
6 return new RandomRule();
7 }
8
9 @Bean
10 public IPing ribbonPing() {
11 return new NoOpPing();
12 }
13 }
用 @RibbonClients 注解为服务指定特定的配置类,并排除掉,不让 Spring 扫描,否则就变成了全局配置了。
1 @RibbonClients({
2 @RibbonClient(name = "demo-producer", configuration = ProducerRibbonConfiguration.class)
3 })
4 @ComponentScan(excludeFilters = {
5 @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ProducerRibbonConfiguration.class)
6 })
③ 配置文件配置
通过配置文件的方式来配置,配置的格式就是 <服务名称>.ribbon.<属性>:
1 demo-producer:
2 ribbon:
3 # ILoadBalancer
4 NFLoadBalancerClassName: com.netflix.loadbalancer.NoOpLoadBalancer
5 # IRule
6 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
7 # IPing
8 NFLoadBalancerPingClassName:
9 # ServerList<Server>
10 NIWSServerListClassName:
11 # ServerListFilter<Server>
12 NIWSServerListFilterClassName:
④ 优先级顺序
这几种配置方式的优先级顺序是 配置文件配置 > @RibbonClients 配置 > 全局配置 > 默认配置。
3、ZoneAwareLoadBalancer 选择 Server
获取到 ILoadBalancer 后,就要去获取 Server 了,可以看到,就是用 ILoadBalancer 来获取 Server。
1 protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
2 if (loadBalancer == null) {
3 return null;
4 }
5 // Use 'default' on a null hint, or just pass it on?
6 return loadBalancer.chooseServer(hint != null ? hint : "default");
7 }
ILoadBalancer 的默认实现类是 ZoneAwareLoadBalancer,进入它的 chooseServer 方法内,如果只配置了一个 zone,就走父类的 chooseServer,否则从多个 zone 中去选择实例。
1 public Server chooseServer(Object key) {
2 // ENABLED => ZoneAwareNIWSDiscoveryLoadBalancer.enabled 默认 true
3 // AvailableZones 配置的只有一个 defaultZone
4 if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
5 logger.debug("Zone aware logic disabled or there is only one zone");
6 // 走父类获取 Server 的逻辑
7 return super.chooseServer(key);
8 }
9
10 // 多 zone 逻辑....
11 }
先看下 ZoneAwareLoadBalancer 的类继承结构,ZoneAwareLoadBalancer 的直接父类是 DynamicServerListLoadBalancer,DynamicServerListLoadBalancer 的父类又是 BaseLoadBalancer。
ZoneAwareLoadBalancer 调用父类的 chooseServer 方法是在 BaseLoadBalancer 中的,进去可以看到,它主要是用 IRule 来选择实例,最终选择实例的策略就交给了 IRule 接口。
1 public Server chooseServer(Object key) {
2 if (counter == null) {
3 counter = createCounter();
4 }
5 counter.increment();
6 if (rule == null) {
7 return null;
8 } else {
9 try {
10 // IRule
11 return rule.choose(key);
12 } catch (Exception e) {
13 logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
14 return null;
15 }
16 }
17 }
4、ZoneAvoidanceRule 断言筛选、轮询选择 Server
IRule 的默认实现类是 ZoneAvoidanceRule,先看下 ZoneAvoidanceRule 的继承结构,ZoneAvoidanceRule 的直接父类是 PredicateBasedRule。
rule.choose 的逻辑在 PredicateBasedRule 中,getPredicate() 返回的是 ZoneAvoidanceRule 创建的一个组合断言 CompositePredicate,就是用这个断言来过滤出可用的 Server,并通过轮询的策略返回一个 Server。
1 public Server choose(Object key) {
2 ILoadBalancer lb = getLoadBalancer();
3 // getPredicate() Server断言 => CompositePredicate
4 // RoundRobin 轮询方式获取实例
5 Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
6 if (server.isPresent()) {
7 return server.get();
8 } else {
9 return null;
10 }
11 }
在初始化 ZoneAvoidanceRule 配置时,创建了 CompositePredicate,可以看到这个组合断言主要有两个断言,一个是断言 Server 的 zone 是否可用,一个断言 Server 本身是否可用,例如 Server 无法 ping 通。
1 public void initWithNiwsConfig(IClientConfig clientConfig) {
2 // 断言 Server 的 zone 是否可用,只有一个 defaultZone 的情况下都是可用的
3 ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this, clientConfig);
4 // 断言 Server 是否可用
5 AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this, clientConfig);
6 // 封装组合断言
7 compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
8 }
9
10 private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
11 // 建造者模式创建断言
12 return CompositePredicate.withPredicates(p1, p2)
13 .addFallbackPredicate(p2)
14 .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
15 .build();
16
17 }
接着看选择Server的 chooseRoundRobinAfterFiltering,参数 servers 是通过 ILoadBalancer 获取的所有实例,可以看到它其实就是返回了 ILoadBalancer 在内存中缓存的服务所有 Server。这个 Server 从哪来的我们后面再来看。
1 public List<Server> getAllServers() {
2 // allServerList => List<Server>
3 return Collections.unmodifiableList(allServerList);
4 }
先对所有实例通过断言过滤掉不可用的 Server,然后是通过轮询的方式获取一个 Server 返回。这就是默认配置下 ILoadBalancer(ZoneAwareLoadBalancer) 通过 IRule(ZoneAvoidanceRule) 选择 Server 的流程了。
1 public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
2 // 断言获取可用的 Server
3 List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
4 if (eligible.size() == 0) {
5 return Optional.absent();
6 }
7 // 通过取模的方式轮询 Server
8 return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
9 }
10
11 public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
12 if (loadBalancerKey == null) {
13 return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
14 } else {
15 List<Server> results = Lists.newArrayList();
16 // 对每个 Server 断言
17 for (Server server: servers) {
18 if (this.apply(new PredicateKey(loadBalancerKey, server))) {
19 results.add(server);
20 }
21 }
22 return results;
23 }
24 }
25
26 private int incrementAndGetModulo(int modulo) {
27 for (;;) {
28 int current = nextIndex.get();
29 // 模运算取余数
30 int next = (current + 1) % modulo;
31 // CAS 更新 nextIndex
32 if (nextIndex.compareAndSet(current, next) && current < modulo)
33 return current;
34 }
35 }
四、Ribbon 整合 Eureka Client 拉取Server列表
前面在通过 IRule 选择 Server 的时候,首先通过 lb.getAllServers() 获取了所有的 Server,那这些 Server 从哪里来的呢,这节就来看下。
1、ILoadBalancer 初始化
ILoadBalancer 的默认实现类是 ZoneAwareLoadBalancer,先从 ZoneAwareLoadBalancer 的构造方法进去看看都做了些什么事情。
1 @Bean
2 @ConditionalOnMissingBean
3 public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
4 ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
5 IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
6 if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
7 return this.propertiesFactory.get(ILoadBalancer.class, config, name);
8 }
9 return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
10 serverListFilter, serverListUpdater);
11 }
可以看到,ZoneAwareLoadBalancer 直接调用了父类 DynamicServerListLoadBalancer 的构造方法,DynamicServerListLoadBalancer 先调用父类 BaseLoadBalancer 初始化,然后又做了一些剩余的初始化工作。
1 public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
2 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
3 ServerListUpdater serverListUpdater) {
4 // DynamicServerListLoadBalancer
5 super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
6 }
7
8 public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
9 ServerList<T> serverList, ServerListFilter<T> filter,
10 ServerListUpdater serverListUpdater) {
11 // BaseLoadBalancer
12 super(clientConfig, rule, ping);
13 this.serverListImpl = serverList;
14 this.filter = filter;
15 this.serverListUpdater = serverListUpdater;
16 if (filter instanceof AbstractServerListFilter) {
17 ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
18 }
19 // 剩余的一些初始化
20 restOfInit(clientConfig);
21 }
22
23 public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
24 // createLoadBalancerStatsFromConfig => LoadBalancerStats 统计
25 initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
26
看 BaseLoadBalancer 的 initWithConfig,主要做了如下的初始化:
- 设置 IPing 和 IRule,ping 的间隔时间是 30 秒,setPing 会启动一个后台定时任务,然后每隔30秒运行一次 PingTask 任务。
- 设置了 ILoadBalancer 的 统计器 LoadBalancerStats,对 ILoadBalancer 的 Server 状态进行统计,比如连接失败、成功、熔断等信息。
- 在启用 PrimeConnections 请求预热的情况下,创建 PrimeConnections 来预热客户端 与 Server 的链接。默认是关闭的。
- 最后是注册了一些监控、开启请求预热。
1 void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
2 this.config = clientConfig;
3 String clientName = clientConfig.getClientName();
4 this.name = clientName;
5 // ping 间隔时间,默认30秒
6 int pingIntervalTime = Integer.parseInt(""
7 + clientConfig.getProperty(
8 CommonClientConfigKey.NFLoadBalancerPingInterval,
9 Integer.parseInt("30")));
10 // 没看到用的地方
11 int maxTotalPingTime = Integer.parseInt(""
12 + clientConfig.getProperty(
13 CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
14 Integer.parseInt("2")));
15 // 设置 ping 间隔时间,并重新设置了 ping 任务
16 setPingInterval(pingIntervalTime);
17 setMaxTotalPingTime(maxTotalPingTime);
18
19 // 设置 IRule、IPing
20 setRule(rule);
21 setPing(ping);
22
23 setLoadBalancerStats(stats);
24 rule.setLoadBalancer(this);
25 if (ping instanceof AbstractLoadBalancerPing) {
26 ((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
27 }
28 logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
29
30 // PrimeConnections,请求预热,默认关闭
31 // 作用主要用于解决那些部署环境(如读EC2)在实际使用实时请求之前,从防火墙连接/路径进行预热(比如先加白名单、初始化等等动作比较耗时,可以用它先去打通)。
32 boolean enablePrimeConnections = clientConfig.get(
33 CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);
34 if (enablePrimeConnections) {
35 this.setEnablePrimingConnections(true);
36 PrimeConnections primeConnections = new PrimeConnections(
37 this.getName(), clientConfig);
38 this.setPrimeConnections(primeConnections);
39 }
40 // 注册一些监控
41 init();
42 }
43
44 protected void init() {
45 Monitors.registerObject("LoadBalancer_" + name, this);
46 // register the rule as it contains metric for available servers count
47 Monitors.registerObject("Rule_" + name, this.getRule());
48 // 默认关闭
49 if (enablePrimingConnections && primeConnections != null) {
50 primeConnections.primeConnections(getReachableServers());
51 }
52 }
再看下 DynamicServerListLoadBalancer 的初始化,核心的初始化逻辑在 restOfInit 中,主要就是做了两件事情:
- 开启动态更新 Server 的特性,比如实例上线、下线、故障等,要能够更新 ILoadBalancer 的 Server 列表。
- 然后就全量更新一次本地的 Server 列表。
1 void restOfInit(IClientConfig clientConfig) {
2 boolean primeConnection = this.isEnablePrimingConnections();
3 // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
4 this.setEnablePrimingConnections(false);
5
6 // 开启动态更新 Server 的特性
7 enableAndInitLearnNewServersFeature();
8
9 // 更新 Server 列表
10 updateListOfServers();
11
12 // 开启请求预热的情况下,对可用的 Server 进行预热
13 if (primeConnection && this.getPrimeConnections() != null) {
14 this.getPrimeConnections()
15 .primeConnections(getReachableServers());
16 }
17 this.setEnablePrimingConnections(primeConnection);
18 LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
19 }
2、全量更新Server列表
先看下 updateListOfServers() 是如何更新 Server 列表的,进而看下 ILoadBalancer 是如何存储 Server 的。
- 首先使用 ServerList 获取所有的 Server 列表,在 RibbonClientConfiguration 中配置的是 ConfigurationBasedServerList,但和 eureka 集合和,就不是 ConfigurationBasedServerList 了,这块下一节再来看。
- 然后使用 ServerListFilter 对 Server 列表过滤,其默认实现类是 ZonePreferenceServerListFilter,它主要是过滤出当前 Zone(defaultZone)下的 Server。
- 最后就是更新所有 Server 列表,先是设置 Server alive,然后调用父类(BaseLoadBalancer)的 setServersList 来更新Server列表,这说明 Server 是存储在 BaseLoadBalancer 里的。
1 public void updateListOfServers() {
2 List<T> servers = new ArrayList<T>();
3 if (serverListImpl != null) {
4 // 从 ServerList 获取所有 Server 列表
5 servers = serverListImpl.getUpdatedListOfServers();
6 LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers);
7
8 if (filter != null) {
9 // 用 ServerListFilter 过滤 Server
10 servers = filter.getFilteredListOfServers(servers);
11 LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers);
12 }
13 }
14 // 更新所有 Server 到本地缓存
15 updateAllServerList(servers);
16 }
17
18 protected void updateAllServerList(List<T> ls) {
19 if (serverListUpdateInProgress.compareAndSet(false, true)) {
20 try {
21 for (T s : ls) {
22 s.setAlive(true); // 设置 Server alive
23 }
24 setServersList(ls);
25 // 强制初始化 Ping
26 super.forceQuickPing();
27 } finally {
28 serverListUpdateInProgress.set(false);
29 }
30 }
31 }
32
33 public void setServersList(List lsrv) {
34 // BaseLoadBalancer
35 super.setServersList(lsrv);
36
37 // 将 Server 更新到 LoadBalancerStats 统计中 ....
38 }
接着看父类的 setServersList,可以看出,存储所有 Server 的数据结构 allServerList 是一个加了 synchronized 的线程安全的容器,setServersList 就是直接将得到的 Server 列表替换 allServerList。
1 public void setServersList(List lsrv) {
2 Lock writeLock = allServerLock.writeLock();
3 ArrayList<Server> newServers = new ArrayList<Server>();
4 // 加写锁
5 writeLock.lock();
6 try {
7 // for 循环将 lsrv 中的 Server 转移到 allServers
8 ArrayList<Server> allServers = new ArrayList<Server>();
9 for (Object server : lsrv) {
10 if (server == null) {
11 continue;
12 }
13 if (server instanceof String) {
14 server = new Server((String) server);
15 }
16 if (server instanceof Server) {
17 logger.debug("LoadBalancer [{}]: addServer [{}]", name, ((Server) server).getId());
18 allServers.add((Server) server);
19 } else {
20 throw new IllegalArgumentException("Type String or Server expected, instead found:" + server.getClass());
21 }
22 }
23
24 boolean listChanged = false;
25 // allServerList => volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>())
26 if (!allServerList.equals(allServers)) {
27 listChanged = true;
28 // 服务列表变更监听器 ServerListChangeListener, 发出服务变更通知...
29 }
30
31 // 启用了服务预热,开始 Server 预热...
32
33 // 直接替换
34 allServerList = allServers;
35 if (canSkipPing()) {
36 for (Server s : allServerList) {
37 s.setAlive(true);
38 }
39 upServerList = allServerList;
40 } else if (listChanged) {
41 forceQuickPing();
42 }
43 } finally {
44 // 释放写锁
45 writeLock.unlock();
46 }
47 }
前面 chooseRoundRobinAfterFiltering 获取所有 Server 时就是返回的这个 allServerList列表。
1 public List<Server> getAllServers() {
2 return Collections.unmodifiableList(allServerList);
3 }
3、Eureka Ribbon 客户端配置
获取 Server 的组件是 ServerList,RibbonClientConfiguration 中配置的默认实现类是 ConfigurationBasedServerList。ConfigurationBasedServerList 默认是从配置文件中获取,可以像下面这样配置服务实例地址,多个 Server 地址用逗号隔开。
1 demo-producer:
2 ribbon:
3 listOfServers: http://10.215.0.92:8010,http://10.215.0.92:8011
但是和 eureka-client 结合后,也就是引入 spring-cloud-starter-netflix-eureka-client 的客户端依赖,它会帮我们引入 spring-cloud-netflix-eureka-client 依赖,这个包中有一个 RibbonEurekaAutoConfiguration 自动化配置类,它通过 @RibbonClients 注解定义了全局的 Ribbon 客户端配置类 为 EurekaRibbonClientConfiguration
1 @Configuration(proxyBeanMethods = false)
2 @EnableConfigurationProperties
3 @ConditionalOnRibbonAndEurekaEnabled
4 @AutoConfigureAfter(RibbonAutoConfiguration.class)
5 @RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
6 public class RibbonEurekaAutoConfiguration {
7
8 }
进入 EurekaRibbonClientConfiguration 可以看到:
- IPing 的默认实现类为 NIWSDiscoveryPing。
- ServerList 的默认实现类为 DomainExtractingServerList,但是 DomainExtractingServerList 在构造时又传入了一个类型为 DiscoveryEnabledNIWSServerList 的 ServerList。看名字大概也可以看出,DiscoveryEnabledNIWSServerList 就是从 EurekaClient 获取 Server 的组件。
1 @Configuration(proxyBeanMethods = false)
2 public class EurekaRibbonClientConfiguration {
3 @Value("${ribbon.eureka.approximateZoneFromHostname:false}")
4 private boolean approximateZoneFromHostname = false;
5
6 @RibbonClientName
7 private String serviceId = "client";
8 @Autowired
9 private PropertiesFactory propertiesFactory;
10
11 @Bean
12 @ConditionalOnMissingBean
13 public IPing ribbonPing(IClientConfig config) {
14 if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
15 return this.propertiesFactory.get(IPing.class, config, serviceId);
16 }
17 NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
18 ping.initWithNiwsConfig(config);
19 return ping;
20 }
21
22 @Bean
23 @ConditionalOnMissingBean
24 public ServerList<?> ribbonServerList(IClientConfig config,
25 Provider<EurekaClient> eurekaClientProvider) {
26 if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
27 return this.propertiesFactory.get(ServerList.class, config, serviceId);
28 }
29 DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(config, eurekaClientProvider);
30 DomainExtractingServerList serverList = new DomainExtractingServerList(discoveryServerList, config, this.approximateZoneFromHostname);
31 return serverList;
32 }
33 }
4、从 DiscoveryClient 获取Server列表
DynamicServerListLoadBalancer 中通过 ServerList 的 getUpdatedListOfServers 方法全量获取服务列表,在 eureka-client 环境下,ServerList 默认实现类为 DomainExtractingServerList,那就先看下它的 getUpdatedListOfServers 方法。
可以看出,DomainExtractingServerList 先用 DomainExtractingServerList 获取服务列表,然后根据 Ribbon 客户端配置重新构造 Server 对象返回。获取服务列表的核心在 DiscoveryEnabledNIWSServerList 中。
1 @Override
2 public List<DiscoveryEnabledServer> getUpdatedListOfServers() {
3 // list => DiscoveryEnabledNIWSServerList
4 List<DiscoveryEnabledServer> servers = setZones(this.list.getUpdatedListOfServers());
5 return servers;
6 }
7
8 private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) {
9 List<DiscoveryEnabledServer> result = new ArrayList<>();
10 boolean isSecure = this.ribbon.isSecure(true);
11 boolean shouldUseIpAddr = this.ribbon.isUseIPAddrForServer();
12 // 根据客户端配置重新构造 DomainExtractingServer 返回
13 for (DiscoveryEnabledServer server : servers) {
14 result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr, this.approximateZoneFromHostname));
15 }
16 return result;
17 }
先看下 DiscoveryEnabledNIWSServerList 的构造初始化:
- 主要是传入了 Provider<EurekaClient> 用来获取 EurekaClient
- 另外还设置了客户端名称 clientName ,以及 vipAddresses 也是客户端名称,这个后面会用得上。
1 public DiscoveryEnabledNIWSServerList(IClientConfig clientConfig, Provider<EurekaClient> eurekaClientProvider) {
2 this.eurekaClientProvider = eurekaClientProvider;
3 initWithNiwsConfig(clientConfig);
4 }
5
6 @Override
7 public void initWithNiwsConfig(IClientConfig clientConfig) {
8 // 客户端名称,就是服务名称
9 clientName = clientConfig.getClientName();
10 // vipAddresses 得到的也是客户端名称
11 vipAddresses = clientConfig.resolveDeploymentContextbasedVipAddresses();
12
13 // 其它的一些配置....
14 }
接着看获取实例的 getUpdatedListOfServers,可以看到它的核心逻辑就是根据服务名从 EurekaClient 获取 InstanceInfo 实例列表,然后封装 Server 信息返回。
1 public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
2 return obtainServersViaDiscovery();
3 }
4
5 private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
6 List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
7 if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
8 return new ArrayList<DiscoveryEnabledServer>();
9 }
10 // 得到 EurekaClient,实际类型是 CloudEurekaClient,其父类是 DiscoveryClient
11 EurekaClient eurekaClient = eurekaClientProvider.get();
12 if (vipAddresses!=null){
13 // 分割 vipAddresses,默认就是服务名称
14 for (String vipAddress : vipAddresses.split(",")) {
15 // 根据服务名称从 EurekaClient 获取实例信息
16 List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
17 for (InstanceInfo ii : listOfInstanceInfo) {
18 if (ii.getStatus().equals(InstanceStatus.UP)) {
19 // ...
20 // 根据实例信息 InstanceInfo 创建 Server
21 DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
22 serverList.add(des);
23 }
24 }
25 if (serverList.size()>0 && prioritizeVipAddressBasedServers){
26 break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
27 }
28 }
29 }
30 return serverList;
31 }
注意这里的 vipAddress 其实就是服务名:
最后看 EurekaClient 的 getInstancesByVipAddress,到这里就很清楚了,其实就是从 DiscoveryClient 的本地应用 Applications 中根据服务名取出所有的实例列表。
这里就和 Eureka 源码那块衔接上了,eureka-client 全量抓取注册表以及每隔30秒增量抓取注册表,都是合并到本地的 Applications 中。Ribbon 与 Eureka 结合后,Ribbon 获取 Server 就从 DiscoveryClient 的 Applications 中获取 Server 列表了。
1 public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, String region) {
2 // ...
3 Applications applications;
4 if (instanceRegionChecker.isLocalRegion(region)) {
5 // 取本地应用 Applications
6 applications = this.localRegionApps.get();
7 } else {
8 applications = remoteRegionVsApps.get(region);
9 if (null == applications) {
10 return Collections.emptyList();
11 }
12 }
13
14 if (!secure) {
15 // 返回服务名对应的实例
16 return applications.getInstancesByVirtualHostName(vipAddress);
17 } else {
18 return applications.getInstancesBySecureVirtualHostName(vipAddress);
19 }
20 }
5、定时更新Server列表
DynamicServerListLoadBalancer 初始化时,有个方法还没说,就是 enableAndInitLearnNewServersFeature()。这个方法只是调用 ServerListUpdater 启动了一个 UpdateAction,这个 UpdateAction 又只是调用了一下 updateListOfServers 方法,就是前面讲解过的全量更新 Server 的逻辑。
1 public void enableAndInitLearnNewServersFeature() {
2 serverListUpdater.start(updateAction);
3 }
4
5 protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
6 @Override
7 public void doUpdate() {
8 // 调用 updateListOfServers
9 updateListOfServers();
10 }
11 };
ServerListUpdater 的默认实现类是 PollingServerListUpdater,看下它的 start 方法:
其实就是以固定的频率,每隔30秒调用一下 updateListOfServers 方法,将 DiscoveryClient 中 Applications 中缓存的实例同步到 ILoadBalancer 中的 allServerList 列表中。
1 public synchronized void start(final UpdateAction updateAction) {
2 if (isActive.compareAndSet(false, true)) {
3 final Runnable wrapperRunnable = new Runnable() {
4 @Override
5 public void run() {
6 // ...
7 try {
8 // 执行一次 updateListOfServers
9 updateAction.doUpdate();
10 // 设置最后更新时间
11 lastUpdated = System.currentTimeMillis();
12 } catch (Exception e) {
13 logger.warn("Failed one update cycle", e);
14 }
15 }
16 };
17
18 // 固定频率调度
19 scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
20 wrapperRunnable,
21 initialDelayMs, // 默认 1000
22 refreshIntervalMs, // 默认 30 * 1000
23 TimeUnit.MILLISECONDS
24 );
25 } else {
26 logger.info("Already active, no-op");
27 }
28 }
6、判断Server是否存活
在创建 ILoadBalancer 时,IPing 还没有看过是如何工作的。在初始化的时候,可以看到,主要就是设置了当前的 ping,然后重新设置了一个调度任务,默认每隔30秒调度一次 PingTask 任务。
1 public void setPing(IPing ping) {
2 if (ping != null) {
3 if (!ping.equals(this.ping)) {
4 this.ping = ping;
5 // 设置 Ping 任务
6 setupPingTask();
7 }
8 } else {
9 this.ping = null;
10 // cancel the timer task
11 lbTimer.cancel();
12 }
13 }
14
15 void setupPingTask() {
16 // ...
17 // 创建一个定时调度器
18 lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name, true);
19 // pingIntervalTime 默认为 30 秒,每隔30秒调度一次 PingTask
20 lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
21 // 立即发起以 Ping
22 forceQuickPing();
23 }
ShutdownEnabledTimer 可以简单了解下,它是继承自 Timer 的,它在创建的时候向 Runtime 注册了一个回调,在 jvm 关闭的时候来取消 Timer 的执行,进而释放资源。


1 public class ShutdownEnabledTimer extends Timer {
2 private static final Logger LOGGER = LoggerFactory.getLogger(ShutdownEnabledTimer.class);
3
4 private Thread cancelThread;
5 private String name;
6
7 public ShutdownEnabledTimer(String name, boolean daemon) {
8 super(name, daemon);
9 this.name = name;
10 // 取消定时器的线程
11 this.cancelThread = new Thread(new Runnable() {
12 public void run() {
13 ShutdownEnabledTimer.super.cancel();
14 }
15 });
16
17 LOGGER.info("Shutdown hook installed for: {}", this.name);
18 // 向 Runtime 注册一个钩子,在 jvm 关闭时,调用 cancelThread 取消定时任务
19 Runtime.getRuntime().addShutdownHook(this.cancelThread);
20 }
21
22 @Override
23 public void cancel() {
24 super.cancel();
25 LOGGER.info("Shutdown hook removed for: {}", this.name);
26 try {
27 Runtime.getRuntime().removeShutdownHook(this.cancelThread);
28 } catch (IllegalStateException ise) {
29 LOGGER.info("Exception caught (might be ok if at shutdown)", ise);
30 }
31
32 }
33 }
再来看下 PingTask,PingTask 核心逻辑就是遍历 allServers 列表,使用 IPingStrategy 和 IPing 来判断 Server 是否存活,并更新 Server 的状态,以及将所有存活的 Server 更新到 upServerList 中,upServerList 缓存了所有存活的 Server。


1 class PingTask extends TimerTask {
2 public void run() {
3 try {
4 // pingStrategy => SerialPingStrategy
5 new Pinger(pingStrategy).runPinger();
6 } catch (Exception e) {
7 logger.error("LoadBalancer [{}]: Error pinging", name, e);
8 }
9 }
10 }
11
12 class Pinger {
13 private final IPingStrategy pingerStrategy;
14
15 public Pinger(IPingStrategy pingerStrategy) {
16 this.pingerStrategy = pingerStrategy;
17 }
18
19 public void runPinger() throws Exception {
20 if (!pingInProgress.compareAndSet(false, true)) {
21 return; // Ping in progress - nothing to do
22 }
23
24 Server[] allServers = null;
25 boolean[] results = null;
26
27 Lock allLock = null;
28 Lock upLock = null;
29
30 try {
31 allLock = allServerLock.readLock();
32 allLock.lock();
33 // 加读锁,取出 allServerList 中的 Server
34 allServers = allServerList.toArray(new Server[allServerList.size()]);
35 allLock.unlock();
36
37 int numCandidates = allServers.length;
38 // 使用 IPingStrategy 和 IPing 对所有 Server 发起 ping 请求
39 results = pingerStrategy.pingServers(ping, allServers);
40
41 final List<Server> newUpList = new ArrayList<Server>();
42 final List<Server> changedServers = new ArrayList<Server>();
43
44 for (int i = 0; i < numCandidates; i++) {
45 boolean isAlive = results[i];
46 Server svr = allServers[i];
47 boolean oldIsAlive = svr.isAlive();
48 // 设置 alive 是否存活
49 svr.setAlive(isAlive);
50
51 // 实例变更
52 if (oldIsAlive != isAlive) {
53 changedServers.add(svr);
54 logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}", name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
55 }
56
57 // 添加存活的 Server
58 if (isAlive) {
59 newUpList.add(svr);
60 }
61 }
62 upLock = upServerLock.writeLock();
63 upLock.lock();
64 // 更新 upServerList,upServerList 只保存了存活的 Server
65 upServerList = newUpList;
66 upLock.unlock();
67 // 通知变更
68 notifyServerStatusChangeListener(changedServers);
69 } finally {
70 pingInProgress.set(false);
71 }
72 }
73 }
IPingStrategy 的默认实现类是 SerialPingStrategy,进入可以发现它只是遍历所有 Server,然后用 IPing 判断 Server 是否存活。
1 private static class SerialPingStrategy implements IPingStrategy {
2 @Override
3 public boolean[] pingServers(IPing ping, Server[] servers) {
4 int numCandidates = servers.length;
5 boolean[] results = new boolean[numCandidates];
6
7 for (int i = 0; i < numCandidates; i++) {
8 results[i] = false;
9 try {
10 if (ping != null) {
11 // 使用 IPing 判断 Server 是否存活
12 results[i] = ping.isAlive(servers[i]);
13 }
14 } catch (Exception e) {
15 logger.error("Exception while pinging Server: '{}'", servers[i], e);
16 }
17 }
18 return results;
19 }
20 }
在集成 eureka-client 后,IPing默认实现类是 NIWSDiscoveryPing,看它的 isAlive 方法,其实就是判断对应 Server 的实例 InstanceInfo 的状态是否是 UP 状态,UP状态就表示 Server 存活。
1 public boolean isAlive(Server server) {
2 boolean isAlive = true;
3 if (server!=null && server instanceof DiscoveryEnabledServer){
4 DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
5 InstanceInfo instanceInfo = dServer.getInstanceInfo();
6 if (instanceInfo!=null){
7 InstanceStatus status = instanceInfo.getStatus();
8 if (status!=null){
9 // 判断Server对应的实例状态是否是 UP
10 isAlive = status.equals(InstanceStatus.UP);
11 }
12 }
13 }
14 return isAlive;
15 }
7、一张图总结 Ribbon 核心原理
① Ribbon 核心工作原理总结
- 首先,Ribbon 的7个核心接口共同定义了 Ribbon 的行为特性,它们就是 Ribbon 的核心骨架。
- 使用 Ribbon 来对客户端做负载均衡,基本的用法就是用 @LoadBalanced 注解标注一个 RestTemplate 的 bean 对象,之后在 LoadBalancerAutoConfiguration 配置类中会对带有 @LoadBalanced 注解的 RestTemplate 添加 LoadBalancerInterceptor 拦截器。
- LoadBalancerInterceptor 会拦截 RestTemplate 的 HTTP 请求,将请求绑定进 Ribbon 负载均衡的生命周期,然后使用 LoadBalancerClient 的 execute 方法来处理请求。
- LoadBalancerClient 首先会得到一个 ILoadBalancer,再使用它去得到一个 Server,这个 Server 就是具体某一个实例的信息封装。得到 Server 之后,就用 Server 的 IP 和端口重构原始 URI。
- ILoadBalancer 最终在选择实例的时候,会通过 IRule 均衡策略来选择一个 Server。
- ILoadBalancer 的父类 BaseLoadBalancer 中有一个 allServerList 列表缓存了所有 Server,Ribbon 中 Server 的来源就是 allServerList。
- 在加载Ribbon客户端上下文时,ILoadBalancer 会用 ServerList 从 DiscoveryClient 的 Applications 中获取客户端对应的实例列表,然后使用 ServerListFilter 过滤,最后更新到 allServerList 中。
- ILoadBalancer 还会开启一个后台任务 ServerListUpdater ,每隔30秒运行一次,用 ServerList 将 DiscoveryClient 的 Applications 中的实例列表同步到 allServerList 中。
- ILoadBalancer 还会开启一个后台任务 PingTask,每隔30秒运行一次,用 IPing 判断 Server 的存活状态,EurekaClient 环境下,就是判断 InstanceInfo 的状态是否为 UP。
② 下面用一张图来总结下 Ribbon 这块获取Server的核心流程以及对应的核心接口间的关系。
五、Ribbon 核心接口
前面已经了解到 Ribbon 核心接口以及默认实现如何协作来查找要调用的一个实例,这节再来看下各个核心接口的一些特性及其它实现类。
1、客户端配置 — IClientConfig
IClientConfig 就是管理客户端配置的核心接口,它的默认实现类是 DefaultClientConfigImpl。可以看到在创建 IClientConfig 时,设置了 Ribbon 客户端默认的连接和读取超时时间为 1 秒,例如读取如果超过1秒,就会返回超时,这两个一般需要根据实际情况来调整。
1 @Bean
2 @ConditionalOnMissingBean
3 public IClientConfig ribbonClientConfig() {
4 DefaultClientConfigImpl config = new DefaultClientConfigImpl();
5 // 加载配置
6 config.loadProperties(this.name);
7 // 连接超时默认 1 秒
8 config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
9 // 读取超时默认 1 秒
10 config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
11 config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
12 return config;
13 }
CommonClientConfigKey 这个类定义了 Ribbon 客户端相关的所有配置的键常量,可以通过这个类来看有哪些配置。


1 public abstract class CommonClientConfigKey<T> implements IClientConfigKey<T> {
2
3 public static final IClientConfigKey<String> AppName = new CommonClientConfigKey<String>("AppName"){};
4
5 public static final IClientConfigKey<String> Version = new CommonClientConfigKey<String>("Version"){};
6
7 public static final IClientConfigKey<Integer> Port = new CommonClientConfigKey<Integer>("Port"){};
8
9 public static final IClientConfigKey<Integer> SecurePort = new CommonClientConfigKey<Integer>("SecurePort"){};
10
11 public static final IClientConfigKey<String> VipAddress = new CommonClientConfigKey<String>("VipAddress"){};
12
13 public static final IClientConfigKey<Boolean> ForceClientPortConfiguration = new CommonClientConfigKey<Boolean>("ForceClientPortConfiguration"){}; // use client defined port regardless of server advert
14
15 public static final IClientConfigKey<String> DeploymentContextBasedVipAddresses = new CommonClientConfigKey<String>("DeploymentContextBasedVipAddresses"){};
16
17 public static final IClientConfigKey<Integer> MaxAutoRetries = new CommonClientConfigKey<Integer>("MaxAutoRetries"){};
18
19 public static final IClientConfigKey<Integer> MaxAutoRetriesNextServer = new CommonClientConfigKey<Integer>("MaxAutoRetriesNextServer"){};
20
21 public static final IClientConfigKey<Boolean> OkToRetryOnAllOperations = new CommonClientConfigKey<Boolean>("OkToRetryOnAllOperations"){};
22
23 public static final IClientConfigKey<Boolean> RequestSpecificRetryOn = new CommonClientConfigKey<Boolean>("RequestSpecificRetryOn"){};
24
25 public static final IClientConfigKey<Integer> ReceiveBufferSize = new CommonClientConfigKey<Integer>("ReceiveBufferSize"){};
26
27 public static final IClientConfigKey<Boolean> EnablePrimeConnections = new CommonClientConfigKey<Boolean>("EnablePrimeConnections"){};
28
29 public static final IClientConfigKey<String> PrimeConnectionsClassName = new CommonClientConfigKey<String>("PrimeConnectionsClassName"){};
30
31 public static final IClientConfigKey<Integer> MaxRetriesPerServerPrimeConnection = new CommonClientConfigKey<Integer>("MaxRetriesPerServerPrimeConnection"){};
32
33 public static final IClientConfigKey<Integer> MaxTotalTimeToPrimeConnections = new CommonClientConfigKey<Integer>("MaxTotalTimeToPrimeConnections"){};
34
35 public static final IClientConfigKey<Float> MinPrimeConnectionsRatio = new CommonClientConfigKey<Float>("MinPrimeConnectionsRatio"){};
36
37 public static final IClientConfigKey<String> PrimeConnectionsURI = new CommonClientConfigKey<String>("PrimeConnectionsURI"){};
38
39 public static final IClientConfigKey<Integer> PoolMaxThreads = new CommonClientConfigKey<Integer>("PoolMaxThreads"){};
40
41 public static final IClientConfigKey<Integer> PoolMinThreads = new CommonClientConfigKey<Integer>("PoolMinThreads"){};
42
43 public static final IClientConfigKey<Integer> PoolKeepAliveTime = new CommonClientConfigKey<Integer>("PoolKeepAliveTime"){};
44
45 public static final IClientConfigKey<String> PoolKeepAliveTimeUnits = new CommonClientConfigKey<String>("PoolKeepAliveTimeUnits"){};
46
47 public static final IClientConfigKey<Boolean> EnableConnectionPool = new CommonClientConfigKey<Boolean>("EnableConnectionPool") {};
48
49 /**
50 * Use {@link #MaxConnectionsPerHost}
51 */
52 @Deprecated
53 public static final IClientConfigKey<Integer> MaxHttpConnectionsPerHost = new CommonClientConfigKey<Integer>("MaxHttpConnectionsPerHost"){};
54
55 /**
56 * Use {@link #MaxTotalConnections}
57 */
58 @Deprecated
59 public static final IClientConfigKey<Integer> MaxTotalHttpConnections = new CommonClientConfigKey<Integer>("MaxTotalHttpConnections"){};
60
61 public static final IClientConfigKey<Integer> MaxConnectionsPerHost = new CommonClientConfigKey<Integer>("MaxConnectionsPerHost"){};
62
63 public static final IClientConfigKey<Integer> MaxTotalConnections = new CommonClientConfigKey<Integer>("MaxTotalConnections"){};
64
65 public static final IClientConfigKey<Boolean> IsSecure = new CommonClientConfigKey<Boolean>("IsSecure"){};
66
67 public static final IClientConfigKey<Boolean> GZipPayload = new CommonClientConfigKey<Boolean>("GZipPayload"){};
68
69 public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout"){};
70
71 public static final IClientConfigKey<Integer> BackoffInterval = new CommonClientConfigKey<Integer>("BackoffTimeout"){};
72
73 public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout"){};
74
75 public static final IClientConfigKey<Integer> SendBufferSize = new CommonClientConfigKey<Integer>("SendBufferSize"){};
76
77 public static final IClientConfigKey<Boolean> StaleCheckingEnabled = new CommonClientConfigKey<Boolean>("StaleCheckingEnabled"){};
78
79 public static final IClientConfigKey<Integer> Linger = new CommonClientConfigKey<Integer>("Linger"){};
80
81 public static final IClientConfigKey<Integer> ConnectionManagerTimeout = new CommonClientConfigKey<Integer>("ConnectionManagerTimeout"){};
82
83 public static final IClientConfigKey<Boolean> FollowRedirects = new CommonClientConfigKey<Boolean>("FollowRedirects"){};
84
85 public static final IClientConfigKey<Boolean> ConnectionPoolCleanerTaskEnabled = new CommonClientConfigKey<Boolean>("ConnectionPoolCleanerTaskEnabled"){};
86
87 public static final IClientConfigKey<Integer> ConnIdleEvictTimeMilliSeconds = new CommonClientConfigKey<Integer>("ConnIdleEvictTimeMilliSeconds"){};
88
89 public static final IClientConfigKey<Integer> ConnectionCleanerRepeatInterval = new CommonClientConfigKey<Integer>("ConnectionCleanerRepeatInterval"){};
90
91 public static final IClientConfigKey<Boolean> EnableGZIPContentEncodingFilter = new CommonClientConfigKey<Boolean>("EnableGZIPContentEncodingFilter"){};
92
93 public static final IClientConfigKey<String> ProxyHost = new CommonClientConfigKey<String>("ProxyHost"){};
94
95 public static final IClientConfigKey<Integer> ProxyPort = new CommonClientConfigKey<Integer>("ProxyPort"){};
96
97 public static final IClientConfigKey<String> KeyStore = new CommonClientConfigKey<String>("KeyStore"){};
98
99 public static final IClientConfigKey<String> KeyStorePassword = new CommonClientConfigKey<String>("KeyStorePassword"){};
100
101 public static final IClientConfigKey<String> TrustStore = new CommonClientConfigKey<String>("TrustStore"){};
102
103 public static final IClientConfigKey<String> TrustStorePassword = new CommonClientConfigKey<String>("TrustStorePassword"){};
104
105 // if this is a secure rest client, must we use client auth too?
106 public static final IClientConfigKey<Boolean> IsClientAuthRequired = new CommonClientConfigKey<Boolean>("IsClientAuthRequired"){};
107
108 public static final IClientConfigKey<String> CustomSSLSocketFactoryClassName = new CommonClientConfigKey<String>("CustomSSLSocketFactoryClassName"){};
109 // must host name match name in certificate?
110 public static final IClientConfigKey<Boolean> IsHostnameValidationRequired = new CommonClientConfigKey<Boolean>("IsHostnameValidationRequired"){};
111
112 // see also http://hc.apache.org/httpcomponents-client-ga/tutorial/html/advanced.html
113 public static final IClientConfigKey<Boolean> IgnoreUserTokenInConnectionPoolForSecureClient = new CommonClientConfigKey<Boolean>("IgnoreUserTokenInConnectionPoolForSecureClient"){};
114
115 // Client implementation
116 public static final IClientConfigKey<String> ClientClassName = new CommonClientConfigKey<String>("ClientClassName"){};
117
118 //LoadBalancer Related
119 public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer"){};
120
121 public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName"){};
122
123 public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){};
124
125 public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){};
126
127 public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){};
128
129 public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){};
130
131 public static final IClientConfigKey<String> NFLoadBalancerStatsClassName = new CommonClientConfigKey<String>("NFLoadBalancerStatsClassName"){};
132
133 public static final IClientConfigKey<String> NIWSServerListClassName = new CommonClientConfigKey<String>("NIWSServerListClassName"){};
134
135 public static final IClientConfigKey<String> ServerListUpdaterClassName = new CommonClientConfigKey<String>("ServerListUpdaterClassName"){};
136
137 public static final IClientConfigKey<String> NIWSServerListFilterClassName = new CommonClientConfigKey<String>("NIWSServerListFilterClassName"){};
138
139 public static final IClientConfigKey<Integer> ServerListRefreshInterval = new CommonClientConfigKey<Integer>("ServerListRefreshInterval"){};
140
141 public static final IClientConfigKey<Boolean> EnableMarkingServerDownOnReachingFailureLimit = new CommonClientConfigKey<Boolean>("EnableMarkingServerDownOnReachingFailureLimit"){};
142
143 public static final IClientConfigKey<Integer> ServerDownFailureLimit = new CommonClientConfigKey<Integer>("ServerDownFailureLimit"){};
144
145 public static final IClientConfigKey<Integer> ServerDownStatWindowInMillis = new CommonClientConfigKey<Integer>("ServerDownStatWindowInMillis"){};
146
147 public static final IClientConfigKey<Boolean> EnableZoneAffinity = new CommonClientConfigKey<Boolean>("EnableZoneAffinity"){};
148
149 public static final IClientConfigKey<Boolean> EnableZoneExclusivity = new CommonClientConfigKey<Boolean>("EnableZoneExclusivity"){};
150
151 public static final IClientConfigKey<Boolean> PrioritizeVipAddressBasedServers = new CommonClientConfigKey<Boolean>("PrioritizeVipAddressBasedServers"){};
152
153 public static final IClientConfigKey<String> VipAddressResolverClassName = new CommonClientConfigKey<String>("VipAddressResolverClassName"){};
154
155 public static final IClientConfigKey<String> TargetRegion = new CommonClientConfigKey<String>("TargetRegion"){};
156
157 public static final IClientConfigKey<String> RulePredicateClasses = new CommonClientConfigKey<String>("RulePredicateClasses"){};
158
159 public static final IClientConfigKey<String> RequestIdHeaderName = new CommonClientConfigKey<String>("RequestIdHeaderName") {};
160
161 public static final IClientConfigKey<Boolean> UseIPAddrForServer = new CommonClientConfigKey<Boolean>("UseIPAddrForServer") {};
162
163 public static final IClientConfigKey<String> ListOfServers = new CommonClientConfigKey<String>("listOfServers") {};
164
165 private static final Set<IClientConfigKey> keys = new HashSet<IClientConfigKey>();
166
167 // ...
168 }
进入到 DefaultClientConfigImpl,可以看到 CommonClientConfigKey 中的每个配置都对应了一个默认值。在加载配置的时候,如果用户没有定制配置,就会使用默认的配置。


1 public class DefaultClientConfigImpl implements IClientConfig {
2
3 public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE;
4
5 public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing"; // DummyPing.class.getName();
6
7 public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule";
8
9 public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer";
10
11 public static final boolean DEFAULT_USEIPADDRESS_FOR_SERVER = Boolean.FALSE;
12
13 public static final String DEFAULT_CLIENT_CLASSNAME = "com.netflix.niws.client.http.RestClient";
14
15 public static final String DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME = "com.netflix.client.SimpleVipAddressResolver";
16
17 public static final String DEFAULT_PRIME_CONNECTIONS_URI = "/";
18
19 public static final int DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS = 30000;
20
21 public static final int DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION = 9;
22
23 public static final Boolean DEFAULT_ENABLE_PRIME_CONNECTIONS = Boolean.FALSE;
24
25 public static final int DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW = Integer.MAX_VALUE;
26
27 public static final int DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS = 60000;
28
29 public static final Boolean DEFAULT_ENABLE_REQUEST_THROTTLING = Boolean.FALSE;
30
31 public static final Boolean DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER = Boolean.FALSE;
32
33 public static final Boolean DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED = Boolean.TRUE;
34
35 public static final Boolean DEFAULT_FOLLOW_REDIRECTS = Boolean.FALSE;
36
37 public static final float DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED = 0.0f;
38
39 public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;
40
41 public static final int DEFAULT_MAX_AUTO_RETRIES = 0;
42
43 public static final int DEFAULT_BACKOFF_INTERVAL = 0;
44
45 public static final int DEFAULT_READ_TIMEOUT = 5000;
46
47 public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;
48
49 public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
50
51 public static final Boolean DEFAULT_ENABLE_CONNECTION_POOL = Boolean.TRUE;
52
53 @Deprecated
54 public static final int DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST = 50;
55
56 @Deprecated
57 public static final int DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS = 200;
58
59 public static final int DEFAULT_MAX_CONNECTIONS_PER_HOST = 50;
60
61 public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 200;
62
63 public static final float DEFAULT_MIN_PRIME_CONNECTIONS_RATIO = 1.0f;
64
65 public static final String DEFAULT_PRIME_CONNECTIONS_CLASS = "com.netflix.niws.client.http.HttpPrimeConnection";
66
67 public static final String DEFAULT_SEVER_LIST_CLASS = "com.netflix.loadbalancer.ConfigurationBasedServerList";
68
69 public static final String DEFAULT_SERVER_LIST_UPDATER_CLASS = "com.netflix.loadbalancer.PollingServerListUpdater";
70
71 public static final int DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS = 30000; // every half minute (30 secs)
72
73 public static final int DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS = 30000; // all connections idle for 30 secs
74
75 protected volatile Map<String, Object> properties = new ConcurrentHashMap<String, Object>();
76
77 protected Map<IClientConfigKey<?>, Object> typedProperties = new ConcurrentHashMap<IClientConfigKey<?>, Object>();
78
79 private static final Logger LOG = LoggerFactory.getLogger(DefaultClientConfigImpl.class);
80
81 private String clientName = null;
82
83 private VipAddressResolver resolver = null;
84
85 private boolean enableDynamicProperties = true;
86 /**
87 * Defaults for the parameters for the thread pool used by batchParallel
88 * calls
89 */
90 public static final int DEFAULT_POOL_MAX_THREADS = DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS;
91 public static final int DEFAULT_POOL_MIN_THREADS = 1;
92 public static final long DEFAULT_POOL_KEEP_ALIVE_TIME = 15 * 60L;
93 public static final TimeUnit DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS = TimeUnit.SECONDS;
94 public static final Boolean DEFAULT_ENABLE_ZONE_AFFINITY = Boolean.FALSE;
95 public static final Boolean DEFAULT_ENABLE_ZONE_EXCLUSIVITY = Boolean.FALSE;
96 public static final int DEFAULT_PORT = 7001;
97 public static final Boolean DEFAULT_ENABLE_LOADBALANCER = Boolean.TRUE;
98
99 public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon";
100
101 private String propertyNameSpace = DEFAULT_PROPERTY_NAME_SPACE;
102
103 public static final Boolean DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS = Boolean.FALSE;
104
105 public static final Boolean DEFAULT_ENABLE_NIWS_EVENT_LOGGING = Boolean.TRUE;
106
107 public static final Boolean DEFAULT_IS_CLIENT_AUTH_REQUIRED = Boolean.FALSE;
108
109 private final Map<String, DynamicStringProperty> dynamicProperties = new ConcurrentHashMap<String, DynamicStringProperty>();
110
111 public Boolean getDefaultPrioritizeVipAddressBasedServers() {
112 return DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS;
113 }
114
115 public String getDefaultNfloadbalancerPingClassname() {
116 return DEFAULT_NFLOADBALANCER_PING_CLASSNAME;
117 }
118
119 public String getDefaultNfloadbalancerRuleClassname() {
120 return DEFAULT_NFLOADBALANCER_RULE_CLASSNAME;
121 }
122
123 public String getDefaultNfloadbalancerClassname() {
124 return DEFAULT_NFLOADBALANCER_CLASSNAME;
125 }
126
127 public boolean getDefaultUseIpAddressForServer() {
128 return DEFAULT_USEIPADDRESS_FOR_SERVER;
129 }
130
131 public String getDefaultClientClassname() {
132 return DEFAULT_CLIENT_CLASSNAME;
133 }
134
135 public String getDefaultVipaddressResolverClassname() {
136 return DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME;
137 }
138
139 public String getDefaultPrimeConnectionsUri() {
140 return DEFAULT_PRIME_CONNECTIONS_URI;
141 }
142
143 public int getDefaultMaxTotalTimeToPrimeConnections() {
144 return DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS;
145 }
146
147 public int getDefaultMaxRetriesPerServerPrimeConnection() {
148 return DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION;
149 }
150
151 public Boolean getDefaultEnablePrimeConnections() {
152 return DEFAULT_ENABLE_PRIME_CONNECTIONS;
153 }
154
155 public int getDefaultMaxRequestsAllowedPerWindow() {
156 return DEFAULT_MAX_REQUESTS_ALLOWED_PER_WINDOW;
157 }
158
159 public int getDefaultRequestThrottlingWindowInMillis() {
160 return DEFAULT_REQUEST_THROTTLING_WINDOW_IN_MILLIS;
161 }
162
163 public Boolean getDefaultEnableRequestThrottling() {
164 return DEFAULT_ENABLE_REQUEST_THROTTLING;
165 }
166
167 public Boolean getDefaultEnableGzipContentEncodingFilter() {
168 return DEFAULT_ENABLE_GZIP_CONTENT_ENCODING_FILTER;
169 }
170
171 public Boolean getDefaultConnectionPoolCleanerTaskEnabled() {
172 return DEFAULT_CONNECTION_POOL_CLEANER_TASK_ENABLED;
173 }
174
175 public Boolean getDefaultFollowRedirects() {
176 return DEFAULT_FOLLOW_REDIRECTS;
177 }
178
179 public float getDefaultPercentageNiwsEventLogged() {
180 return DEFAULT_PERCENTAGE_NIWS_EVENT_LOGGED;
181 }
182
183 public int getDefaultMaxAutoRetriesNextServer() {
184 return DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER;
185 }
186
187 public int getDefaultMaxAutoRetries() {
188 return DEFAULT_MAX_AUTO_RETRIES;
189 }
190
191 public int getDefaultReadTimeout() {
192 return DEFAULT_READ_TIMEOUT;
193 }
194
195 public int getDefaultConnectionManagerTimeout() {
196 return DEFAULT_CONNECTION_MANAGER_TIMEOUT;
197 }
198
199 public int getDefaultConnectTimeout() {
200 return DEFAULT_CONNECT_TIMEOUT;
201 }
202
203 @Deprecated
204 public int getDefaultMaxHttpConnectionsPerHost() {
205 return DEFAULT_MAX_HTTP_CONNECTIONS_PER_HOST;
206 }
207
208 @Deprecated
209 public int getDefaultMaxTotalHttpConnections() {
210 return DEFAULT_MAX_TOTAL_HTTP_CONNECTIONS;
211 }
212
213 public int getDefaultMaxConnectionsPerHost() {
214 return DEFAULT_MAX_CONNECTIONS_PER_HOST;
215 }
216
217 public int getDefaultMaxTotalConnections() {
218 return DEFAULT_MAX_TOTAL_CONNECTIONS;
219 }
220
221 public float getDefaultMinPrimeConnectionsRatio() {
222 return DEFAULT_MIN_PRIME_CONNECTIONS_RATIO;
223 }
224
225 public String getDefaultPrimeConnectionsClass() {
226 return DEFAULT_PRIME_CONNECTIONS_CLASS;
227 }
228
229 public String getDefaultSeverListClass() {
230 return DEFAULT_SEVER_LIST_CLASS;
231 }
232
233 public int getDefaultConnectionIdleTimertaskRepeatInMsecs() {
234 return DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS;
235 }
236
237 public int getDefaultConnectionidleTimeInMsecs() {
238 return DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS;
239 }
240
241 public VipAddressResolver getResolver() {
242 return resolver;
243 }
244
245 public boolean isEnableDynamicProperties() {
246 return enableDynamicProperties;
247 }
248
249 public int getDefaultPoolMaxThreads() {
250 return DEFAULT_POOL_MAX_THREADS;
251 }
252
253 public int getDefaultPoolMinThreads() {
254 return DEFAULT_POOL_MIN_THREADS;
255 }
256
257 public long getDefaultPoolKeepAliveTime() {
258 return DEFAULT_POOL_KEEP_ALIVE_TIME;
259 }
260
261 public TimeUnit getDefaultPoolKeepAliveTimeUnits() {
262 return DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS;
263 }
264
265 public Boolean getDefaultEnableZoneAffinity() {
266 return DEFAULT_ENABLE_ZONE_AFFINITY;
267 }
268
269 public Boolean getDefaultEnableZoneExclusivity() {
270 return DEFAULT_ENABLE_ZONE_EXCLUSIVITY;
271 }
272
273 public int getDefaultPort() {
274 return DEFAULT_PORT;
275 }
276
277 public Boolean getDefaultEnableLoadbalancer() {
278 return DEFAULT_ENABLE_LOADBALANCER;
279 }
280
281
282 public Boolean getDefaultOkToRetryOnAllOperations() {
283 return DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS;
284 }
285
286 public Boolean getDefaultIsClientAuthRequired(){
287 return DEFAULT_IS_CLIENT_AUTH_REQUIRED;
288 }
289
290
291 /**
292 * Create instance with no properties in default name space {@link #DEFAULT_PROPERTY_NAME_SPACE}
293 */
294 public DefaultClientConfigImpl() {
295 this.dynamicProperties.clear();
296 this.enableDynamicProperties = false;
297 }
298
299 /**
300 * Create instance with no properties in the specified name space
301 */
302 public DefaultClientConfigImpl(String nameSpace) {
303 this();
304 this.propertyNameSpace = nameSpace;
305 }
306
307 public void loadDefaultValues() {
308 putDefaultIntegerProperty(CommonClientConfigKey.MaxHttpConnectionsPerHost, getDefaultMaxHttpConnectionsPerHost());
309 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalHttpConnections, getDefaultMaxTotalHttpConnections());
310 putDefaultBooleanProperty(CommonClientConfigKey.EnableConnectionPool, getDefaultEnableConnectionPool());
311 putDefaultIntegerProperty(CommonClientConfigKey.MaxConnectionsPerHost, getDefaultMaxConnectionsPerHost());
312 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalConnections, getDefaultMaxTotalConnections());
313 putDefaultIntegerProperty(CommonClientConfigKey.ConnectTimeout, getDefaultConnectTimeout());
314 putDefaultIntegerProperty(CommonClientConfigKey.ConnectionManagerTimeout, getDefaultConnectionManagerTimeout());
315 putDefaultIntegerProperty(CommonClientConfigKey.ReadTimeout, getDefaultReadTimeout());
316 putDefaultIntegerProperty(CommonClientConfigKey.MaxAutoRetries, getDefaultMaxAutoRetries());
317 putDefaultIntegerProperty(CommonClientConfigKey.MaxAutoRetriesNextServer, getDefaultMaxAutoRetriesNextServer());
318 putDefaultBooleanProperty(CommonClientConfigKey.OkToRetryOnAllOperations, getDefaultOkToRetryOnAllOperations());
319 putDefaultBooleanProperty(CommonClientConfigKey.FollowRedirects, getDefaultFollowRedirects());
320 putDefaultBooleanProperty(CommonClientConfigKey.ConnectionPoolCleanerTaskEnabled, getDefaultConnectionPoolCleanerTaskEnabled());
321 putDefaultIntegerProperty(CommonClientConfigKey.ConnIdleEvictTimeMilliSeconds, getDefaultConnectionidleTimeInMsecs());
322 putDefaultIntegerProperty(CommonClientConfigKey.ConnectionCleanerRepeatInterval, getDefaultConnectionIdleTimertaskRepeatInMsecs());
323 putDefaultBooleanProperty(CommonClientConfigKey.EnableGZIPContentEncodingFilter, getDefaultEnableGzipContentEncodingFilter());
324 String proxyHost = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(CommonClientConfigKey.ProxyHost.key()));
325 if (proxyHost != null && proxyHost.length() > 0) {
326 setProperty(CommonClientConfigKey.ProxyHost, proxyHost);
327 }
328 Integer proxyPort = ConfigurationManager
329 .getConfigInstance()
330 .getInteger(
331 getDefaultPropName(CommonClientConfigKey.ProxyPort),
332 (Integer.MIN_VALUE + 1)); // + 1 just to avoid potential clash with user setting
333 if (proxyPort != (Integer.MIN_VALUE + 1)) {
334 setProperty(CommonClientConfigKey.ProxyPort, proxyPort);
335 }
336 putDefaultIntegerProperty(CommonClientConfigKey.Port, getDefaultPort());
337 putDefaultBooleanProperty(CommonClientConfigKey.EnablePrimeConnections, getDefaultEnablePrimeConnections());
338 putDefaultIntegerProperty(CommonClientConfigKey.MaxRetriesPerServerPrimeConnection, getDefaultMaxRetriesPerServerPrimeConnection());
339 putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalTimeToPrimeConnections, getDefaultMaxTotalTimeToPrimeConnections());
340 putDefaultStringProperty(CommonClientConfigKey.PrimeConnectionsURI, getDefaultPrimeConnectionsUri());
341 putDefaultIntegerProperty(CommonClientConfigKey.PoolMinThreads, getDefaultPoolMinThreads());
342 putDefaultIntegerProperty(CommonClientConfigKey.PoolMaxThreads, getDefaultPoolMaxThreads());
343 putDefaultLongProperty(CommonClientConfigKey.PoolKeepAliveTime, getDefaultPoolKeepAliveTime());
344 putDefaultTimeUnitProperty(CommonClientConfigKey.PoolKeepAliveTimeUnits, getDefaultPoolKeepAliveTimeUnits());
345 putDefaultBooleanProperty(CommonClientConfigKey.EnableZoneAffinity, getDefaultEnableZoneAffinity());
346 putDefaultBooleanProperty(CommonClientConfigKey.EnableZoneExclusivity, getDefaultEnableZoneExclusivity());
347 putDefaultStringProperty(CommonClientConfigKey.ClientClassName, getDefaultClientClassname());
348 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname());
349 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname());
350 putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname());
351 putDefaultBooleanProperty(CommonClientConfigKey.PrioritizeVipAddressBasedServers, getDefaultPrioritizeVipAddressBasedServers());
352 putDefaultFloatProperty(CommonClientConfigKey.MinPrimeConnectionsRatio, getDefaultMinPrimeConnectionsRatio());
353 putDefaultStringProperty(CommonClientConfigKey.PrimeConnectionsClassName, getDefaultPrimeConnectionsClass());
354 putDefaultStringProperty(CommonClientConfigKey.NIWSServerListClassName, getDefaultSeverListClass());
355 putDefaultStringProperty(CommonClientConfigKey.VipAddressResolverClassName, getDefaultVipaddressResolverClassname());
356 putDefaultBooleanProperty(CommonClientConfigKey.IsClientAuthRequired, getDefaultIsClientAuthRequired());
357 // putDefaultStringProperty(CommonClientConfigKey.RequestIdHeaderName, getDefaultRequestIdHeaderName());
358 putDefaultBooleanProperty(CommonClientConfigKey.UseIPAddrForServer, getDefaultUseIpAddressForServer());
359 putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
360 }
361 }
也可以在配置文件中定制配置,例如配置超时和重试:
1 # 全局配置
2 ribbon:
3 # 客户端读取超时时间
4 ReadTimeout: 3000
5 # 客户端连接超时时间
6 ConnectTimeout: 3000
7 # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
8 OkToRetryOnAllOperations: false
9 # 重试次数
10 MaxAutoRetries: 1
11 # 最多重试几个实例
12 MaxAutoRetriesNextServer: 1
13
14 # 只针对 demo-producer 客户端
15 demo-producer:
16 ribbon:
17 # 客户端读取超时时间
18 ReadTimeout: 5000
19 # 客户端连接超时时间
20 ConnectTimeout: 3000
2、均衡策略 — IRule
IRule 是最终选择 Server 的策略规则类,核心的接口就是 choose。
1 public interface IRule{
2
3 // 选择 Server
4 public Server choose(Object key);
5
6 // 设置 ILoadBalancer
7 public void setLoadBalancer(ILoadBalancer lb);
8
9 // 获取 ILoadBalancer
10 public ILoadBalancer getLoadBalancer();
11 }
Ribbon 提供了丰富的负载均衡策略,我们也可以通过配置指定使用某个均衡策略。下面是整个Ribbon提供的 IRule 均衡策略。
3、服务检查 — IPing
IPing 是用于定期检查 Server 的可用性的,它只提供了一个接口,用来判断 Server 是否存活:
1 public interface IPing {
2
3 public boolean isAlive(Server server);
4 }
IPing 也提供了多种策略可选,下面是整个 IPing 体系结构:
4、获取服务列表 — ServerList
ServerList 提供了两个接口,一个是第一次获取 Server 列表,一个是更新 Server 列表,其中 getUpdatedListOfServers 会每被 Loadbalancer 隔 30 秒调一次来更新 allServerList。
1 public interface ServerList<T extends Server> {
2
3 public List<T> getInitialListOfServers();
4
5 /**
6 * Return updated list of servers. This is called say every 30 secs
7 * (configurable) by the Loadbalancer's Ping cycle
8 */
9 public List<T> getUpdatedListOfServers();
10 }
ServerList 也提供了多种实现,ServerList 体系结构如下:
5、过滤服务 — ServerListFilter
ServerListFilter 提供了一个接口用来过滤出可用的 Server。
1 public interface ServerListFilter<T extends Server> {
2
3 public List<T> getFilteredListOfServers(List<T> servers);
4 }
ServerListFilter 体系结构如下:
6、服务列表更新 — ServerListUpdater
ServerListUpdater 有多个接口,最核心的就是 start 开启定时任务调用 updateAction 来更新 allServerList。
1 public interface ServerListUpdater {
2
3 /**
4 * an interface for the updateAction that actually executes a server list update
5 */
6 public interface UpdateAction {
7 void doUpdate();
8 }
9
10 /**
11 * start the serverList updater with the given update action
12 * This call should be idempotent.
13 */
14 void start(UpdateAction updateAction);
15 }
默认有两个实现类:
7、负载均衡器 — ILoadBalancer
ILoadBalancer 是负载均衡选择服务的核心接口,主要提供了如下的获取Server列表和根据客户端名称选择Server的接口。
1 public interface ILoadBalancer {
2
3 // 添加Server
4 public void addServers(List<Server> newServers);
5
6 // 根据key选择一个Server
7 public Server chooseServer(Object key);
8
9 // 获取存活的Server列表,返回 upServerList
10 public List<Server> getReachableServers();
11
12 // 获取所有Server列表,返回 allServerList
13 public List<Server> getAllServers();
14 }
ILoadBalancer 的体系结构如下:
SpringCloud 源码系列(4)—— 负载均衡 Ribbon的更多相关文章
- SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)
SpringCloud 源码系列(4)-- 负载均衡 Ribbon(上) SpringCloud 源码系列(5)-- 负载均衡 Ribbon(下) 五.Ribbon 核心接口 前面已经了解到 Ribb ...
- SpringCloud 源码系列(6)—— 声明式服务调用 Feign
SpringCloud 源码系列(1)-- 注册中心 Eureka(上) SpringCloud 源码系列(2)-- 注册中心 Eureka(中) SpringCloud 源码系列(3)-- 注册中心 ...
- SpringCloud之实现客户端的负载均衡Ribbon(二)
一 Ribbon简介 Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为.为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务 ...
- Dubbo 源码解析四 —— 负载均衡LoadBalance
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...
- SpringCloud 源码系列(1)—— 注册中心 Eureka(上)
Eureka 是 Netflix 公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡.熔断器.网关等)一起,被 Spring Cloud 整合为 Spring C ...
- SpringCloud 源码系列(3)—— 注册中心 Eureka(下)
十一.Eureka Server 集群 在实际的生产环境中,可能有几十个或者几百个的微服务实例,Eureka Server 承担了非常高的负载,而且为了保证注册中心高可用,一般都要部署成集群的,下面就 ...
- dubbo源码解析之负载均衡
在分布式系统中,负载均衡是必不可少的一个模块,dubbo 中提供了五种负载均衡的实现,在阅读这块源码之前,建议先学习负载均衡的基础知识.把看源码当做一个印证自己心中所想的过程,这样会得到事半功倍的效果 ...
- dubbo源码分析1——负载均衡
dubbo中涉及到的负载均衡算法只要有四种:Random LoadBalance(随机均衡算法).RoundRobin LoadBalance(权重轮循均衡算法).LeastAction LoadBa ...
- dubbo源码阅读之负载均衡
负载均衡 在之前集群的文章中,我们分析了通过监听注册中心可以获取到多个服务提供者,并创建多个Invoker,然后通过集群类如FailoverClusterInvoker将多个Invoker封装在一起, ...
随机推荐
- webpack 无法打包:No configuration file found and no output filename configured via CLI option
报错内容 No configuration file found and no output filename configured via CLI option.A configuration fi ...
- Seay源代码审计系统使用
Seay源代码审计系统简介 Seay源代码审计系统使用 如何使用"Seay源代码审计系统"扫描源代码漏洞 Seay源代码审计系统下载安装 github-Seay源代码审计系统
- 从执行上下文角度重新理解.NET(Core)的多线程编程[1]:基于调用链的”参数”传递
线程是操作系统能够进行运算调度的最小单位,操作系统线程进一步被封装成托管的Thread对象,手工创建并管理Thread对象已经成为了所能做到的对线程最细粒度的控制了.后来我们有了ThreadPool, ...
- Camtasia快捷键大全
Camtasia是一款专业屏幕录制软件,它能在任何颜色模式下轻松地记录屏幕动作,另外它还具有即时播放和编辑压缩的功能.在生活上应用范围相当的广泛.在实际运用中如果能了解到相关的快捷键知识,相信是一定程 ...
- CorelDRAW 条形码改不了字体如何解决?
看到有朋友提问说CorelDRAW条码生成设置里面的字体不能更改,是灰色的,不能选择.这个默认字体怎么改? 出现问题:条码生成设置里面的字体不能更改,是灰色的,不能选择. 解决方法一:找到C盘字体文件 ...
- CollectionUtils和StringUtils
1.StringUtils(常用-用来操作String的字符串)1.1 判断某字符串是否为空isEmpty StringUtils.isEmpty(null) = true StringUtils.i ...
- Java命令行启动jar包更改默认端口以及配置文件的几种方式
Java命令行启动jar包更改默认端口以及配置文件的几种方式 java -jar xxx.jar --server.port=8081 默认如果jar包没有启动文件,可以采用这种方式进行启动 java ...
- IO模式 select、poll、epoll
阻塞(blocking).非阻塞(non-blocking):最常听到阻塞与非阻塞这两个词就是在函数调用中,比如waitid这个函数,通过NOHANG参数可以把waitid设置为非阻塞的,也就是问询一 ...
- Python中的静态属性、实例属性、静态方法、实例方法之间的区别
- 如何在Linux下关闭ARP协议
方法一:临时关闭ARP协议 echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignoreecho 2 > /proc/sys/net/ipv4/conf ...