Ribbon
Ribbon是Netflix 公司开源的一个客户端的负载均衡组件,在Spring Cloud的微服务生态中, Ribbon主要有两种使用方式, 一种是和RestTemplate相结合,另一种是和Feign相结合, 这也是Spring Cloud微服务间最主要的调用方式。其中Feign底层已经默认集成了Ribbon。
Netflix为Ribbon开发了很多子模块,不过目前可用于生产环境下的子模块主要有以下几个
- ribbon-core:Ribbon 的核心API 。
- ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载均衡器API 。
- ribbon-eureka :ribbon结合Eureka客户端的API ,为负载均衡器提供动态服务注册列表信息。
Ribbon配置RestTemplate的简单使用,及自定义负载策略
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate () {
return new RestTemplate();
}
// 修改负载策略,同时配置中也需要修改
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
# 修改Ribbion的负载均衡策略
service-name: # 服务名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Ribbon原理简述
1)服务列表获取:Ribbon负载的核心接口是LoadBalancerClient, LoadBalancerClient在初始化时会通过Eureka Client向Eureka服务端获取所有服务实例的注册信息,并且每10秒向EurekaClient 发送“ ping ”,来判断服务的可用性。如果服务的可用性发生了改变或者服务数量和之前的不一致,则更新或者重新拉取。
相关源码如下:
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnections.PrimeConnectionListener, IClientConfigAware {
//...
private final static IRule DEFAULT_RULE = new RoundRobinRule();
protected IRule rule = DEFAULT_RULE;
protected int pingIntervalSeconds = 10;
public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats,IPing ping, IPingStrategy pingStrategy) {
//...
setupPingTask();
//...
}
void setupPingTask() {
//../
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
}
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,ServerListUpdater serverListUpdater) {
//...
restOfInit(clientConfig);
}
void restOfInit(IClientConfig clientConfig) {
//...
updateListOfServers();
//...
}
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
//...
}
}
}
public class DiscoveryEnabledNIWSServerList extends bstractServerList<DiscoveryEnabledServer>{
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
return obtainServersViaDiscovery();
}
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
//...
EurekaClient eurekaClient = eurekaClientProvider.get();
//...
}
2)负载算法:LoadBalancerClient的实现类为RibbonLoadBalancerClient,RibbonLoadBalancerClient中的choose(..)会根据实例名,提取出对应的服务实例列表,然后交给ILoadBalancer去选择服务实例,集群环境下ILoadBalancer的实现类为ZoneAwareLoadBalancer,该类采用ZoneAvoidanceRule负载算法,即片区域加轮询的负载算法选择实例。 Ribbon默认提供了以下负载算法(即IRule的实现类):
- BestAvailableRule : 选择最小请求数。
- ClientConfigEnabledRoundRobinRule:轮询。
- RandornRule : 随机选择一个server 。
- RoundRobinRule : 轮询选择server ,又分为换响应时间权重、和
- RetryRule : 根据轮询的方式重试。
- WeightedResponseTimeRule: 根据响应时间去分配一个weight , weight 越低,被选择的可能性就越低。
- ZoneAvoidanceRule :根据server的zone区域和可用性来轮询选择。集群环境下默认为这种轮询方式,也就是按片区来轮询。
负载相关源码如下:
public class RibbonLoadBalancerClient implements LoadBalancerClient {
//...
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
//...
}
//...
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
}
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.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);
}
}
整体来说,Ribbon底层是通过Eureka Client获取服务实例列表并本地缓存,然后通过心跳检测服务可用性,当访问服务时,根据一定的负载算法获取服务实例。
为什么加上了@LoadBalance之就能使调用有负载均衡效果?
原因在@LoadBalance注解的处理器LoadBalancerAutoConfiguration 当中,LoadBalancerAutoConfiguration 中维护了一个被@LoadBalanced 修饰的RestTemplate 对象的List。在初始化的过程中,通过调用customizer.customize(restTemplate)方
法来给RestTemplate增加拦截器LoadBalancerlnterceptor ,这样调用restTemplate的方法时就会先进入到LoadBalancerinterceptor中,在LoadBalancerlnterceptor 中实现了负载均衡的方法。源码如下:
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();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = 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) {
customizer.customize(restTemplate); //配置拦截器
}
}
});
}
}