源码解析-Ribbon

222 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。 源码:github.com/Netflix/rib… 原理:www.jianshu.com/p/aafe33cc9…

问题:

  1. ribbon.restclient.enabled,开启此参数时,ribbon.ReadTimeout和ribbon.ConnectTimeout无效
  2. spring-retry模块,RetryListener.onError在修改choose(serviceId)后执行,导致获取的实例id错误

image.png

image.png

image.png

		@Autowired
		private SpringClientFactory springClientFactory;

		@Bean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
			return restTemplate -> restTemplate
					.setRequestFactory(ribbonClientHttpRequestFactory);
		}

		@Bean
		public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
			return new RibbonClientHttpRequestFactory(this.springClientFactory);
		}
IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
RibbonProperties ribbon = RibbonProperties.from(clientConfig);

 DefaultClientConfigImpl.getClientConfigWithDefaultValues(name)

组成部分

接口描述默认实现类
ILoadBalancer定义负载均衡核心接口(addServers、chooseServer、getReachableServers等)ZoneAwareLoadBalancer
IRule定义负载均衡策略的接口(choose、setLoadBalancer、getLoadBalancer)ZoneAvoidanceRule
IPing定义服务检查可用性的接口(isAlive)DummyPing
IClientConfig加载Ribbon配置相关接口DefaultClientConfigImpl
LoadBalancerClient定义客户端负载均衡接口(choose、execute、reconstructURI)RibbonLoadBalancerClient
ServerList定义获取服务列表方法的接口(getInitialListOfServers、getUpdatedListOfServers)ConfigurationBasedServerList
ServerListFilter定义特定期望获取服务列表方法的接口(getFilteredListOfServers)ZonePreferenceServerListFilter
ServerListUpdater定义动态更新服务列表的接口(start、stop、getLastUpdate等)PollingServerListUpdater

ILoadBalancer(负载均衡调度管理)

ZoneAwareLoadBalancer是对DynamicServerListLoadBalancer的扩展,它主要增加了区域过滤的功能。

IRule(负载均衡策略)

  • RandomRule:随机
  • RoundRobinRule:轮询
  • RetryRule:轮询重试
  • BestAvailableRule:最小请求数
  • WeightedResponseTimeRule:响应时间加权,响应时间越短权重越大
  • ZoneAvoidancePredicate:以一个区域为单位考察可用性,对于不可用的区域整个丢弃
  • AvailabilityPredicate:过滤掉连接数过多的Server
  • AvailabilityFilteringRule:使用AvailabilityPredicate,过滤掉那些一直连接失败被标记的Server,然后轮询
  • ZoneAvoidanceRule:使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server

IPing(心跳检测)

  • PingUrl:使用HttpClient去get请求某个url,判断其是否alive
  • PingConstant:固定返回某服务是否可用,默认返回true
  • NoOpPing:没有任何操作,直接返回true
  • DummyPing:同样是直接返回true

核心逻辑

描述
RibbonAutoConfiguration[自动配置类]初始化负载均衡重试工厂类RibbonLoadBalancedRetryFactory
LoadBalancerAutoConfiguration[自动配置类]注册HTTP请求拦截器RetryLoadBalancerInterceptor
RibbonClientConfiguration客户端配置类,初始化核心对象(IClientConfig、ILoadBalancer、IRule、IPing、ServerListUpdater等)
LoadBalancerContext负载均衡器客户端
RibbonLoadBalancerContextRibbon负载均衡器客户端,extends LoadBalancerContext
ClientHttpRequestInterceptor[接口]HTTP请求拦截器(intercept)
LoadBalancerInterceptor非spring-retry模块的重试拦截器,implements ClientHttpRequestInterceptor
RetryLoadBalancerInterceptorspring-retry模块负载均衡重试核心逻辑拦截器,implements ClientHttpRequestInterceptor
ClientHttpRequestFactory[接口]创建HTTP请求对象工厂(createRequest)
RibbonClientHttpRequestFactoryimplements ClientHttpRequestFactory
LoadBalancedRetryFactory[接口]创建负载均衡工厂包含重试、回退和监听信息(createRetryPolicy、createRetryListeners、createBackOffPolicy)
RibbonLoadBalancedRetryFactoryimplements LoadBalancedRetryFactory
LoadBalancedRetryPolicy[接口]负载均衡重试策略(canRetrySameServer、canRetryNextServer、close、registerThrowable、retryableStatusCode)
RibbonLoadBalancedRetryPolicyimplements LoadBalancedRetryPolicy
InterceptingClientHttpRequestHTTP请求拦截器处理类(ClientHttpRequestInterceptor)
LoadBalancerCommand非spring-retry模块负载均衡重试核心逻辑

源码分析

初始化

RibbonAutoConfiguration

org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
		AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
		ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
  // 1. 初始化LoadBalancedRetryFactory
	@Bean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	@ConditionalOnMissingBean
	public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
			final SpringClientFactory clientFactory) {
		return new RibbonLoadBalancedRetryFactory(clientFactory);
	}
	
	// 2. 开启ribbon.restclient.enabled时实例化,优先于LoadBalancerAutoConfiguration加载
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HttpRequest.class)
	@ConditionalOnRibbonRestClient
	protected static class RibbonClientHttpRequestFactoryConfiguration {
		@Autowired
		private SpringClientFactory springClientFactory;
		// 2.2 创建RestTemplateCustomizer,配置ClientHttpRequestFactory
		@Bean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
			return restTemplate -> restTemplate
					.setRequestFactory(ribbonClientHttpRequestFactory);
		}
		// 2.1 创建HTTP对象工厂类
		@Bean
		public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
			return new RibbonClientHttpRequestFactory(this.springClientFactory);
		}
	}
}

org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

// 2. 如果有spring-retry模块时,添加RetryLoadBalancerInterceptor拦截器;否则添加LoadBalancerInterceptor拦截器,
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
  // 2. 没有spring-retry模块时实例化
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
	}

  // 2. 有spring-retry模块时实例化
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {
    // @ConditionalOnMissingBean表示:开启ribbon.httpclient.enabled时,不注册拦截器
		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}
	}
}

org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
		RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public IRule ribbonRule(IClientConfig config) {
		if (this.propertiesFactory.isSet(IRule.class, name)) {
			return this.propertiesFactory.get(IRule.class, config, name);
		}
		ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
		rule.initWithNiwsConfig(config);
		return rule;
	}
	@Bean
	@ConditionalOnMissingBean
	public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
		return new PollingServerListUpdater(config);
	}
  4. 初始化BaseLoadBalancer
	@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}
}

服务列表更新机制

com.netflix.loadbalancer.PollingServerListUpdater.java

public class PollingServerListUpdater implements ServerListUpdater {
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        // 更新服务列表
                        updateAction.doUpdate();
                    } catch (Exception e) {}
                }
            };
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    // 刷新服务列表时间间隔,默认30秒。配置参数[ribbon.ServerListRefreshInterval]
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {}
    }
}

服务心跳检测任务

com.netflix.loadbalancer.BaseLoadBalancer.java

// 启动服务检测任务
void setupPingTask() {
    // DummyPing时,跳过检测
    if (canSkipPing()) {return;}
    lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name, true);
    // 执行时间间隔。配置参数[ribbon.NFLoadBalancerPingInterval]
    lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
    // 快速执行一次任务
    forceQuickPing();
}

HTTP请求拦截器

org.springframework.http.client.InterceptingClientHttpRequest.java

private class InterceptingRequestExecution implements ClientHttpRequestExecution {
	private final Iterator<ClientHttpRequestInterceptor> iterator;
	public InterceptingRequestExecution() {
		this.iterator = interceptors.iterator();
	}
	@Override
	public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
		if (this.iterator.hasNext()) {
			ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
			// 执行拦截器业务RetryLoadBalancerInterceptor
			return nextInterceptor.intercept(request, body, this);
		}
		else {
		  // 拦截器执行完成回调,执行HTTP请求
			HttpMethod method = request.getMethod();
			ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
			request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
			if (body.length > 0) {
				if (delegate instanceof StreamingHttpOutputMessage) {
					StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
					streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
				}
				else {
					StreamUtils.copy(body, delegate.getBody());
				}
			}
			return delegate.execute();
		}
	}
}

org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor.java

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
	// 获取重试策略RibbonLoadBalancedRetryPolicy
	final LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory.createRetryPolicy(serviceName, this.loadBalancer);
	RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);
	return template.execute(context -> {
	  // 获取服务实例
		ServiceInstance serviceInstance = null;
		// 执行业务逻辑,回调InterceptingClientHttpRequest.execute
		ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer
				.execute(serviceName, serviceInstance,
						this.requestFactory.createRequest(request, body, execution));
		int statusCode = response.getRawStatusCode();
		// 根据HTTP响应状态码重试
		if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
			byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
			response.close();
			throw new ClientHttpResponseStatusCodeException(serviceName, response,
					bodyCopy);
		}
		return response;
	}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
	  // 最后一次重试失败逻辑
		@Override
		protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) {
			return response;
		}
	});
}

使用

配置(CommonClientConfigKey)

# 开启RestTemplate模块,默认true
spring:
  cloud:
    loadbalancer:
      retry: true

ribbon:
  eager-load:
    # 饥饿加载模式,服务启动时初始化,默认false
    enabled: false
    # 指定客户端名称,逗号分隔
    clients: 
  httpclient:
    # 开启HttpClient线程池,默认true
    enabled: true
  restclient:
    # 开启NFHttpClient,与负载均衡器集成的REST客户端,不走retryTemplate逻辑,默认false
    enabled: false
  # 开启服务启动后检查服务列表是否可用,默认false
  EnablePrimeConnections: false
  # 检查服务GET请求地址,默认http://host:port + PrimeConnectionsURI
  PrimeConnectionsURI: /
  # 服务列表刷新间隔,默认30秒
  ServerListRefreshInterval: 30000
  # 服务检查时间间隔,默认30秒
  NFLoadBalancerPingInterval: 30000
  
## HttpClient连接池配置,EnableConnectionPool开启生效,如果配置了Feign优先使用Feign连接池
  MaxTotalConnections: 200
  MaxConnectionsPerHost: 50
  MaxTotalHttpConnections: 200
  # 开启连接池过期清理任务,默认true
  ConnectionPoolCleanerTaskEnabled: true
  # 清理过期的连接时间间隔,默认30秒
  ConnectionCleanerRepeatInterval: 30000
  # 定义为空闲连接的时间,默认30秒
  ConnIdleEvictTimeMilliSeconds: 30000
  # 保持长连接时间,默认900秒
  PoolKeepAliveTime: 900
  # 开启gzip内容压缩,默认true
  GZipPayload: true
# 使用RestTemplate时,设置ConnectTimeout和ReadTimeout无效,从ClientHttpRequestFactory中设置有效
# 建立连接超时时间,默认0(ribbon.restclient.enabled开启生效)
  ConnectTimeout: 2000
# 数据传输超时时间,默认0(ribbon.restclient.enabled开启生效)
  ReadTimeout: 3000

## 重试配置
  # 对所有操作请求都进行重试,默认false只允许GET重试
  OkToRetryOnAllOperations: true
  # 对当前实例的重试次数,默认0
  MaxAutoRetries: 0
  # 切换服务器实例的重试次数,默认1
  MaxAutoRetriesNextServer: 1
  # 指定需要重试的响应状态码,默认仅当请求报错的时候重试,Retry有效
  retryableStatusCodes: 502,503
  # http连接是否自动处理重定向,默认false
  FollowRedirects: false
  # 开启Zone亲近和排斥,ZoneAffinityServerListFilter使用
  EnableZoneAffinity: false
  EnableZoneExclusivity: false
  
## 具体客户端配置(全局默认值从RibbonClientConfiguration中初始化)
flight-demo2-service:
  ribbon:
    # 覆盖默认配置
    MaxAutoRetries: 0
    MaxAutoRetriesNextServer: 1
    # 轮训策略
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    # 服务检测
    NFLoadBalancerPingClassName:
    NFLoadBalancerClassName:
    NIWSServerListFilterClassName:
    # 配置服务列表,多个逗号分隔
    listOfServers: ip:port,ip1:port
    # 服务列表,测试时使用配置指定的服务列表[ribbon.listOfServers]
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList

RestTemplate + @LoadBalanced

@Configuration
public class Config {
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        // 提供一个标记@LoadBalanced的RestTemplate Bean
        return new RestTemplate();
    }
}

@RestController
public class HelloController {
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("/hi")
    public String hi() {
        // 直接使用即可
        return restTemplate.getForEntity("http://SERVER-NAME/hello", String.class).getBody();
    }
}

Feign

// 定义Feign接口
@FeignClient(value = "SERVER-NAME", fallbackFactory = HelloClientFallbackFactory.class)
public interface HelloClient {
    @GetMapping("/hello")
    String hello();
}

// 订单熔断快速失败回调
@Component
public class HelloClientFallbackFactory implements FallbackFactory<HelloClient>, HelloClient {
    @Override
    public HelloClient create(Throwable throwable) {
        return this;
    }

    @Override
    public String hello() {
        return "熔断";
    }
}

// 使用
@RestController
public class HelloController {
    @Resource
    private HelloClient helloClient;

    @GetMapping("/hello")
    public String hello() {
        return helloClient.hello();
    }
}