Ribbon是一个客户端负载均衡器,它赋予了应用一些支配HTTP与TCP行为的能力,这里的负载均衡是客户端的负载均衡,也有人称为后端负载均衡是进程内负载均衡的一种。Ribbon是SpringCloud生态里的不可缺少的组件,有了它,是个服务的横向扩展更加方便了。此外想Feign和Zuul默认是集成了Ribbon。
Ribbon是Neflix开源的一个组件,目前Ribbon早已进入维护状态,但是就目前的情况来看,Spring Cloud Netflix的一些组件还是可以使用。
Spring Cloud Loadbalancer是Spring Cloud社区开源的组件,目的是替代进入维护状态的Ribbon,但是Loadbalancer还是有很长的一段路要走。
代买很简单
官网下载例子
https://spring.io/projects/spring-cloud-alibaba#samples
官网完整的demo
从上一节可以看到,开启负载均衡只需通过@LoadBalanced注解即可。负载均衡中又很多的负载均衡策略,如轮询(Round Robin)、权重(Weight)、ip_hash等。这些丰富的策略让我们在构建应用的时候,有很多选择的余地,可以根据实际的业务场景选择最合适的策略。
在Ribbon中一共提供了7中负载均衡策略:
1、RandomRule 随机策略 随机选择Server
2、RoundRobinRule 轮询策略 按顺序选择Server 默认
3、RetryRule 重试策略 在一个配置时间段内当选择Server不成功,则一直尝试选择一个可用的Server
4、BestAvailableRule 最低并发策略 逐个考察Server,如果Server断路器打开,则忽略,再选择其中并发连接最低的Server
5、AvailabilityFilteringRule 可用过滤策略 过滤一直连接失败并标记为circuit tripped的Server,过滤掉那些高并发连接的Server(active connections超过配置的阈值)
6、ResponseTimeWeightedRule 响应时间加权策略 已经被弃用,作用同WeightedResponseTimeRule
WeightedResponseTimeRule 响应时间加权策略 根据Server的响应时间分配权重,响应时间越长,权重越低,被选中的概率就越低。响应时间越短,权重越高,被选择到的概率越高
7、ZoneAvoidanceRule 区域权衡策略 综合判断Server所在区域的性能和Server的可用性轮询选择Server,并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有Server
Ribbon自定义配置负载均衡策略全局配置
使用Ribbon时配置全局的负载均衡策略,需要加一个配置类。改配置类需要被@ComponentScan扫描到才能全局生效。
@Configuration
public class GlobalRuleConfig {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
启动类的配置
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "ribbon-service-b", configuration = AnnoRuleConfig.class)
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
通过@RibbonClient指定某个服务的负载均衡策略,其他没有被指定的,就是用默认的负载均衡策略。该注解可以把其他的配置类作为另外一个IOC容器导入到应用中,相当于加载了两个完全不相干的Spring的beans配置文件,此时应用中会有两个IOC容器。
@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class)
也可以使用一下的方式,指定多个服务的负载均衡策略
@RibbonClients(value = {
@RibbonClient(name = “RIBBON-SERVICE-B”, configuration = AnnoRuleConfig.class),
@RibbonClient(name = “RIBBON-SERVICE-C”, configuration = AnnoRuleConfig.class)
})
基于配置文件
下面对服务ribbon-service-b的负载均衡策略使用
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Ribbon超时与重试
使用HTTP发起请求难免会发生问题,在F版开始Ribbon的重试机制默认是开启的,需要添加对超时时间与重试策略的配置。列入下面ribbon-service-b服务的配置
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
也可以全局配置
ribbon: ConnectTimeout: 3000 ReadTimeout: 60000 MaxAutoRetries: 3 #对第一次请求的服务的重试次数 MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务) OkToRetryOnAllOperations: true
一般Ribbon都是搭配OpenFeign这类Http客户端或者其他RPC使用。因为这样去调用远程服务会更加优雅和方便。而OpenFeign默认是继承了Ribbon,对于Ribbon的超时时间配置也是很简单。
对于网络抖动这些可以使用spring-retry,spring-retry是spring提供的一个基于spring的重试框架,非常好用。
Ribbon饥饿加载
Ribbon在进行客户端负载均衡的时候,并不是启动时就加载上下文,而是在实际请求的时候采取创建。因为要加载上下文的原因,在第一次调用时可能会很慢,甚至导致超时。所以我们可以指定Ribbon客户端开启立即加载(饥饿加载),在应用启动的时候就立即加载所有配置项的应用程序上下文。
ribbon:
eager-load:
clients: ribbon-service-b, ribbon-service-order
enabled: true
自定义Ribbon客户端
在Ribbon的1.2.0版本之后,就可以使用配置文件来定制Ribbon客户端,其实实质就是使用配置文件来指定一些默认加载类,从而更改Ribbon客户端的行为,并且使用这种方式优先级最高,优先级高于使用注解@RibbonClient指定的配置和源码中加载的相关的Bean。看下表:
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
NiWSServerListClassName: com.netflix.loadbalancer.ConfigurationbasedServerList
Ribbon进阶
核心工作原理
Ribbon的核心接口:
Ribbon完全是基于这些接口上建立起来的,是Ribbon的核心。了解这些核心的类的功能对于理解Ribbon的原理和扩展很有利。
在之前的例子中,使用Ribbon负载均衡都是通过在RestTemplate的Bean上添加注解@LoadBalanced,使得RestTemplate拥有了负载均衡的能力。
LoadBalanced源码:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
在注释中可以看到:该注解标记在RestTemplate或者其他的WebClient的Bean上,来使用LoadBalancerClient。
LoadBalancerClient:该接口扩展自ServiceInstanceChooser
public interface LoadBalancerClient extends ServiceInstanceChooser {
T execute(String serviceId, LoadBalancerRequest request) throws IOException;
T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
ServiceInstanceChooser:
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
ServiceInstance choose(String serviceId):根据ServiceId,结合负载均衡器选择一个服务实例
T execute(String serviceId, LoadBalancerRequest request):使用来自LoadBalancer的ServiceInstance为指定的服务执行请求
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request):使用来自LoadBalancer的ServiceInstance为指定的服务执行请求,是上一个方法的重载,在实现类中可以看到它们的关系,就是前一个方法的细节实现
URI reconstructURI(ServiceInstance instance, URI original):使用注解ip和port构建特定的URL以供Ribbon内部使用。Ribbon使用具有逻辑服务名称的URL作为host,例如:http://service-b/order/add。
从这些方法的功能可以知道这两个接口的重要性了。这两个接口的同一包下有一个类LoadBalancerAutoConfiguration。LoadBalancerAutoConfiguration在org.springframework.cloud.client.loadbalancer包下,在spring-cloud-commons里面。该自动配置类正式Ribbon的核心配置类。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List restTemplates = Collections.emptyList();
@Autowired(required = false)
private List transformers = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@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 list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
这就清晰起来了,他的配置加载时机是当前工程环境必须有RestTemplate的实例和必须初始化了LoadBalancerClient的实现类。
@ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class)
LoadBalancerRequestFactory:用于创建LoadBalancerRequest给LoadBalancerInterceptor使用。
LoadBalancerInterceptorConfig:维护了LoadBalancerInterceptor与RestTemplateCustomizer的实例。
LoadBalancerInterceptor:拦截每一次的HTTP请求,将请求绑定金Ribbon的负载均衡的生命周期。
RestTemplateCustomizer:为每一个Restemplate绑定LoadBalancerInterceptor拦截器。
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));
}
}
在源码可以看到它是通过ClientHttpRequestInterceptor实现每次对HTTP请求的拦截,ClientHttpRequestInterceptor类是Spring中维护的请求拦截器,实现它的intercept方法就可以使得请求进入方法内,从而Ribbon就可以做一些自己的处理了。
在使用RestTemplate请求服务时使用的URI:http://serviceName/path/to/se…
LoadBalancerInterceptor中的intercept方法,最终调用的是RibbonLoadBalancerClient的execute方法:
publicT execute(String serviceId, LoadBalancerRequest request, Object hint) throws IOException { // 拿到负载均衡器的实现 ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); // 拿到具体的Server Server server = this.getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getmetadata(server)); return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request); } } protected Server getServer(ILoadBalancer loadBalancer, Object hint) { return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default"); }
对于chooseServer是接口ILoadBalancer的方法,这里就先看一下其中的一个实现baseLoadBalancer
public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
return this.rule.choose(key);
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
最后是通过:rule.choose(key)拿到Server,而rule就是IRule。
在RibbonClientConfiguration中初始化了上面表格提到几个核心类
初始化ribbonRule: ZoneAvoidanceRule
初始化ribbonPing:DummyPing
初始化ribbonServerList:ConfigurationbasedServerList
初始化ServerListUpdater:new PollingServerListUpdater(config)
初始化ILoadBalancer:ZoneAwareLoadBalancer
初始化ribbonServerListFilter:ZonePreferenceServerListFilter
初始化ribbonLoadBalancerContext:RibbonLoadBalancerContext
初始化serverIntrospector:DefaultServerIntrospector
关于BlockingLoadBalancerClient:
Spring Cloud Hoxton.RELEASE 版本发布之后,新增了一个新的负载均衡器实现BlockingLoadBalancerClient。它是第一个包含阻塞式和非阻塞式负载均衡器客户端实现的版本,作为已进入维护模式的Netflix Ribbon的替代方案。
如果想在 RestTemplate使用新的 BlockingLoadBalancerClient, 需要增加 spring-cloud-loadbalancer 的依赖,否则默认使用RibbonLoadBalancerClient。



