SpringCloud源码解析之Ribbon


一、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流程图:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值