前言
客户端负载均衡和服务端负载均衡的区别:
客户端负载均衡:客户端➡服务端
(自己选择服务器实现)
服务端负载均衡:客户端➡负载均衡服务器➡服务端
(有一个管理分配服务器)
什么是负载均衡?
负载均衡器会维护一个可用的服务清单,通过心跳检测来剔除清单中故障的服务端节点。
当客户端借助网络发送请求到负载均衡器时,负载均衡器从维护的服务清单里面选择一个服务器,并将客户端请求转发到此服务器,从而提高系统的可用性和稳定性。
一、Ribbeon实现客户端负载均衡
1、什么是Ribbon
Ribbon是Netflix开源的一款用于客户端负载均衡的软件工具,
它在集群中为各个客户端的通信提供了支持,有助于控制HTTP和TCP客户端的行为,提供了很多负载均衡的算法,例如轮询,随机等,
同时也可以实现自定义的算法。
在Spring Cloud 构建的微服务中,Ribbon作为服务消费者的负载均衡器,有两种使用方式,一种是与RestTemplate相结合,另一种是与Feign相结合。
Feign已经默认集成了Ribbon。
用于生产的Ribbon的子模块
Ribbon包含很多子模块,但很多子模块没有用于生产环境,目前用于生产的Ribbon的子模块具体如下:
• ribbon-core:定义负载均衡接口、客户端接口、内置的负载均衡实现等API。
• ribbon-eureka :提供eureka客户端实现负载均衡的API。
• ribbon-httpclient:对Apache的HttpClient进行封装,该模块提供了含有负载均衡功能的REST客户端。
Ribbon整合Eureka
在Spring Cloud 中,当Ribbon和Eureka配合使用时,Ribbon可从Eureka Server中获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
Ribbon整合Eureka的结构示例如下图所示。
在上图中,搭建了一个Eureka服务器,三个服务提供者以及一个含有Ribbon的服务消费者。三个服务提供者向Eureka服务器注册服务,当多个URL向服务调用者发起请求时,基于Ribbon的负载均衡器能够有效地将请求分摊到不同的机器上。
微服务学习-2中已经实现了Eureka 高可用,理论上使得微服务已经很完美了。但是,考虑到机器自身硬件条件的限制,面对流量高峰,系统同样还会存在宕机等情况。此时,如果使用Ribbon整合Eureka实现负载均衡,将用户请求分摊到多个服务上,能够大幅减轻访问服务压力,使系统达到更好的负载能力。下面我们在之前搭建的Eureka集群基础上进行改造。
2、第一个Ribbon实例
第一个Ribbon实例的架构图:
2.1、改造服务者
改造微服务学习2中服务提供者Eureka-provider和eureka-provider-another
(1)在两个服务者下各自创建一个Controller包
(2)在各自的Controller包下创建一个PortController类
(3)在两个PortController下都填写如下代码
(该类能够返回当前项目的端口号。)
2.2、搭建含有Ribbon的服务消费者
2.2.1. 创建项目,引入依赖
(1)在父级目录下创建Ribbon服务消费者模块
(2)在pom中添加ribbon依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.2.2、在application.yml文件进行相关配置
(1)在resources目录下添加application.yml文件
(2)application.yml下填写如下配置:
2.2.3、添加@EnableEurekaClient注解
(1)新建一个RibbonClient包
(2)添加RibbonClientApplication启动类
在 项 目 启 动 类 RibbonClientApplication 上 添 加@EnableEurekaClient注解开启Eureka Client功能。
2.2.4、创建配置类
(1)创建一个Config包
(2)在config包下创建RibbonConfig类
(3)在RibbonConfig类注入restTemplate的Bean,并在这个Bean中加上@LoadBalanced注解
@Bean注解的使用和详解
RestTemplate简洁使用指南
2.2.5、创建Service类
(1)新建Service包
(2)在Service下创建一个RibbonService类
(3)在RibbonService类中添加代码
(在该类的hi()方法中使用restTemplate调用eureka-provider的API接口)
2.2.6、创建Controller类
(1)新建Controller包
(2)在Controller包下创建一个RibbonController类
(3)在RibbonController类中添加如下代码
(在该类上添加@RsetController注解,将RibbonController 标注为一个Controller类。在类中写一个hi ()方法,调用RibbonService的hi()方法。)
2.2.7、测试运行
依次启动 eureka-server, eureka-server-another, eureka-provider, eureka-provider-another,ribbon-client,所有服务启动成功后,在浏览器输入地址栏输入127.0.0.1:8764/hi一直刷新,每次刷新显示的端口号一直在变说明负载均衡成功。
3、Ribbon的工作原理
前面我们使用Ribbon实现负载均衡
时,基本用法是注入一个RestTemplate, 并使用@LoadBalanced注解标注RestTemplate
,从而使RestTemplate具备负载均衡的能力。
当Spring容器启动时,使用@LoadBalanced注解修饰的RestTemplate会被添加拦截器,拦截器中使用了LoadBalancerClient处理请求
,从而达到负载均衡的目的。那么LoadBalancerClient内部是如何做到的呢?接下来我们通过源码分析的方式来剖析Ribbon负载均衡的工作原理。
LoadBalancerClient
是Spring Cloud提供的一个非常重要的接口,它继承自ServiceInstanceChooser接口
,该接口的实现类是RibbonLoadBalanceClient,
它们之间的关系如下图所示。
3.1、ServiceInstanceChooser源码解析
为了大家更好地理解LoadBalancerClient接口及其实现类的实现细节,我们先查看LoadBalancerClient的部分源码,具体如下:
public interface LoadBalancerClient extends ServiceInstanceChooser {
<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);
}
上述源码中,LoadBalancerClient提供的两个execute()方法用于执行请求,reconstructURI()方法用于重构URL。
继续查看LoadBalancerClient继承的ServiceInstanceChooser接口源码,具
体如下:
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
上述源码中,ServiceInstanceChooser接口定义一个choose()
方法,该方法用于根据serviceId选择一个服务实例,即通过服务名选择服务实例。
3.2、介绍ILoadBalancer接口
RibbonLoadBalanceClient是LoadBalancerClient的实现类,它用来执行最终的负载均衡请求
。其中,RibbonLoadBalanceClient的一个choose()方法用于选择具体的服务实例,
其内部是通过getServer()方法交给ILoadBalancer完成的。
ILoadBalancer
是一个接口,该接口定义了一系列实现负载均衡的方法。
ILoadBalancer接口的实现类结果如下图所示。
3.3、BaseLoadBalancer和DynamicServerListLoadBalancer实现的配置
查看BaseLoadBalancer和DynamicServerListLoadBalancer源码,默认情况下实现了以下配置:
(1)IClientConfig clientConfig:
用于配置负载均衡客户端,
默认实现类是DefaultClientConfigImpl。
(2)IRule rule:
用于配置负载均衡的策略,
默认使用的是RoundRobinRule策略,也就是轮询策略。
(3)IPing ping:
用于检查当前服务是否有响应,
从而判断当前服务是否可用,默实现类是DummyPing,
该实现类的isAlive()方法返回值是true,默认所有服务实例都是可用的。
(4)ServerList serverList:
用于获取所有Server注册列表信息。
通过跟踪源码会发现,ServerList的实现类是DiscoveryEnabledNIWSServerList,该类定义的obtainServersViaDiscovery()方法是根据eurekaClientProvider.get()方法获取EurekaClient,再根据EurekaClient获取服务注册列表信息。EurekaClient的实现类是DiscoveryClient,DiscoveryClient具有服务注册、获取服务注册列表等功能。
(5)ServerListFilter filter:
定义了根据配置过滤或者动态获取符合条件的服务列表,
默认实现类是ZonePreferenceServerListFilter,该策略能够优先过滤出与请求调用方处于同区域的服务实例。
综上所述,使用RibbonLoadBalanceClient实现负载均衡时,会从EurekaClient获取服务列表信息,然后根据IPing判断服务是否可用。如果服务可用,则会根据IRule选择负载均衡策略,否则会重新获取服务清单。
4、 Ribbon负载均衡策略
4.1、介绍IRule接口
默认情况下,Ribbon使用的负载均衡策略是轮询,
实际上,Ribbon提供了很多负载均衡算法,其中IRule接口就是所有负载均衡算法的父接口,它的实现类结构如下图所示。
4.2、介绍AbstractLoadBalancerRule抽象类
AbstractLoadBalancerRule是负载均衡策略的抽象类,
该抽象类中定义了负载均衡器ILoaderBalancer对象,
该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略。
4.3、介绍实现负载均衡算法的实现类
接下来,对实现负载均衡算法的实现类进行介绍,具体如下:
RoundRobinRule:
实现了按照线性轮询
的方式依次选择服务的功能。
WeightedResponseTimeRule:
它是对RoundRobinRule的扩展,会根据平均响应时间计算所有服务的权重,
响应时间越快,服务权重越大,被选中的概率越高。
ZoneAvoidanceRule:
它是PredicateBasedRule的具体实现类,其内部通过使用ZoneAvoidancePredicate和AvailabilityPredicate判断是否选择某一个服务,前者用于判断服务所在区域的性能是否可用,后者用于过滤掉连接数过多的服务。
AvailabilityFilteringRule:
使用AvailabilityPredicate过滤由于多次访问故障而处于断路器跳闸状态的服务,
还有并发的连接数超过阀值的服务,然后对剩余的服务列表进行轮询。
BestAvailableRule:用于先过滤掉多次访问故障而处于断路跳闸状态的服务,然后选择一个并发量最小的服务。
RandomRule:该策略实现了从服务清单中随机选择一个服务的功能。
ClientConfigEnableRoundRobinRule:
该类是一个抽象类,
该类本身没有实现什么特殊的处理逻辑,我们也不会直接使用该策略,但是通过BestAvailableRule和继承该策略默认实现了线性轮询,
它的内部定义了一个RoundRobinRule策略,
PredicateBasedRule:
继承了ClientConfigEnableRoundRobinRule,其内部会先通过chooseRoundRobinAfterFiltering()方法筛选服务清单,然后以线性轮询的方式从过滤后的服务清单中选择一个服务。
二、Feign实现客户端负载均衡
1、Feign简述
Feign是Netflix开发的声明式、模板化的HTTP客户端。当Feign与Eureka和Ribbon组合使用时,Feign就具有了负载均衡的功能。在Feign的实现下,我们只需要定义一个接口
并使用注解方式配置,即可完成服务接口的绑定,从而简化了Ribbon自动封装服务调用客户端的开发工作量。
如此看来,我们可以把Feign理解为一个Spring Cloud远程服务的框架或者工具,它能够帮助开发者用更少的代码,更好的兼容方式对远程服务进行调用。
2、第一个Feign程序
2.1、创建Feign客户端
1.在父级目录下创建Feign服务消费者模块
2.在pom中添加ribbon依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.在resources目录下添加application.yml文件
4.application.yml下填写如下配置:
5.新建一个FeignClient包
6.添加FeignClientApplication启动类
7.在启动类里添加如下代码:
在启动类EurekaFeignClientApplication中添加@EnableEurekaClient注解开启Eureka Client功能,再添加@EnableFeignClients注解开启Feign Client功能。
8.创建一个Service包
9.在Service包下创建FeignService接口类
10.在接口类中添加代码如下
(通过添加@FeignClient注解指定要调用的服务。)
11.新建Controller包
12在Controller包下创建一个FeignController类
13.在FeignController类中添加代码
(该类定义的hi()方法用于调用FeignService的sayHello()方法。)
2.2、测试运行
依次启动eureka-server,eureka-server-another,eureka-provider,eureka-provider-another,feign-client,所有服务启动成功后,在浏览器输入地址栏输入127.0.0.1:8765/hi,一直刷新,每次刷新显示的端口号一直在变说明feign启动并负载均衡成功。
3、Feign配置
Feign从全局配置和指定服务配置两个方面对服务调用进行相关设置。
3.1、全局配置
全局配置其实非常简单,直接在application.yml配置文件中,示例代码如下:
feign:
client:
config:
default:
connectTimeout: 1000 #设置连接超时,单位毫秒
readTimeout: 6500 #设置调用超时,单位毫秒
为了测试设置的ConnectTimeout参数是否生效,在服务提供者eurekaprovider的getPort ()方法中添加下列代码:
try{
Thread.sleep(6000);
}catch(Exception e){
}
由于ConnectTimeout参数设置的超时时间是5秒,上述代码设置的程序等待时间是6秒,这样必然会导致出现超时。此时使用浏览器访问http://localhost:8765/hi,发现消费者lesson04-feign-client的控制台会报java.net.SocketTimeoutException: Read timed out。
3.2、指定服务配置
大多数的情况下,我们对于服务调用超时时间可能会根据实际服务的特性做一些调整,所以仅仅依靠默认的全局配置是不行的。使用Spring Cloud Feign时,如果要对各个服务消费者进行不同配置,可以指定服务进行设置,示例代码如下:
feign:
client:
config:
eureka-provider:
connectTimeout: 1000 #设置连接超时,单位毫秒
readTimeout: 5000 #设置调用超时,单位毫秒
3.3、其他配置
Spring Cloud Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗。
只需要通过以下参数设置,就可以开启请求与响应的压缩功能,代码如下:
feign:
compression:
request:
enabled: true
response:
enabled: true
还能对请求压缩做一些细致的设置,例如,下面的配置内容指定了压缩请求支持的MIME TYPE类型,并设置了请求压缩的大小下限,只有超过这个大小的请求才会对其进行压缩。代码如下
feign:
compression:
request:
mime-types:
text/xml,application/xml,application/json
min-request-size: 2048
上述配置的参数中,feign.compression.request.mime-types和feign.compressionfeign.compression.request.min-request-size设置的均为默认值.
4、Feign的工作原理
Feign服务调用的工作原理可以分为以下几个步骤:
(1)首先通过@EnableFeignClients注解开启FeignClient功能。
程序启动时,会通过该注解开启对@FeignClient注解的包扫描。
(2)根据Feign规则实现接口,并在接口上面添加@FeignClient注解。
(3)程序启动后,会进行包扫描,扫描所有的@FeignClient注解类
并将这些信息注入IoC容器。
(4)当接口方法被调用时,通过JDK的代理生成具体的RequestTemplate模板对象。根据RequestTemplate再生成HTTP请求的Request对象,Request对象交给Client处理。
总结:
Feign调用工作原理:
@EanbleFeignClient开启FeignClient;
Feign接口用@FeignClient注入IoC容器;
通过JDK代理生成具体的HTTP服务调用对象。
扩展: