客户端负载均衡Ribbon之源码解析
什么是负载均衡器?
假设有一个分布式系统,该系统由在不同计算机上运行的许多服务组成。但是,当用户数量很大时,通常会为服务创建多个副本。每个副本都在另一台计算机上运行。此时,出现 “Load Balancer(负载均衡器)”。它有助于在服务器之间平均分配传入流量。
服务器端负载均衡器
传统上,Load Balancers(例如Nginx、F5)是放置在服务器端的组件。当请求来自 客户端 时,它们将转到负载均衡器,负载均衡器将为请求指定 服务器。负载均衡器使用的最简单的算法是随机指定。在这种情况下,大多数负载平衡器是用于控制负载平衡的硬件集成软件。
重点:
- 对客户端不透明,客户端不知道服务器端的服务列表,甚至不知道自己发送请求的目标地址存在负载均衡器。
- 服务器端维护负载均衡服务器,控制负载均衡策略和算法。
客户端负载均衡器
当负载均衡器位于 客户端 时,客户端得到可用的服务器列表然后按照特定的负载均衡策略,分发请求到不同的 服务器 。
重点:
- 对客户端透明,客户端需要知道服务器端的服务列表,需要自行决定请求要发送的目标地址。
- 客户端维护负载均衡服务器,控制负载均衡策略和算法。
- 目前单独提供的客户端实现比较少( 我用过的只有Ribbon),大部分都是在框架内部自行实现。
Ribbon
简介
Ribbon是Netflix公司开源的一个客户单负载均衡的项目,可以自动与 Eureka 进行交互。它提供下列特性:
- 负载均衡
- 容错
- 以异步和反应式模型执行多协议 (HTTP, TCP, UDP)
- 缓存和批量
Ribbon中的关键组件
- ServerList:可以响应客户端的特定服务的服务器列表。
- ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。
- ServerListUpdater:用于执行动态服务器列表更新。
- Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
- Ping:客户端用于快速检查服务器当时是否处于活动状态。
- LoadBalancer:负载均衡器,负责负载均衡调度的管理。
源码分析
LoadBalancerClient
实际应用中,通常将 RestTemplate 和 Ribbon 结合使用,例如:
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
消费者调用服务接口:
@Service
public class RibbonService {
@Autowired
private RestTemplate restTemplate;
public String hi(String name) {
return restTemplate.getForObject("http://service-hi/hi?name="+name,String.class);
}
}
@LoadBalanced,通过源码可以发现这是一个标记注解:
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
通过注释可以知道@LoadBalanced注解是用来给RestTemplate做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。
根据spring boot的自动配置原理,可以知道同包下的LoadBalancerAutoConfiguration,应该是实现客户端负载均衡器的自动化配置类。代码如下:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
从代码可以看出,这个类作用主要是使用RestTemplateCustomizer对所有标注了@LoadBalanced的RestTemplate Bean添加了一个LoadBalancerInterceptor拦截器,而这个拦截器的作用就是对请求的URI进行转换获取到具体应该请求哪个服务实例。
那再看看添加的拦截器LoadBalancerInterceptor的代码,如下:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@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);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
从代码可以看出 LoadBalancerInterceptor 拦截了请求后,通过LoadBalancerClient执行具体的请求发送。
打开LoadBalancerClient,发现它是一个接口:
public interface LoadBalancerClient {
ServiceInstance choose(String serviceId);
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
接口说明:
- ServiceInstance choose(String serviceId):根据传入的服务id,从负载均衡器中为指定的服务选择一个服务实例。
- T execute(String serviceId, LoadBalancerRequest request):根据传入的服务id,指定的负载均衡器中的服务实例执行请求。
- T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request):根据传入的服务实例,执行请求。
LoadBalancerClient 有一个唯一的实现类 RibbonLoadBalancerClient,关键代码如下:
public class RibbonLoadBalancerClient implements LoadBalancerClient {
public ServiceInstance choose(String serviceId) {
Server server = this.getServer(serviceId);
return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
Server server = this.getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, ribbonServer, request);
}
}
protected Server getServer(String serviceId) {
return this.getServer(this.getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
return loadBalancer == null ? null : loadBalancer.chooseServer("default");
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
//省略...
}
负载均衡器
从 RibbonLoadBalancerClient 代码可以看出,实际负载均衡的是通过 ILoadBalancer 来实现的。
ILoadBalancer 接口代码如下:
public interface ILoadBalancer {
public void addServers(List<Server> newServers);
public Server chooseServer(Object key);
public void markServerDown(Server server);
public List<Server> getReachableServers();
public List<Server> getAllServers();
}
接口说明:
- addServers:向负载均衡器中添加一个服务实例集合。
- chooseServer:跟据key,从负载均衡器获取服务实例。
- markServerDown:用来标记某个服务实例下线。
- getReachableServers:获取可用的服务实例集合。
- getAllServers():获取所有服务实例集合,包括下线的服务实例。
ILoadBalancer 的实现 依赖关系示意图如下:
- NoOpLoadBalancer:啥都不做
- BaseLoadBalancer:
- 一个负载均衡器的基本实现,其中有一个任意列表,可以将服务器设置为服务器池。
- 可以设置一个ping来确定服务器的活力。
- 在内部,该类维护一个“all”服务器列表,以及一个“up”服务器列表,并根据调用者的要求使用它们。
- DynamicServerListLoadBalancer:
- 通过动态的获取服务器的候选列表的负载平衡器。
- 可以通过筛选标准来传递服务器列表,以过滤不符合所需条件的服务器。
- ZoneAwareLoadBalancer:
- 用于测量区域条件的关键指标是平均活动请求,它根据每个rest客户机和每个区域聚合。这是区域内未完成的请求总数除以可用目标实例的数量(不包括断路器跳闸实例)。当在坏区上缓慢发生超时时,此度量非常有效。
- 该负载均衡器将计算并检查所有可用区域的区域状态。如果任何区域的平均活动请求已达到配置的阈值,则该区域将从活动服务器列表中删除。如果超过一个区域达到阈值,则将删除每个服务器上活动请求最多的区域。一旦去掉最坏的区域,将在其余区域中选择一个区域,其概率与其实例数成正比。服务器将使用给定的规则从所选区域返回。对于每个请求,将重复上述步骤。也就是说,每个与区域相关的负载平衡决策都是实时做出的,最新的统计数据可以帮助进行选择。
那么在整合Ribbon的时候Spring Cloud默认采用了哪个具体实现呢?我们通过RibbonClientConfiguration配置类,可以知道在整合时默认采用了ZoneAwareLoadBalancer来实现负载均衡器。
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
return (ILoadBalancer)(this.propertiesFactory
.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory
.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}
从这段代码 ,也可以看出,负载均衡器所需的主要配置项是IClientConfig, ServerList, ServerListFilter, IRule, IPing, ServerListUpdater。下面逐一分析他们。
IClientConfig
IClientConfig 用于对客户端或者负载均衡的配置,它的默认实现类为 DefaultClientConfigImpl。
IRule
为LoadBalancer定义“负载均衡策略”的接口。
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
IRule 的实现 依赖关系示意图如下:
- BestAvailableRule:选择具有最低并发请求的服务器。
- ClientConfigEnabledRoundRobinRule:轮询。
- RandomRule:随机选择一个服务器。
- RoundRobinRule:轮询选择服务器。
- RetryRule:具备重试机制的轮询。
- WeightedResponseTimeRule:根据使用平均响应时间去分配一个weight(权重) ,weight越低,被选择的可能性就越低。
- ZoneAvoidanceRule:根据区域和可用性筛选,再轮询选择服务器。
IPing
定义如何 “ping” 服务器以检查其是否存活。
public interface IPing {
public boolean isAlive(Server server);
}
IPing 的实现 依赖关系示意图如下:
- PingUrl:真实的去ping 某个url,判断其是否alive。
- PingConstant:固定返回某服务是否可用,默认返回true,即可用
- NoOpPing:不去ping,直接返回true,即可用。
- DummyPing:继承抽象类AbstractLoadBalancerPing,认为所以服务都是存活状态,返回true,即可用。
- NIWSDiscoveryPing:结合eureka使用时,如果Discovery Client在线,则认为心跳检测通过。
ServerList
定义获取所有的服务实例清单。
public interface ServerList<T extends Server> {
public List<T> getInitialListOfServers();
public List<T> getUpdatedListOfServers();
}
ServerList 的实现 依赖关系示意图如下:
- DomainExtractingServerList:代理类,根据传入的ServerList的值,实现具体的逻辑。
- ConfigurationBasedServerList:从配置文件中加载服务器列表。
- DiscoveryEnabledNIWSServerList:从Eureka注册中心中获取服务器列表。
- StaticServerList:通过静态配置来维护服务器列表。
ServerListFilter
允许根据过滤配置动态获得的具有所需特性的候选服务器列表。
public interface ServerListFilter<T extends Server> {
public List<T> getFilteredListOfServers(List<T> servers);
}
ServerListFilter 的实现 依赖关系示意图如下:
- DefaultNIWSServerListFilter:完全继承自ZoneAffinityServerListFilter。
- ZonePreferenceServerListFilter:EnableZoneAffinity 或 EnableZoneExclusivity 开启状态使用,默认关闭。处理基于区域感知的过滤服务器,过滤掉不和客户端在相同zone的服务,若不存在相同zone,则不进行过滤。
- ServerListSubsetFilter:服务器列表筛选器,它将负载平衡器使用的服务器数量限制为所有服务器的子集。如果服务器机群很大(例如数百个),并且不需要使用每一个机群并将连接保存在http客户机的连接池中,那么这是非常有用的。它还可以通过比较总的网络故障和并发连接来驱逐相对不健康的服务器。
ServerListUpdater
用于执行动态服务器列表更新。
public interface ServerListUpdater {
public interface UpdateAction {
void doUpdate();
}
void start(UpdateAction updateAction);
void stop();
String getLastUpdate();
long getDurationSinceLastUpdateMs();
int getNumberMissedCycles();
int getCoreThreads();
}
ServerListUpdater 的实现 依赖关系示意图如下:
- PollingServerListUpdater:默认的实现策略,会启动一个定时线程池,定时执行更新策略。
- EurekaNotificationServerListUpdater:利用Eureka的事件监听器来驱动服务列表的更新操作。
参考资料
https://github.com/Netflix/ribbon/wiki
http://tech.lede.com/2018/01/11/rd/server/NetflixRibbon/
http://blog.didispace.com/springcloud-sourcecode-ribbon/
https://www.fangzhipeng.com/springcloud/2017/08/11/Ribbon-resources.html
https://blog.csdn.net/Tincox/article/details/79210309
欢迎扫码或微信搜索公众号《程序员果果》关注我,关注有惊喜~
客户端负载均衡Ribbon之源码解析的更多相关文章
- 客户端负载均衡Ribbon
客户端负载均衡Ribbon 一.Ribbon是什么 二.Ribbon实现客户端负载均衡 三.Ribbon负载均衡策略 四.Rest请求模板类解读 4.1 RestTemplate的GET请求 第一种: ...
- 客户端负载均衡Ribbon之二:Loadbalance的源码
Load Balance负载均衡是用于解决一台机器(一个进程)无法解决所有请求而产生的一种算法. 像nginx可以使用负载均衡分配流量,ribbon为客户端提供负载均衡,dubbo服务调用里的负载均衡 ...
- Spring Cloud第四篇 | 客户端负载均衡Ribbon
本文是Spring Cloud专栏的第四篇文章,了解前三篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring Cl ...
- 【Dalston】【第二章】客户端负载均衡(Ribbon)
对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...
- 客户端负载均衡Ribbon之一:Spring Cloud Netflix负载均衡组件Ribbon介绍
Netflix:['netfliːks] ribbon:英[ˈrɪbən]美[ˈrɪbən]n. 带; 绶带; (打印机的) 色带; 带状物;v. 把…撕成条带; 用缎带装饰; 形成带状; L ...
- Spring Cloud入门教程(二):客户端负载均衡(Ribbon)
对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...
- 服务注册发现Eureka之三:Spring Cloud Ribbon实现客户端负载均衡(客户端负载均衡Ribbon之三:使用Ribbon实现客户端的均衡负载)
在使用RestTemplate来消费spring boot的Restful服务示例中,我们提到,调用spring boot服务的时候,需要将服务的URL写死或者是写在配置文件中,但这两种方式,无论哪一 ...
- 五、springcloud之客户端负载均衡Ribbon
一.简介 在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的.Spring cloud有两种服务调用方式: 一种是ribbon+restTemplate, ...
- 003客户端负载均衡Ribbon & 短路器Hystrix
1.POM配置 和普通Spring Boot工程相比,仅仅添加了Eureka.Ribbon.Hystrix依赖和Spring Cloud依赖管理 <dependencies> <!- ...
随机推荐
- destoon 支付异步接口文件 notify.php 调试方式
在if($verify_result) { 之前复制这三个变量 就可以直接访问notify.php 启用调试模式 或者 逐步echo 相关变量来调试 错误原因 notify.php没有入口文件 是 ...
- Python基础——赋值机制
使用id()函数用于获取对象的内存地址. 使用is来判断是不是指向同一个内存. 把一个对象赋值给另一个对象,两个对象都指向同一个内存地址. test=1000 test1=test id(test) ...
- 按键精灵安卓版 tap、touch命令 不好用的解决办法!
用按键精灵手机版写脚本来操作新浪微博APP,在关注列表页自动取消关注,代码如下: If x > -1 And y > -1 Then delay 1000 tap x,y delay 10 ...
- Python学习笔记:面向对象(类)
1.类定义:Python3中,如果新建的类没有继承任何其他类,默认继承基础类object.Python2中如果没有显式继承object类就是经典类,而显式继承了object类就是新式类,Python2 ...
- c++IDE
暂时使用Code::Blocks 16.01. 因为之前没有c++编译器,所以去官网选择安装codeblocks-16.01mingw-setup.exe 然后settings>Compiler ...
- 搜索引擎elasticsearch常用指令演示
目录 交互方式 常用操作示例 添加文档 删除文档 修改文档 查询 简单查询 高级多条件查询 交互方式 操作ES有3种方式: kibana控制台(Dev Tools) Http + json api接口 ...
- golang json 示例
jsonStr, err := client.Get( deviceIdKey ).Result() if err == redis.Nil { deviceIds = []string{device ...
- <node>……express的中间件……//
Express是一个基于Node.js平台的web应用开发框架,在Node.js基础之上扩展了web应用开发所需要的基础功能,从而使得我们开发Web应用更加方便.更加快捷. 中间件是什么? 中间件函数 ...
- 光学字符识别OCR-8 综合评估
数据验证 尽管在测试环境下模型工作良好,但是实践是检验真理的唯一标准.在本节中,我们通过自己的模型,与京东的测试数据进行比较验证. 衡量OCR系统的好坏有两部分内容:(1)是否成功地圈 ...
- Flask_单例模式
在flask实现单例模式的方法有多种: 这里我们列举五种,行吗? 第一种: 国际惯例:基于文件导入 第二种: 基于类的单例模式: 它又分两种: 一种加锁,一种不加锁. 不加锁的话,可以并发,但是我们的 ...