目录
一、Ribbon介绍
Ribbon的基本工作模式是:IPing+IRule;
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法;
Ribbon使用的是客户端负载均衡,在客户端负载均衡中,所有的 户端节点都有 份自己要访问的服务端地址列表,这些列表统统都是从服务注册中心获取的;
二、预备知识
1. 七种负载均衡策略
1.1、随机策略——RandomRule
1.2、轮询策略——RoundRobinRule(默认策略)
1.3、重试策略——RetryRule
在选定的负载均衡策略上添 重试机制
1.4、最低并发策略——BestAvailableRule
选择最小请求数的服务器
1.5、可用过滤策略——AvailabilityFilteringRule (性能仅次于最低并发策略)
过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,
并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)
1.6、响应时间加权策略——WeightedResponseTimeRule
每隔30秒计算一次服务器响应时间,以响应时间作为权重,响应时间越短的服务器被选中的概率越大
1.7、区域权衡策略——ZoneAvoidanceRule
根据服务器所属的服务区的整体运行状况来轮询选择
Ribbon的负载均衡策略使用建议:一般情况下,推荐使用最低并发策略,这个性能比默认的轮询策略高很多
2. IRule机制
IRule的类图:
IRule源码:
IRule接口中有两个类型值得注意:Server和ILoadBalancer
Server:封装了是注册进Eureka的微服务信息,也就代表注册进Eureka的微服务
ILoadBalancer:是一个接口,用来获取注册进Eureka的全部或部分或某个微服务信息;
IRule接口是通过ILoadBalancer来获取Server,进而实现负载均衡
3. IPing机制
IPing是一个主动探测服务节点存活的机制,通过判断服务节点的当前状态,设置节点的可用状态,只有当节点为可用时候才会作为负载均衡器的选取节点。
IPing模式:
1、DummyPing:默认返回true,即认为所有节点都可用,这也是单独使用Ribbon时的默认模式
2、NIWSDiscoveryPing:借助Eureka服务发现机制获取节点状态,选取节点状态是UP的服务
3、NoOpPing:返回true
4、PingConstant:返回设置的常量值
5、PingUrl:主动向服务节点发起一次http调用,对方有响应则认为节点是可用状态
代码配置:
#单个服务设置
[service-name]:
ribbon:
NFLoadBalancerPingClassName: com.netflix.loadbalancer.DummyPing
4. Ribbon配置方式
1.1、全局配置
方式1、新增Configuration类声明一个Bean
方式2、在Application中直接声明一个Bean
使用LoadBalanced开启客户端负载均衡的功能
1.2、指定服务配置
方式1、通过配置文件配置
#eureka-client是对应的服务的名称
eureka-client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
方式2、通过注解方式配置
注意事项:
1、针对一个服务配置的 负载均衡策略 的优化级比全局的配置更高;
2、Ribbon的自定义配置的加载顺序:
配置文件>java代码>默认配置
2、Ribbon的自定义配置的生效优先级:
java代码 > 配置文件
5. LoadBalancerClient负载均衡器
LoadBalancerClient负载均衡器是 Ribbon 的核心类之一,可以在 RestTemplate 发送网络请求时替代 RestTemplate 进行网络调用;
1、首先Ribbon的负载均衡的引入是从以下代码开始的:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
2、从@LoadBalanced注解开始,进入源码:
// 标记RestTemplate的注解以使用LoadBalancerClient对象.
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
这个注解是用来给RestTemplate做标记,以使用负载均衡客户端(LoadBalancerClient)来配置它。
所以,我们在生成的RestTemplate的bean上添加这么一个注解,这个bean就会配置LoadBalancerClient。
3、进入LoadBalancerClient源码:
LoadBalancerClient是一个接口,里面有三个方法:
-
继承父类的ServiceInstance choose(String serviceId)方法:
从负载均衡器中选择一个服务实例ServiceInstance类
-
execute方法:
使用从负载均衡器中选择的服务实例来执行请求内容
-
URI reconstructURI(ServiceInstance instance, URI original)方法:
把请求的URI进行转换,返回host+port,通过host+port的形式去请求服务
6. ILoadBalancer
ILoadBalancer是 Ribbon 的核心类之一,是定义负载均衡操作过程的接口,与ILoadBalancer相关重要的类是:IClientConfig、IRule、IPing、ServerList(服务列表获取)和ServerListFilter(服务列表过滤)
IClientConfig :client的配置类,具体指的DefaultClientConfigImpl
IRule :负载均衡的策略类,默认的轮询策略是:RoundRobinRule
IPing :服务可用性检查,默认为:DummyPing,
ServerList :服务列表获取
ServerListFilter :服务列表过滤
三、源码解析
1、进入入口 @RibbonClient 注解:
@Configuration
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
// value和name是等同的 用于设置客户端的实例名称
String value() default "";
String name() default "";
// configuration用于指定配置类
Class<?>[] configuration() default {};
}
2、重点来查看一下RibbonClientConfigurationRegistrar中实现的源码:
重点关注registerBeanDefinitions()方法:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
// 获取@RibbonClient的参数,获取clientName后进行configuraction注册
Map<String, Object> attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true);
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
// 注册
registerClientConfiguration(registry, getClientName(client),client.get("configuration"));
}
}
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 注册
registerClientConfiguration(registry, name,attrs.get("defaultConfiguration"));
}
Map<String, Object> client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true);
// 获取 name或value值
String name = getClientName(client);
if (name != null) {
// 注册
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
接着关注registerClientConfiguration()注册方法:
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + ".RibbonClientSpecification",builder.getBeanDefinition());
}
RibbonClientSpecification 实现了 NamedContextFactory.Specification,提供给SpringClientFactory使用的,他用于初始化ribbon的相关实例.
3、关注RibbonAutoConfiguration 自动配置类
创建好SpringClientFactory后,可以给后面的LoadBalancerClient的Bean使用
根据下面的注解发现,在加载RibbonAutoConfiguration类之前要先加载LoadBalancerAutoConfiguration类和AsyncLoadBalancerAutoConfiguration类
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,AsyncLoadBalancerAutoConfiguration.class })
进入LoadBalancerAutoConfiguration类发现:
- @ConditionalOnClass(RestTemplate.class):RestTemplate必须存在于当前工程的环境中
- @ConditionalOnBean(LoadBalancerClient.class):在Spring的Bean工程中必须有LoadBalancerClient的实现bean
LoadBalancerAutoConfiguration类中关注@Bean的代码
// 创建Ribbon自定义拦截器LoadBalancerInterceptor
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
/**
* 添加拦截器具体方法
* 首先获取当前拦截器集合(List)
* 然后将loadBalancerInterceptor添加到当前集合中
* 最后将新的集合放回到restTemplate中
**/
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
- 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡;
- 创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadbalancerInterceptor;
- 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器;
再进入LoadBalancerInterceptor类 分析为什么在RestTemplate中发出请求时如何被LoadBalancerInterceptor拦截的逻辑
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
@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);
/**
* 拦截请求,并调用loadBalancer.execute()方法在该方法内部完成server的选取
* 向选取的server发起请求,并获得返回结果
**/
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
由于在自动配置类中,对restTemplate实例添加了LoadBalancerInterceptor拦截器,所以,当用restTemplate发送http请求时,就会执行这个拦截器的intercept方法;
在intercept方法中,会根据request.getURI(),获取请求的URI,再获取host,我们在发送http请求的时候,是用的服务名作为host,服务名后再调用具体LoadBalancerClient实例的execute方法,发送请求。
4、之后将进入LoadBalancerClient负载均衡器,即客户端的负载均衡实现类
5、LoadBalancerClient如预备知识中所讲,先从它的入口注解@LoadBalanced开始,进入LoadBalancerClient
6、 LoadBalancerClient的实现类为RibbonLoadBalancerClient,这个类是非常重要的一个类,最终的负载均衡的请求处理,由它来执行。 请求的负载均衡在execute中实现:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {
// 每次发送请求都会获取一个ILoadBalancer ,会涉及负载均衡(IRULS),服务器列表集群(ServerList)
// 并检验服务是否存活(IPing)等细节实现
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 根据上面的规则获取一个可用的服务实例
// getServer()直接调用了 ILoadBalancer 的chooseServer方法来使用负载均衡策略,从已知的服务列表中选出一个服务器实例
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);
}
ILoadBalancer 的实现是由它的实现类 ZoneAwareLoadBalancer 类(默认采用的负载均衡策略,是一个基于区域感知的轮询策略),它的构造方法:有上面提到的ILoadBalancer的重要的类
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
总结
Ribbon流程图: