在spring cloud微服务体系中,可以用ribbon来做客户端的负载均衡。而这,只要在RestTemplate实例上添加了⼀个@LoadBalanced注解就能实现了,接下来让我们分析下这背后的过程吧。
首先,老规矩,通过spring.factories文件,找到RibbonAutoConfiguration类,发现RibbonAutoConfiguration类装配后,会去装配LoadBalancerAutoConfiguration类,我们先看LoadBalancerAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
...
}
可以看到这个类必须有RestTemplate类存在才会去装配,而且他声明了一个List的集合,会自动注入那些添加了@LoadBalanced注解的RestTemplate对象。接着我们再去看往下看这个类
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@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);
restTemplate.setInterceptors(list);
};
}
}
遍历可用定制器,给注入的每个restTemplate添加一个拦截器LoadBalancerInterceptor,所以,我们来分析一下LoadBalancerInterceptor,对于restTemplate的请求会被LoadBalancerInterceptor的intercept()⽅法拦截
@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,然后获取uri中的服务名,调用loadBalancer的execute方法,执行具体请求。loadBalancer的实现类是RibbonLoadBalancerClient是在RibbonAutoConfiguration中注入的,在注入LoadBalancerInterceptor时,参数中传入了这个loadBalancer,会去容器中找到对应的LoadBalancerClient。
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
RibbonLoadBalancerClient最终调用的是RibbonLoadBalancerClient#execute(String serviceId, LoadBalancerRequest request, Object hint)
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
//获取负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//通过负载均衡器选择一个最终要使用的server实例
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
//把server封装成RibbonServer
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
//执行请求
return execute(serviceId, ribbonServer, request);
}
首先获取获取负载均衡器,再根据负载均衡器去拿到要使用的server实例,再去调用请求执行方法。
获取负载均衡器
获取负载均衡器RibbonLoadBalancerClient#getLoadBalancer
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
可以看到,获取负载均衡器方法会委托给clientFactory,这个clientFactory就是SpringClientFactory,是在RibbonAutoConfiguration中注入的,在创建RibbonLoadBalancerClient会作为参数传递进去。
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
在SpringClientFactory的构造函数中有个装配类是RibbonClientConfiguration,这个类中装配了一些负载均衡的组件
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@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 IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
//服务列表
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
//负载均衡器
@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
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
...
}
继续回到上面RibbonLoadBalancerClient#getLoadBalancer方法会委托给SpringClientFactory#getLoadBalancer
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
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);
}
调用父类去获取ILoadBalancer实例NamedContextFactory#getInstance
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;
}
可以看到context.getBean(type)方法,会去容器中找到对应ILoadBalancer类型的bean对象返回,这里返回的就是之前配置类注入的ZoneAwareLoadBalancer。而此时的ZoneAwareLoadBalancer其实已经拿到所有的server服务列表信息了。在创建ZoneAwareLoadBalancer时,他的构造函数会调用父类构造DynamicServerListLoadBalancer#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);
}
重点关注DynamicServerListLoadBalancer#restOfInit
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
//开启定时任务,每隔一段时间从Eureka缓存中获取新的服务实例信息
enableAndInitLearnNewServersFeature();
//执行更新服务信息
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
首先他会开启定时任务,每隔一段时间从Eureka缓存中获取新的服务实例信息,DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
serverListUpdater.start(updateAction);
}
serverListUpdater就是在RibbonClientConfiguration中注入的
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
调用他的start方法
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
首先定义了一个线程,线程的逻辑就是调用传进来的updateAction的doUpdate方法,然后再开启延迟定时任务。接着看具体执行的逻辑doUpdate在DynamicServerListLoadBalancer中
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
跟进DynamicServerListLoadBalancer#updateListOfServers
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
跟Eureka结合使用,这里serverListImpl会在EurekaRibbonClientConfiguration 中注入初始化,实际上就是调用DiscoveryEnabledNIWSServerList#getUpdatedListOfServers
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
return obtainServersViaDiscovery();
}
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
logger.warn("EurekaClient has not been initialized yet, returning an empty list");
return new ArrayList<DiscoveryEnabledServer>();
}
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
// if targetRegion is null, it will be interpreted as the same region of client
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
if(shouldUseOverridePort){
if(logger.isDebugEnabled()){
logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
}
// copy is necessary since the InstanceInfo builder just uses the original reference,
// and we don't want to corrupt the global eureka copy of the object which may be
// used by other clients in our system
InstanceInfo copy = new InstanceInfo(ii);
if(isSecure){
ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
}else{
ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
}
}
DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
serverList.add(des);
}
}
if (serverList.size()>0 && prioritizeVipAddressBasedServers){
break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
}
}
}
return serverList;
}
通过eurekaClientProvider获取对应EurekaClient,通过vipAdress(实际就是serviceName)获取对应注册表集合信息,将注册信息组装成DiscoveryEnabledServer,放入serverList返回。
然后再经过服务列表过滤器过滤,调用DynamicServerListLoadBalancer#updateAllServerList将服务列表设置到负载均衡器LoadBalancer上
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); // set so that clients can start using these
// servers right away instead
// of having to wait out the ping cycle.
}
//设置到负载均衡器上
setServersList(ls);
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false);
}
}
}
注意到这里为止,只是定义了一个延迟任务,还没有执行。在DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature方法,创建完延迟任务后,调用DynamicServerListLoadBalancer#updateListOfServers方法,这里执行了一遍延迟任务里的逻辑,从Eureka Client缓存中,获取服务列表设置到负载均衡器中。
选择最终要使用的server实例
选择一个最终要使用的server实例,RibbonLoadBalancerClient#getServer
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
调用负载均衡器的chooseServer方法,ZoneAwareLoadBalancer#chooseServer
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
...
}
如果是一个zone的话会走当前分支,调用父类chooseServer方法
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
会调用对应负载均衡策略的choose方法,去选择一个服务实例。而负载均衡策略如果我们没有配置,则默认的是ZoneAvoidanceRule.所以这里会调用其父类PredicateBasedRule#choose方法
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//从过滤后的服务列表中l根据轮询策略选择一个server
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
跟进AbstractServerPredicate#chooseRoundRobinAfterFiltering
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
//过滤
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
//incrementAndGetModulo方法轮询得到实例的索引值
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
在进行相应过滤后,会调用AbstractServerPredicate#incrementAndGetModulo方法得到对应索引值,再去列表中获取对应server实例
private int incrementAndGetModulo(int modulo) {
for (;;) {
//获取当前服务实例的索引值
int current = nextIndex.get();
//通过求余获取下一个索引值
int next = (current + 1) % modulo;
//通过cas设置下一个索引值
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
执行请求
回到RibbonLoadBalancerClient#execute方法,在获取到需要的server实例后,将server组装成RibbonServer,并继续执行RibbonLoadBalancerClient#execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest request)
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if (serviceInstance instanceof RibbonServer) {
server = ((RibbonServer) serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
向server实例发起请求的关键方法是request.apply(serviceInstance),这个request是之前调用LoadBalancerRequestFactory#createRequest方法创建出来的
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
return execution.execute(serviceRequest, body);
};
}
调用request.apply会去执行InterceptingClientHttpRequest.InterceptingRequestExecution#execute
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 {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
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();
}
}
最终调用AbstractClientHttpRequest#execute
public final ClientHttpResponse execute() throws IOException {
assertNotExecuted();
ClientHttpResponse result = executeInternal(this.headers);
this.executed = true;
return result;
}
此处,就已经到了RestTemplate底层执⾏的代码了,由此也将验证最终请求的调⽤还是靠的
RestTemplate。
小结
ribbon的负载均衡器,会在创建时去Eureka客户端缓存中获取服务列表并过滤,开启定时任务更新服务列表。在执行请求时,会调用负载均衡策略去根据相应策略从服务列表中选择一个服务实例,最后调用restTemplate去发起请求。
还有个服务实例存活探IPing是用来检测一个微服务实例是否有相应。Ribbon通过该组件来判断所持有的服务实例列表中各服务可用情况,如果检测到某服务实例不存在,则会从列表中及时移除。这里没有讨论,很多细节也没有去关注。有时间可以再去好好看看。