Ribbon负载均衡的实现流程简要分析
SpringCloud中使用Netflix方案做分布式时,只需要在RestTemplate的bean定义上加一个注解@LoadBalanced,无需做其它任何操作就可以开启负载均衡,怎么做到的呢?
不从@LoadBalanced开始倒推,我觉得简单描述正向实现流程,更容易理解
从RestTemplate入手
Ribbon的负载均衡实现,其实就是利用了RestTemplate上的可自定义拦截器功能,给RestTemplate的bean定义上添加@LoadBalanced就是为了给RestTemplate对象添加上指定的拦截器
RestTemplate上具有的拦截器功能来自于RestTemplate的父类InterceptingHttpAccessor
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations
public abstract class InterceptingHttpAccessor extends HttpAccessor {
private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
// Take getInterceptors() List as-is when passed in here
if (this.interceptors != interceptors) {
this.interceptors.clear();
this.interceptors.addAll(interceptors);
AnnotationAwareOrderComparator.sort(this.interceptors);
}
//省略其它代码
}
}
接下来再看下是怎么加上的拦截器,自动加上的,就先看自动化配置LoadBalancerAutoConfiguration,相关配置如下
public class LoadBalancerAutoConfiguration {
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
//执行RestTemplateCustomizer.customize()方法,对restTemplate进行增强
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
//定义LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
//将定义LoadBalancerInterceptor拦截器添加到restTemplate的拦截器列表中
restTemplate.setInterceptors(list);
};
}
}
}
LoadBalancerInterceptor里怎么做的呢?看下
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
发现了,从服务调用URI中获取到serviceName,然后再传给LoadBalancerClient
如果去看注解@LoadBalanced的定义,也能发现里面提到了LoadBalancerClient
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
看下LoadBalancerClient,就是个接口
public interface LoadBalancerClient extends ServiceInstanceChooser {
// 根据传入的serviceId,从负载均衡器中挑选服务实例执行请求内容
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
// 根据传入的serviceId,用指定的服务实例执行请求内容
<T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException;
// 将服务实例转换成实际的URI信息
URI reconstructURI(ServiceInstance instance, URI original);
}
再看下父类ServiceInstanceChooser
public interface ServiceInstanceChooser {
// 从负载均衡器中挑选服务实例
ServiceInstance choose(String serviceId);
}
而LoadBalancerClient的实现呢,只有一个,就是RibbonLoadBalancerClient,那么主要逻辑肯定就在这里面了
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
//从springClientFactory中根据serviceId获取到RibbonLoadBalancerContext
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
URI uri;
Server server;
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
server = ribbonServer.getServer();
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
}
else {
server = new Server(instance.getScheme(), instance.getHost(),
instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
uri = updateToSecureConnectionIfNeeded(original, clientConfig,
serverIntrospector, server);
}
//RibbonLoadBalancerContext.reconstructURIWithServer(),将服务名的调用URL转换成真实IP地址的实际操作逻辑
return context.reconstructURIWithServer(server, uri);
}
//代码太多,省略其它代码
}
里面还有些其它代码,就不一一贴出来了,其它的基本都是对LoadBalancerClient的实现,看这个重构URI的方法就能看到RibbonLoadBalancerContext和IClientConfig都是从SpringClientFactory clientFactory里取出来的,而且也能看到RibbonLoadBalancerClient的构造函数也只需要这一个属性,有SpringClientFactory就能创建出来RibbonLoadBalancerClient对象,那这个肯定很重要,看下是什么
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
public IClientConfig getClientConfig(String name) {
return getInstance(name, IClientConfig.class);
}
public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
return getInstance(serviceId, RibbonLoadBalancerContext.class);
}
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
//获取负载均衡器
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
//省略其它代码
}
SpringClientFactory是NamedContextFactory的子类,这些获取ILoadBalancer、IClientConfig、RibbonLoadBalancerContext的方法,都是调用父类中的getInstance(name, type)而已
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {
private final String propertySourceName;
private final String propertyName;
//存放AnnotationConfigApplicationContext的容器,每一个服务都对应一个AnnotationConfigApplicationContext,没有就创建
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private Map<String, C> configurations = new ConcurrentHashMap<>();
private ApplicationContext parent;
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
//通过name(也就是serviceId)获取到AnnotationConfigApplicationContext,然后再从AnnotationConfigApplicationContext里取到type类型的bean
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
return context.getBean(type);
}
return null;
}
}
到这也就基本到头了,NamedContextFactory是专门定义出来的,每一个需要调用的微服务都创建一个AnnotationConfigApplicationContext,并存放各自的bean,也就是说IOC容器分离开了,没有混在一起,这样可以做到针对不同的微服务情况做不同的配置
再接下来,就是找到这些配置,或者自定义这些bean的配置就可以了,也就是负载均衡器、负载均衡策略等的配置了
自动配置
在RibbonAutoConfiguration中有如下相关配置
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());
}
//省略其它配置
}
创建SpringClientFactory时需要设置一个属性List
- 来自于RibbonClientConfigurationRegistrar中registerBeanDefinitions()方法的执行
- RibbonClientConfigurationRegistrar会执行是因为@RibbonClient上有个@Import(RibbonClientConfigurationRegistrar.class)
- @RibbonClient是@RibbonClients注解里的属性
- @RibbonClients又在RibbonAutoConfiguration和RibbonEurekaAutoConfiguration上有了使用,并且在EurekaRibbonClientConfiguration上标注时,做了配置@RibbonClients(defaultConfiguration =
- EurekaRibbonClientConfiguration中呢又包含EurekaClientConfig(eureka.client相关属性配置类),EurekaInstanceConfig(eureka.instance相关属性配置类)
就这样一长串的关联,就能把相关bean的配置给引入到了SpringClientFactory中,并可以按服务给分别设定了IOC容器并做了bean注入(PS:我也只是看出来了相关性,没看那么具体)
通过这两个Bean的配置,IOC容器中就有了RibbonLoadBalancerClient对象
在RibbonClientConfiguration中,有如下相关配置
public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@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 IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
@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);
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
}
可以看到,默认的负载均衡器ILoadBalancer配置是ZoneAwareLoadBalancer,默认的负载策略IRule配置是ZoneAvoidanceRule,默认的实例状态检查IPing配置是DummyPing
这几个也是我们常用的自定义的配置项,可以针对不同需要进行配置,@ConditionalOnMissingBean,如果有手动配置了,那么自动配置便不会生效了
自定义配置
负载均衡器ZoneAwareLoadBalancer是实现好的区域感知均衡器,对同Zone区域的服务友好会优先调用,继承自DynamicServerListLoadBalancer动态服务列表均衡器,DynamicServerListLoadBalancer又继承自BaseLoadBalancer基础均衡器,各项都有了基本实现,如果有需要进行负载均衡器调整的话,得需要自己去实现了,可以通过继承DynamicServerListLoadBalancer或BaseLoadBalancer来做
默认的负载策略IRule配置是ZoneAvoidanceRule,也是对区域友好的,要调整的话,这项有其它的选择,在IRule下有很多实现,如:
- RandomRule随机策略
- RoundRobinRule线性轮询策略
- RetryRule重试机制策略(基于其它策略的具备重试机制选择策略[默认RoundRobinRule]和超时时间[默认500毫秒]的配置)
- WeightedResponseTimeRule响应时间权重策略(继承自RoundRobinRule并扩展了按服务响应时间计算权重)
- BestAvailableRule最佳可用策略(过滤掉故障实例,选出并发请求数最小的实例)
- AvailabilityFilteringRule可用性过滤器策略(选取没有故障且没有超过指定的可配置并发阀值的实例)
默认的实例状态检查IPing配置是DummyPing,Dummy就是“假的”的意思,看代码里也是,检查时什么操作都没做,直接返回true,其它可选配置有:
- NIWSDiscoveryPing(不做ping检查,只是判断下服务状态是否是UP)
- NoOpPing无操作检查(也是啥都不做)
- PingConstant(通过一个可设置的boolean变量来控制检查结果)
- PingUrl连通性检查(可指定要ping的url路径)
总结
大概流程在这了,写的不好,请见谅,有错误的地方欢迎指正,感谢~