Ribbon是Netflix公司开源的一个客户端负载均衡的项目。支持脱离Eureka注册中心单独使用。其负载均衡主要是通过SpringBoot中RestTemplate协助实现的。
关于Ribbon的分析其中涉及多个核心类:
核心功能是初始化用于客户端负载均衡的RibbonLoadBalancerClient
。
@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// LoadBalancerAutoConfiguration 该配置类自动化配置 落后于 当前配置类
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
...
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
...
public PropertiesFactory() {
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
@AutoConfigureAfter & @AutoConfigureAfter 是在通过SPI机制加载到所有自动装配的候选类后控制集合中元素顺序。
核心功能是初始化RestTemplate
处理请求过程中需要的拦截器LoadBalancerInterceptor
。
拦截器的作用:通过其成员属性RibbonLoadBalancerClient对客户端进行负载均衡处理。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 注解 @Qualifier 自动装配
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
// LoadBalancerInterceptorConfig#restTemplateCustomizer
customizer.customize(restTemplate);// 调用 RestTemplateCustomizer 的实现类
}
}
});
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
//初始化拦截器
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// RestTemplateCustomizer 接口的实现
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 添加拦截器
restTemplate.setInterceptors(list);
};
}
}
}
集合restTemplates元素是指用户显示注入的RestTemplate实例。通常情况下用户应用服务中自定义的候选类其初始化时机是优先于自动装配的候选类。
通过上述1、2章节得知,此时RibbonLoadBalancerClient触发客户端的负载均衡,但此时还缺少关键一步:负载均衡策略IRule。
负载均衡策略的选择时机:
AnnotationConfigApplicationContext
触发RibbonClientConfiguration
实例的初始化。RibbonClientConfiguration核心类并非是自动装配类,而是由AnnotationConfigApplicationContext显示注册生成的,并且该核心类并非受制于Spring常规IOC容器管理
protected <T> T doExecute(URI url,HttpMethod method,RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor){
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
...
// 执行拦截器
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
...
}
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);// 拦截器处理流程
}
else {
...
}
通过请求拦截器改变请求流程,从传统的请求流程改变至从注册中心获取目标服务的集群网络地址
,从而通过负载均衡算法获取到具体目标服务的网络地址。
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
// 被调用方的 服务名
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
return execute(serviceId, request, null);
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
// 初始化 IOC 容器,加载配置类信息,尤其是 初始化配置类 RibbonClientConfiguration
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过 RibbonClientConfiguration 获取到具体的 IRule 实例,执行对应的策略
Server server = getServer(loadBalancer);
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId), null);
}
protected Server getServer(ILoadBalancer loadBalancer) {
return getServer(loadBalancer, null);
}
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
}
对ILoadBalancer接口的子类解析参考文章:SpringCloud之RibbonClientConfiguration
public class DefaultClientConfigImpl implements IClientConfig {
Map<String, Object> properties = new ConcurrentHashMap<String, Object>();
Map<String, DynamicStringProperty> dynamicProperties = new ConcurrentHashMap<String, DynamicStringProperty>();
}
public void loadProperties(String restClientName){
loadDefaultValues();//加载默认的配置时间,其中包括链接超时、读超时
//此处会重新解析全部定制化配置信息,覆盖原有的全局配置信息
for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
String key = keys.next();
String prop = key;
if (prop.startsWith(getNameSpace())){
prop = prop.substring(getNameSpace().length() + 1);
}
setPropertyInternal(prop, getStringValue(props, key));
}
}
public void loadDefaultValues() {
...
//默认读写都是5000ms
putDefaultIntegerProperty(CommonClientConfigKey.ConnectTimeout, getDefaultConnectTimeout());
putDefaultIntegerProperty(CommonClientConfigKey.ReadTimeout, getDefaultReadTimeout());
...
}
// propName:ConnectTimeout、ReadTimeout
protected void putDefaultIntegerProperty(IClientConfigKey propName, Integer defaultValue) {
// 内部通过Spring中Environment直接读取配置文件中配置的实际取值,替代默认值。
// getDefaultPropName(propName):ribbon.ReadTimeout、ribbon.ConnectTimeout
Integer value = ConfigurationManager.getConfigInstance().getInteger(
getDefaultPropName(propName), defaultValue);
setPropertyInternal(propName, value);
}
protected void setPropertyInternal(final String propName, Object value) {
....
//propName:ReadTimeout、ConnectTimeout,stringValue:实际值
properties.put(propName, stringValue);
String configKey = getConfigKey(propName);
// configKey:provider-service.ribbon.ReadTimeout、provider-service.ribbon.ConnectTimeout
DynamicStringProperty prop = DynamicPropertyFactory.getInstance().getStringProperty(configKey, null);
...
dynamicProperties.put(propName, prop);
}
properties
:设置全局配置信息。
ribbon.ReadTimeout=3000
ribbon.ConnectTimeout=3000
dynamicProperties
:设置个性化|定制化配置信息。
provider-service.ribbon.ReadTimeout=4000
provider-service.ribbon.ConnectTimeout=4000
备注:针对全局配置信息,首先会读取用户配置,否则默认值。其次如果存在定制化,则覆盖全局配置信息。最终再次将全局配置设置为1s。所以ribbon默认超时、连接时间均为1s。
ZoneAwareLoadBalancer的父类DynamicServerListLoadBalancer。
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);//开启定时任务更新客户端本地注册列表信息
enableAndInitLearnNewServersFeature();
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
}
public void enableAndInitLearnNewServersFeature() {
serverListUpdater.start(updateAction);//PollingServerListUpdater#start
}
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
//ConsulServerList:利用ConsulClient客户端通过接口/v1/health/service/查询serverName所有的服务网络地址列表
servers = serverListImpl.getUpdatedListOfServers();
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
}
}
updateAllServerList(servers);
}
protected void updateAllServerList(List<T> ls) {
// other threads might be doing this - in which case, we pass
if (serverListUpdateInProgress.compareAndSet(false, true)) {
try {
for (T s : ls) {
s.setAlive(true);
}
setServersList(ls);
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false);
}
}
}
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
updateAction.doUpdate();// DynamicServerListLoadBalancer.UpdateAction#doUpdate
lastUpdated = System.currentTimeMillis();
}
};
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,//初始化延迟时间为1s
refreshIntervalMs,// 任务执行完毕之后,30s后继续执行任务
TimeUnit.MILLISECONDS
);
}
}
通过定时任务定时更新客户端本地的注册列表信息。
public interface ILoadBalancer {
public Server chooseServer(Object key);
public List<Server> getReachableServers();
public List<Server> getAllServers();
}
从接口抽象方法可以看出持有目标服务列表 以及 实现负载均衡策略 IRule 对目标服务的选择。
SimpleClientHttpRequestFactory:如果单独使用ribbon发送HTTP请求,使用的客户端默认好像HttpURLConnection。
针对HttpURLConnection,其超时时间、连接超时等配置好像只能通过SimpleClientHttpRequestFactory初始化完成。
设置ribbon.ReadTimeout、ribbon.ConnectTimeout应该没有效果。猜测这俩选项是针对feign设置的。
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);
}
if (this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);
}
connection.setDoInput(true);
if ("GET".equals(httpMethod)) {
connection.setInstanceFollowRedirects(true);
}
else {
connection.setInstanceFollowRedirects(false);
}
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
connection.setRequestMethod(httpMethod);
}