说的多,也要做的多,这样才踏实多。
- 1.基础知识
- 2.Netflix Ribbon 主要组件
- 3.SC-Ribbon 主要组件(脱离 Eureka)
- 3.SC-Ribbon 与 eureka 一起使用又发生了什么
- 4.Resttemplate 如何具有负载均衡能力
- 5.总结
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
3.SC-Ribbon 主要组件(脱离 Eureka)
SpringCloud-Ribbon 集成了Netflix Ribbon
,使其融入 springcloud 体系。
核心的功能都在Netflix Ribbon
项目中了。SC-Ribbon 封装Netflix Ribbon
,主要做了一些集成工作。
为此,SpringCloud-Ribbon 又增加了哪些组件,干了哪些事呢?
思考
:既然核心功能在Netflix Ribbon
项目中了。此项目干的事,无非就是从以下几方面入手
- 配置层面:符合 Spring 体系的配置习惯
- 使用层面: 如何使用 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;
}
}
这里有两个点:
- 根据传入服务
serverId
创建一个AnnotationConfigApplicationContext
,装载属于他自己的负载均衡套件。不同的服务有不同的上下文,里面存储的是属于他的负载均衡套件组合 - 服务的负载均衡套件组合方式。是上文提到的两种配置方式决定的。也就是
configurations
与defaultConfigType
两个属性所代表的规范。
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.配置层面:
当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;
}
}
- IPing: 使用 NIWSDiscoveryPing 这个实现
- 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。
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/73c117fbf e10
如果本文任何错误,请批评指教,不胜感激 ! 如果文章哪些点不懂,可以联系我交流学习!