持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。 源码:github.com/Netflix/rib… 原理:www.jianshu.com/p/aafe33cc9…
问题:
- ribbon.restclient.enabled,开启此参数时,ribbon.ReadTimeout和ribbon.ConnectTimeout无效
- spring-retry模块,RetryListener.onError在修改choose(serviceId)后执行,导致获取的实例id错误
@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 | 负载均衡器客户端 |
| RibbonLoadBalancerContext | Ribbon负载均衡器客户端,extends LoadBalancerContext |
| ClientHttpRequestInterceptor | [接口]HTTP请求拦截器(intercept) |
| LoadBalancerInterceptor | 非spring-retry模块的重试拦截器,implements ClientHttpRequestInterceptor |
| RetryLoadBalancerInterceptor | spring-retry模块负载均衡重试核心逻辑拦截器,implements ClientHttpRequestInterceptor |
| ClientHttpRequestFactory | [接口]创建HTTP请求对象工厂(createRequest) |
| RibbonClientHttpRequestFactory | implements ClientHttpRequestFactory |
| LoadBalancedRetryFactory | [接口]创建负载均衡工厂包含重试、回退和监听信息(createRetryPolicy、createRetryListeners、createBackOffPolicy) |
| RibbonLoadBalancedRetryFactory | implements LoadBalancedRetryFactory |
| LoadBalancedRetryPolicy | [接口]负载均衡重试策略(canRetrySameServer、canRetryNextServer、close、registerThrowable、retryableStatusCode) |
| RibbonLoadBalancedRetryPolicy | implements LoadBalancedRetryPolicy |
| InterceptingClientHttpRequest | HTTP请求拦截器处理类(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();
}
}