spring cloud ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具.基于HTTP和TCP的客户端均衡工具
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡,将Netflix中间层服务连接在一起.Ribbon客户端组件提供一系列完善配置,比如连接超时,重试等等.
借用网络上朋友的一幅图。
当我们使用RestTemplate或者Feign的时候,通过Ribbon实现负载均衡,获取到可用的远程服务列表,最后通过TCP完成最后的调用。
比如上图中的IRule就是从服务列表中获取可用服务的计算策略。
eureka client是一个实现服务注册和发现的中间件,可用的服务列表就是从eureka中获取到,因为每个关联的服务启动的时候都会注册到注册中心,调用方只需要从注册中心拉取对应的数据配置即可。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
只需要引入spring cloud ribbon的jar包,通过springboot的自动装配原理,我们就可以通过ribbon实现负载均衡。
@LoadBalanced
@Bean("restTemplate")
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
相信大家在调用其他远程服务的时候,都需要使用HTTP相关的客户端,比如ApacheHttpClient,Okhttp2/3,当然还spring提供的RestTemplate。
通过上面的代码定义,添加@LoadBalanced注解,当我们使用RestTemplate调用远程服务的时候,就会通过拦截器帮助我们实现服务负载均衡的功能。
对于负载均衡我们最想知道的是,它是如何帮助我们处理的?怎么判断哪个服务是可用?怎么判断哪个服务负载比较高?
上面我们解释了,springboot中仅仅引入了一个jar包,添加了一个注解,它就帮我们实现了负载均衡的功能。
大家都知道springboot要做的事情就是减少spring的xml配置,添加了很多自动装配的配置信息,同样的我们先看下ribbon相关的自动装配的配置类。
这个配置中着重看一下负载均衡器和重试Handler的默认实现
//这里创建默认的负载均衡器,是ZoneAwareLoadBalancer,在实际的远程调用中,通过它来实现负载均衡,找到合适的server
@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);
}
//ribbon的重试是通过RetryHandler实现的
@Bean
@ConditionalOnMissingBean
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
继续跟踪看一下ZoneAwareLoadBalancer的构造过程
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);
}
我们主要关注一下,在服务启动的时候,它是如何赋值服务列表的
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
//省略....
}
updateAllServerList(servers);
}
从serverListImpl的实现来看,主要包含3种方式获取服务列表
public void setServersList(List lsrv) {
super.setServersList(lsrv);
List<T> serverList = (List<T>) lsrv;
Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>();
for (Server server : serverList) {
// make sure ServerStats is created to avoid creating them on hot
// path
getLoadBalancerStats().getSingleServerStat(server);
String zone = server.getZone();
if (zone != null) {
zone = zone.toLowerCase();
List<Server> servers = serversInZones.get(zone);
if (servers == null) {
servers = new ArrayList<Server>();
serversInZones.put(zone, servers);
}
servers.add(server);
}
}
setServerListForZones(serversInZones);
}
因为默认实现的是ZoneAwareLoadBalancer,这里保存的时候也是根据Zone保存对应的server映射关系。
那么这个server map会保存到哪里呢?
LoadBalancerStats,一个用于处理负载均衡策略的存储类,这个要记住,在调用远程服务的时候,就是使用这个实例对象的upServerListZoneMap成员变量,因为它存储了我们所有的服务列表。
//RibbonAutoConfiguration加载前需要加载的Configuration
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
//配置是否懒加载
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
//从名字上我们可以看到,它是一个client的工厂,很多的LoadBalance的client都是要通过这个工厂获取
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
//定义LoadBalancerClient客户端,默认实现的Ribbon的
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
//LoadBalance重试工厂
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
final SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryFactory(clientFactory);
}
看一下它的主要代码
//所有添加了@Balanced注解的RestTemplate
@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) {
//设置RestTemplateCustomizer, 这个对象里面封装了LoadBalancerInterceptor,
//当使用RestTemplate的时候,就会先走拦截器中的方法
customizer.customize(restTemplate);
}
}
});
}
@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);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
在拦截器里面就实现了负载均衡的业务逻辑
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
//这里注入的就是RibbonLoadBalancerClient
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory 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 {
.//对应服务的url
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));
}
}
对应的execute的方法就是RibbonLoadBalancerClient的方法实现。
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
//获取负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
//从可用的服务列表中选择一个服务调用
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
//执行调用
return execute(serviceId, ribbonServer, request);
}
我们的重点就是看它是根据什么策略实现负载均衡的?
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");
}
这里呢,我们已经知道它用的是默认的ZoneAwareLoadBalancer负载均衡器。
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);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
//所有的已注册的区域服务列表
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
//省略....
//可用的区域服务列表
//如果当前服务列表只有一个,就返回这一个
//如果包含多个实例,首选判断服务的实例数,如果为0,剔除掉
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
//随机选择区域
//实际的zone是从可用的zones里面获取的
//如果只有1个可用的,就返回这一个
//如果包含多个,随机选择,这里使用的是Random
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
if (zone != null) {
//选择负载均衡器
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
//选择服务,调用的是ZoneAwareLoadBalancer的继承类BaseLoadBalancer
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
//省略.....
}
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;
}
}
}
public interface IRule{
/*
从所有的服务列表或者可用的服务列表选择还存活的服务
*/
//根据指定的key选择服务
public Server choose(Object key);
//设置或者获取负载均衡器
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
each client ill get a random list of servers which avoids the problem that one server with the lowest concurrent requests is chosen by a large number of clients and immediately gets overwhelmed
选择最低并发的服务
Supports preferentially calling the ribbon load balancing rules of the same cluster instance.
同集群优先调用
如果我们想定义自己的选择策略,只需要实现IRule接口,在Configuration中配置我们自己实现的规则。
上面这一部分内容,主要是通过RestTemplate进行远程服务调用的时候,调用的链路过程,大家注意一下。
Feign的基本原理,有一篇博文已经解释过了,大家可前去查看。Feign基本原理
在Feign的MethodHandler中,选择不同的Client实现调用流程,与Ribbon的整合实际调用的就是LoadBalancerFeignClient类。
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//创建Ribbon Request
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
//通过负载均衡调用远程服务
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
//省略....
}
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
//Function函数,提供一个Server参数,这里的server就是负载均衡后的可server
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//执行调用
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
}
//省略...
}
着重看下command.submit()方法的内部逻辑,返回的是一个Observable实例。
这个代码是真的长,看的头疼,作者写的时候也不拆分一下,哎。
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
if (listenerInvoker != null) {
try {
listenerInvoker.onExecutionStart();
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
//selectServer()根据ribbon定义的IRule规则,选择一个可用的服务节点
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(final Server server) {
final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();
return operation.call(server).doOnEach(new Observer<T>() {
//执行完的回调
//省略....
private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) {
tracer.stop();
loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);
}
});
}
});
//重试重试处理机制
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
if (maxRetrysNext > 0 && server == null)
o = o.retry(retryPolicy(maxRetrysNext, false));
//省略....
到这里基本上就是ribbon的负载均衡策略,它主要是客户端层面的负载均衡,服务端的话,比如说Nginx,有些源码写的不是很详细,想具体了解的朋友,建议自己去看一遍源码,加深一下印象,ribbon里面确实有很多的代码嵌套太深。
这里还有一个重试机制没写,篇幅太长了,太多了,看着不舒服,等有时间再搞一波。