说的多,也要做的多,这样才踏实多。
首先要明白一个基础知识点:
Netfix公司开源了一系列微服务组件。项目地址 github.com/Netflix 。里面有 eureka
, Ribbon
等等
SpringCloud 集成了Netfix开源的套件的几个套件,也组成了一个项目,项目地址 github.com/spring-clou… 。我们熟悉 eureka,ribbon就在这里。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法
Spring Cloud 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 复制代码
先从 Netflix Ribbon
项目的角度去看
存储服务列表,也就是被负载均衡的对象
主要实现
Eureka Client
中获取服务列表。 筛选特定条件的服务列表,对ServerList服务器列表进行二次过滤
主要实现
:
定义关于更新动作,根据策略执行更新操作,更新ServerList列表,一般用于动态ServerList
主要实现
:
PollingServerListUpdater
默认的实现策略。此对象会启动一个定时线程池,定时执行更新策略
EurekaNotificationServerListUpdater
当收到缓存刷新的通知,会更新服务列表。
检查一个服务是否存活,
主要实现
:
IPing检查的是当个服务的存活性。
IPingStrategy
:服务列表检查策略接口,
唯一实现: SerialPingStrategy
:采用线性遍历的策略方式,使用IPing检查每个服务的存活性。
根据算法中从服务列表中选取一个要访问的服务
主要实现
:
非常好
) 刚启动时,统计数据不足,先使用 RoundRobinRule
策略。有个 DynamicServerWeightTask
定时任务,默认每隔30秒会计算一次各个服务实例响应时间,等统计信息足够,切换到 WeightedResponseTimeRule
. 小结:我们可以看出上述组件是对 服务列表( 待负载对象
)的 定义
,与 操作
。但是要想让他们协同起来干活。这就需要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的状态和平均请求情况。区域指标作为选择服务的影响因素
组件有了,到底采用哪种组合方式呢?Ribbon为使用者提供了可配置化。
通过不同的组件组合,配置不同的负载均衡效果。
配置的方式有哪些呢:
DefaultClientConfigImpl
。你不配置任何属性,则默认会使用这里面定义的组件。当然这里还有其他各种属性的配置 默认配置的组件有: 复制代码
文件配置: 当我们更换默认的配置时,我们可以在配置文件里配置各种属性
属性格式配置如下
<clientName>.<nameSpace>.<propertyName>=<value> api-user.ribbon.listOfServers=localhost:8099 复制代码
这样:Netfix-Ribbon的大概组件,以及工作原理也就一目了然了
项目地址 github.com/Netflix/rib…
SpringCloud-Ribbon集成了 Netflix Ribbon
,使其融入springcloud体系。
核心的功能都在 Netflix Ribbon
项目中了。SC-Ribbon封装 Netflix Ribbon
,主要做了一些集成工作。
为此,SpringCloud-Ribbon又增加了哪些组件,干了哪些事呢?
思考
:既然核心功能在 Netflix Ribbon
项目中了。此项目干的事,无非就是从以下几方面入手
!!!!!注意注意: 此处说的是脱离Eureka使用SpringCloud-Ribbon
下面我们来看为此做出的工作:
此配置类也是作为一个默认配置类存在,也就是在你不配置任何东西是 SpringCloud-Ribbon
默认装载的核心组件的组合,
相比于 Netflix Ribbon
项目 DefaultClientConfigImpl
的提供的默认组件为
本着可扩展原则: SpringCloudRibbon也要提供个性化配置功能。
这里涉及两个注解: @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
: 直译为客户端规范,对,你的个性化配置被解析为一个客户端规范
举例说明为:
RibbonClientSpecification RibbonClientSpecification
这个规范是如何生效的呢?见下文
另一种就是直接在spring体系内的配置文件中配置
application.yml
api-user: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList 复制代码
在这些属性中定义的类优先于使用@RibbonClient(configuration=MyRibbonConfig.class)定义的bean和由Spring Cloud Netflix提供的默认值。
说了配置,在讲讲使用层面的工作
使用层面的工作:
负载均衡功能的使用其实就在 ILoadBalancer
这个统筹组件上。所谓使用,其实就是就在这个 ILoadBalancer
上。
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; } } 复制代码
这里有两个点:
根据传入服务 serverId
创建一个 AnnotationConfigApplicationContext
,装载属于他自己的负载均衡套件。不同的服务有不同的上下文,里面存储的是属于他的负载均衡套件组合
服务的负载均衡套件组合方式。是上文提到的两种配置方式决定的。也就是 configurations
与 defaultConfigType
两个属性所代表的规范。
private Map<String, C> configurations = new ConcurrentHashMap<>(); private Class<?> defaultConfigType;//代表默认的组件组合 复制代码
RibbonClientSpecification 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") 复制代码
LoadBalancerClient封装了SpringClientFactory, 作为负载均衡器客户端,被广泛使用。这个类才是SC-Ribbon的真正负载均衡客户端的入口。
提供了三个方法:
每个方法都是与负载均衡有关
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 } } 复制代码
当 SpringCloudRibbon
与 Eureka
一起使用时,会使用 @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; } } 复制代码
DomainExtractingServerList
。 DomainExtractingServerList
代理了 DiscoveryEnabledNIWSServerList
。 DiscoveryEnabledNIWSServerList
存储的服务列表都从 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。
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拥有负载均衡的能力 文本从介绍负载均衡的几个组件开始,由底层讲到了 Resttemplate
原理。打开了 Resttemplate
发起负载均衡请求的黑盒。顺着这条线,只要仔细研读各个部分的知识点。
你会感叹开源框架的大厦的建立,为我们省去了多少开发工作。
我们不应该是停留在使用的阶段,还应该深入去了解底层框架的原理,这样你才会在大厦之上更进一层。
参考链接:
https://www.jianshu.com/p/73c117fbfe10
https://blog.csdn.net/hry2015/article/details/78357990
如果本文任何错误,请批评指教,不胜感激 !
如果文章哪些点不懂,可以联系我交流学习!
微信公众号:
享学源码,行动起来,来源码行动