ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

二刷Ribbon源码

2021-05-26 13:57:05  阅读:234  来源: 互联网

标签:负载 服务 配置 二刷 源码 组件 class Ribbon


说的多,也要做的多,这样才踏实多。

文章目录

1.基础知识

首先要明白一个基础知识点:

  • Netfix公司开源了一系列微服务组件。项目地址https://github.com/Netflix。里面有eureka,Ribbon等等

  • SpringCloud 集成了Netfix开源的套件的几个套件,也组成了一个项目,项目地址https://github.com/spring-cloud/spring-cloud-netflix。我们熟悉 eureka,ribbon就在这里。

  • Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法

  • Spring Cloud Ribbon模块是封装了Netflix公司开发的这个Ribbon项目。

也就是说

  • Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具。
  • 核心功能是在Netflix Ribbon项目中

本文基于Brixton版本,springboot环境下直接看的源码

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.SR4</version>
        <type>pom</type>
        <scope>import</scope>
</dependency>ml

2.Netflix Ribbon主要组件

先从Netflix Ribbon项目的角度去看

2.1 ServerList 服务列表

存储服务列表,也就是被负载均衡的对象

主要实现

  • ConfigurationBasedServerList: 静态服务列表,也就是我们通常所说的写死,项目启动前配置好。
  • DiscoveryEnabledNIWSServerList:动态服务列表,从Eureka Client中获取服务列表。

2.2 ServerListFilter服务筛选

筛选特定条件的服务列表,对ServerList服务器列表进行二次过滤

主要实现

  • AbstractServerListFilter
    负责从负载均衡器中过滤出当前可用的服务器列表的类
  • ZoneAffinityServerListFilter: 过滤掉所有的不和客户端在相同zone的服务(注意:如果和客户端相同的zone不存在,才不过滤不同zone有服务)
  • ZonePreferenceServerListFilter
  • ServerListSubsetFilter

2.3 ServerListUpdater 服务列表的更新

定义关于更新动作,根据策略执行更新操作,更新ServerList列表,一般用于动态ServerList

主要实现

  • PollingServerListUpdater
    默认的实现策略。此对象会启动一个定时线程池,定时执行更新策略

  • EurekaNotificationServerListUpdater
    当收到缓存刷新的通知,会更新服务列表。

2.4 IPing 服务存活检查

检查一个服务是否存活,

主要实现

  • DummyPing : 总是认为存活
  • NoOpPing:什么都不做
  • PingConstant:通过配置参数设置指定服务器存活状态
  • NIWSDiscoveryPing: 如果服务实例在本地的Eureka缓存中存在,则返回true(默认配置)。ribbon-eureka包中提供的类,结合eureka使用时,如果Discovery Client在线,则认为心跳检测通过
  • PingUrl:用HttpClient调用服务的一个URL,如果调用成功,则认为本次心跳成功,表示此服务活着

IPing检查的是当个服务的存活性。

IPingStrategy:服务列表检查策略接口,
唯一实现:SerialPingStrategy:采用线性遍历的策略方式,使用IPing检查每个服务的存活性。

2.5 IRule 根据算法选择一个服务

根据算法中从服务列表中选取一个要访问的服务

主要实现

  • RandomRule:随机算法
  • RoundRobinRule : 轮训策略(默认策略
  • RetryRule: 轮询 + 重试,在RoundRobinRule基础上,增加重试功能
  • WeightedResponseTimeRule: (非常好) 刚启动时,统计数据不足,先使用RoundRobinRule策略。有个DynamicServerWeightTask定时任务,默认每隔30秒会计算一次各个服务实例响应时间,等统计信息足够,切换到WeightedResponseTimeRule.
  • BestAvailableRule: 先过滤到断路器处于打开的服务,然后选择并发请求最小的服务实例来使用。刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到BestAvailableRule。
  • PredicateBasedRule: 过滤+轮训
  • AvailabilityFilteringRule: (1)由于多次访问故障而处于断路器跳闸状态;(2)并发的连接数量超过阈值. (3)对剩余的服务列表,进行轮询

小结:我们可以看出上述组件是对 服务列表(待负载对象)的定义,与操作。但是要想让他们协同起来干活。这就需要ILoadBalancer

2.6 ILoadBalancer:统筹者,总管

负载均衡器,负责负载均衡调度的管理,总管级别。调度其他组件,完成从服务列表里获取一个服务的目标

对外提供一个方法,选择出一个Server来。

public Server chooseServer(Object key);

抽象类:AbstractLoadBalancer 主要定义了服务的分组

主要实现

1.BaseLoadBalancer: 基本负载均衡器实现.维护
(1)维护一个所有服务列表+当前存活服务列表
(2)默认使用轮训选择一个服务
(3)定义一个定时器,根据SerialPingStrategy策略定时检活

2.DynamicServerListLoadBalancer动态升级版
(1)DomainExtractingServerList 动态从EurekaClient获取UP状态服务列表
(2)ZoneAffinityServerListFilter对列表进行过滤
(3)PollingServerListUpdater定时对服务进行更新
3.ZoneAwareLoadBalancer: 在DynamicServerListLoadBalancer基础上增加了zone策略。
(1)此类使用ZoneStats存储每个Zone的状态和平均请求情况。区域指标作为选择服务的影响因素

2.7 配置组件组合方式

组件有了,到底采用哪种组合方式呢?Ribbon为使用者提供了可配置化。

通过不同的组件组合,配置不同的负载均衡效果。

配置的方式有哪些呢:

  • IClientConfig 接口:默认实现DefaultClientConfigImpl。你不配置任何属性,则默认会使用这里面定义的组件。当然这里还有其他各种属性的配置
默认配置的组件有:

  • 文件配置: 当我们更换默认的配置时,我们可以在配置文件里配置各种属性
    属性格式配置如下
<clientName>.<nameSpace>.<propertyName>=<value>
api-user.ribbon.listOfServers=localhost:8099

这样:Netfix-Ribbon的大概组件,以及工作原理也就一目了然了
项目地址https://github.com/Netflix/ribbon

3.SC-Ribbon主要组件(脱离Eureka)

SpringCloud-Ribbon集成了Netflix Ribbon,使其融入springcloud体系。

核心的功能都在Netflix Ribbon项目中了。SC-Ribbon封装Netflix Ribbon,主要做了一些集成工作。

为此,SpringCloud-Ribbon又增加了哪些组件,干了哪些事呢?

思考:既然核心功能在Netflix Ribbon项目中了。此项目干的事,无非就是从以下几方面入手

  1. 配置层面:符合Spring体系的配置习惯
  2. 使用层面: 如何使用Ribbon的负载均衡

!!!!!注意注意: 此处说的是脱离Eureka使用SpringCloud-Ribbon

下面我们来看为此做出的工作:

配置层面的工作

3.1 (配置)默认的配置RibbonClientConfiguration

此配置类也是作为一个默认配置类存在,也就是在你不配置任何东西是SpringCloud-Ribbon默认装载的核心组件的组合,
相比于Netflix Ribbon项目DefaultClientConfigImpl 的提供的默认组件为
在这里插入图片描述

3.2 (配置)自定义配置

本着可扩展原则: SpringCloudRibbon也要提供个性化配置功能。

3.2.1 注解式配置类

这里涉及两个注解:@RibbonClient@RibbonClients (注意有个s

  • 这两注解类功能是,被他们注解的类会通过某种机制RibbonClientConfiguration替换默认的配置
  • 这两个注解的区别是:一个覆盖的是单个服务的某个组件,一个针对多个服务

使用方式如下:我把user服务负载均衡的IPing 组件替换为我的MyPingUrl

Configuration
@RibbonClient(name = "user", configuration = UserConfiguration.class)
public class UserRibbonConfiguration {
}
@Configuration
protected static class UserConfiguration{
    @Bean
    public IPing ribbonPing() {
        return new MyPingUrl();
    }
}

注意 添加UserConfiguration中的配置。UserConfiguration必须使用@Configuration进行声明,而且不能放在可以被main application context的@ComponentScan或@SpringBootApplication扫描到的地方,防止该配置被所有的@RibbonClient共享

如果想把所有服务的负载均衡的的IPing 组件替换为我的MyPingUrl

@RibbonClient(defaultConfiguration = UserConfiguration.class)
public class UserRibbonConfiguration {
}

工作原理

这两个注解的工作原理是:通过引入RibbonClientConfigurationRegistrar注册器,将配置类注册为一个RibbonClientSpecification

@Configuration
@Import(RibbonClientConfigurationRegistrar.class)//这里
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
}
//注册器
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
		private void registerClientConfiguration(BeanDefinitionRegistry registry,
			Object name, Object configuration) {
			//注册为一个RibbonClientSpecification
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}
}

RibbonClientSpecification: 直译为客户端规范,对,你的个性化配置被解析为一个客户端规范

举例说明为:

  • A服务,B服务使用同一个个性化配置,A,B服务就有一个RibbonClientSpecification规范
  • C服务使用一个个性化配置,C服务就有一个RibbonClientSpecification规范

这个规范是如何生效的呢?见下文

3.2.1 配置文件里配置:

另一种就是直接在spring体系内的配置文件中配置
application.yml

api-user:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList

在这些属性中定义的类优先于使用@RibbonClient(configuration=MyRibbonConfig.class)定义的bean和由Spring Cloud Netflix提供的默认值。

说了配置,在讲讲使用层面的工作

使用层面的工作
负载均衡功能的使用其实就在ILoadBalancer这个统筹组件上。所谓使用,其实就是就在这个ILoadBalancer上。

3.3 (使用) SpringClientFactory

SpringClientFactory使用工厂模式对外提供:根据serviceId获取IClient, ILoadBalance, ILoadBalanceContext等对象的服务。

这里我们只关注ILoadBalance的获取。

SpringClientFactory 为每个服务创建一个独立的上下文,并在其中加载对应的配置及Ribbon核心接口的实现类。创建逻辑在其父类NamedContextFactory

public abstract class NamedContextFactory{
	private Map<String, C> configurations = new ConcurrentHashMap<>();
	private Class<?> defaultConfigType;
	//创建上下文
	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object> singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}
		context.refresh();
		return context;
	}
}

这里有两个点:

  1. 根据传入服务serverId 创建一个AnnotationConfigApplicationContext,装载属于他自己的负载均衡套件。不同的服务有不同的上下文,里面存储的是属于他的负载均衡套件组合

  2. 服务的负载均衡套件组合方式。是上文提到的两种配置方式决定的。也就是configurationsdefaultConfigType两个属性所代表的规范。

	private Map<String, C> configurations = new ConcurrentHashMap<>();
	private Class<?> defaultConfigType;//代表默认的组件组合
  • configurations :代表的是个性化配置的套件规范。上文提到的RibbonClientSpecification
  • defaultConfigType :代表的就是sc-ribbon的默认配置类RibbonClientConfiguration

当我们调用SpringClientFactory.getLoadBalancer方法获取一个负载均衡器实例时, 本质就是在这个服务对应的上下文中取出属于他的ILoadBalancer实现。

public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return context.getBean(type);返回bean
		}
		return null;
	}

获取到ILoadBalancer实现,我们就可以调用其chooseServer获得一个服务实例,进行请求了。

ILoadBalancer userLoadBalancer = clientFactory.getLoadBalancer("user");
Server server = userLoadBalancer.chooseServer("default")

3.4 (使用) LoadBalancerClient

LoadBalancerClient封装了SpringClientFactory, 作为负载均衡器客户端,被广泛使用。这个类才是SC-Ribbon的真正负载均衡客户端的入口。

提供了三个方法:

  • 选择服务实例方法;
  • 执行请求方法
  • 重构uri方法。

每个方法都是与负载均衡有关

	ServiceInstance choose(String serviceId);
	
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	URI reconstructURI(ServiceInstance instance, URI original);

LoadBalancerClient作为负载均衡客户端。他的能力完全是建立在以上所有组件的共同努力下实现的。

public class MyClass {
    @Autowired
    private LoadBalancerClient loadBalancer;

    public void doStuff() {
        ServiceInstance instance = loadBalancer.choose("stores");
        URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
        // ... do something with the URI
    }
}

3.SC-Ribbon与eureka一起使用又发生了什么

1.配置层面:

SpringCloudRibbonEureka一起使用时,会使用@RibbonClients为所有的服务的负载均衡替换一些默认组件(更换组合套件)

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnProperty(value = "ribbon.eureka.enabled", matchIfMissing = true)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}
----------------------------
@Configuration
@CommonsLog
public class EurekaRibbonClientConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public IPing ribbonPing(IClientConfig config) {
		NIWSDiscoveryPing ping = new NIWSDiscoveryPing();//
		ping.initWithNiwsConfig(config);
		return ping;
	}

	@Bean
	@ConditionalOnMissingBean
	public ServerList<?> ribbonServerList(IClientConfig config) {
		DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
				config);
		DomainExtractingServerList serverList = new DomainExtractingServerList(
				discoveryServerList, config, this.approximateZoneFromHostname);
		return serverList;
	}
}	
  • IPing: 使用NIWSDiscoveryPing这个实现
  • ServerList:使用了DomainExtractingServerListDomainExtractingServerList代理了DiscoveryEnabledNIWSServerListDiscoveryEnabledNIWSServerList存储的服务列表都从EurekaClient本地缓存里取到的。(取操作是PollingServerListUpdater,定时执行的)
public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
	EurekaClient eurekaClient = eurekaClientProvider.get();
	List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
	....
	
}
}

2.使用层面:
没啥变化,就是使用LoadBalancerClient来使用负载均衡获取一个Server。

4.Resttemplate 如何具有负载均衡能力

Resttemplate 具有负载均衡能力,其本质还是通过使用LoadBalancerClient来实现。

如何实现呢?

这就涉及到两个知识点:

  • Resttemplate相关体系:
  • @LoadBalanced 注解。

Resttemplate 是有拦截器的概念的 。也就是说在真正发起请求之前,会走一些拦截器。这就给负载均衡选择一个Server提供了机会。

@LoadBalanced 注解就是给其修饰的Resttemplate实例,注入LoadBalancerInterceptor拦截器。此拦截器就是通过LoadBalancerClient来实现了负载均衡

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	...
}

具体可阅读我的其他文章
发送http请求(1):发送http请求的几种方式
发送http请求(2):RestTemplate发送http请求
@LoadBalanced注解的RestTemplate拥有负载均衡的能力

5.总结

文本从介绍负载均衡的几个组件开始,由底层讲到了Resttemplate原理。打开了Resttemplate发起负载均衡请求的黑盒。顺着这条线,只要仔细研读各个部分的知识点。

你会感叹开源框架的大厦的建立,为我们省去了多少开发工作。

我们不应该是停留在使用的阶段,还应该深入去了解底层框架的原理,这样你才会在大厦之上更进一层。

参考链接:
https://www.jianshu.com/p/73c117fbfe10
https://blog.csdn.net/hry2015/article/details/78357990


如果本文任何错误,请批评指教,不胜感激 !
如果文章哪些点不懂,可以联系我交流学习!

微信公众号:源码行动
享学源码,行动起来,来源码行动

标签:负载,服务,配置,二刷,源码,组件,class,Ribbon
来源: https://blog.51cto.com/u_8958931/2817488

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有