概要: 微服务架构中,负载均衡是一个必不可少的技术。负载均衡决定着整个服务集群的性能和稳定。Spring Cloud体系中加入了Netflix公司的很多优秀产品,其中一个就是实现客户端负载均衡的Ribbon。
1.什么是负载均衡
负载均衡是高可用网络基础架构的一个关键组成部分,有了负载均衡,我们通常可以将我们的应用服务器部署多台,然后通过负载均衡将用户的请求分发到不同的服务器用来提高网站、应用、数据库或其他服务的性能以及可靠性。
不使用负载均衡的Web架构。
上图中,客户端之间通过网络与 Web 服务端相连,假想如果 Web 服务器宕机,那么用户访问网站时将得不到任何响应,出现单点故障问题。即使服务器可以正常工作,如果很多用户同时访问服务器,超过服务器的处理能力,那么会出现响应速度慢或者无法连接的情况,这也是用户无法接受的。
为了解决单点故障,引入负载均衡就可以解决上述问题!!
负载均衡,它可以将负载(工作任务)进行平衡、分摊到多个执行单元上运行。例如,Web服务器、FTP 服务器、企业关键应用服务器和其他主要任务服务器等,协同完成工作任务。
负载均衡分为硬件负载均衡和软件负载均衡两种:
- 硬件负载均衡的解决方案就是直接在服务器和外部网络间安装负载均衡设备,通常这种设备称为负载均衡器。由专门的设备完成专门的任务,独立于操作系统,整体性能得到大量提高,加上多样化的负载均衡策略,智能化的流量统计,可达到最佳的负载均衡效果。
- 软件负载均衡的解决方案是指在一台或多台服务器相应的操作系统上安装一个或多个附加软件来实现负载均衡,如DNS Load Balance,CheckPoint Firewall-1 ConnectControl等,它的优点是基于特定环境,配置简单,使用灵活,成本低廉,可以满足一般的负载均衡需求。
无论哪种负载均衡策略,都是为了系统高可用、缓解网络压力以及扩容机器处理能力。
使用负载均衡的Web架构,如下图所示:
负载均衡器会维护一个可用的服务清单,通过心跳检测来剔除清单中故障的服务端节点。当客户端借助网络发送请求到负载均衡器时,负载均衡器从维护的服务清单里面选择一个服务器,并将客户端请求转发到此服务器,从而提高系统的可用性和稳定性。
2.认识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的负载均衡器能够有效地将请求分摊到不同的机器上。
3.第一个Ribbon实例
Eureka高可用集群-CSDN博客已经实现了Eureka 高可用,理论上使得微服务已经很完美了。但是,考虑到机器自身硬件条件的限制,面对流量高峰,系统同样还会存在宕机等情况。此时,如果使用Ribbon整合Eureka实现负载均衡,将用户请求分摊到多个服务上,能够大幅减轻访问服务压力,使系统达到更好的负载能力。
下面我们在搭建的Eureka集群(Eureka高可用集群-CSDN博客)基础上进行改造。
第一个Ribbon实例的架构图如下所示:
搭建含有Ribbon的服务消费者
项目目录图:
pom.xml为这样时,右键这个文件
即可。
1.创建项目,引入依赖
因为我使用的spring-boot-starter-parent是2.7.0的版本,并且spring-cloud.version为2021.0.3,所以Spring Cloud 2020.0 及更高版本中,Ribbon 已经被 Spring Cloud LoadBalancer 替代
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xiaofen</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot with Eureka Server</description>
<properties>
<java.version>1.8</java.version>
<!-- 使用具体的Spring Cloud版本,避免使用别名或描述性文本 -->
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 使用与 Spring Cloud 版本兼容的 Eureka Server 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<!-- 注意:这里不直接指定版本,因为会在 dependencyManagement 中管理 -->
</dependency>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>-->
<!--Spring Cloud 2020.0 及更高版本中,Ribbon 已经被 Spring Cloud LoadBalancer 替代-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 如果需要,可以添加构建插件等配置 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.在application.yml文件进行相关配置
spring:
application:
name: eureka-ribbon-client
server:
port: 8764
eureka:
client:
service-url:
defaultZone: http://server1:7000/eureka/
instance:
hostname: localhost
3.添加@EnableEurekaClient注解
4.创建配置类
@Configuration
public class RibbonConfig {
/*@Bean:将当前对象注册到Spring容器*/
@Bean
@LoadBalanced /*将当前RestTemplate工具类赋予负载均衡的功能*/
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
return restTemplateBuilder.build();
}
}
5.创建Service类
@Service
public class RibbonService {
@Autowired
private RestTemplate restTemplate;
public String hello(){
return restTemplate.getForObject("http://eureka-provider/port",String.class);
}
}
6.创建Controller类
@RestController
public class RibbonController {
@Autowired
private RibbonService ribbonService;
@RequestMapping("/hello")
public String hello(){
return ribbonService.hello();
}
}
7.测试运行
访问Ribbon类写的接口,会发现端口变化!!
第一次:
第二次:
多次访问http://localhost:8764/hello时,浏览器页面会轮流显示两个服务提供者的端口号,说明负载均衡器会轮流请求服务提供者中的“hello”接口。
4.Ribbon的工作原理
前面我们使用Ribbon实现负载均衡时,基本用法是注入一个RestTemplate,并使用@LoadBalanced注解标注RestTemplate,从而使RestTemplate具备负载均衡的能力。
当Spring容器启动时,使用@LoadBalanced注解修饰的RestTemplate会被添加拦截器,拦截器中使用了LoadBalancerClient处理请求,从而达到负载均衡的目的。那么LoadBalancerClient内部是如何做到的呢?接下来我们通过源码分析的方式来剖析Ribbon负载均衡的工作原理。
LoadBalancerClient、ServiceInstanceChooser、 RibbonLoadBalanceClient之间的关系:
LoadBalancerClient是Spring Cloud提供的一个很重要的接口,它继承自ServiceInstanceChooser接口,该接口的实现类是RibbonLoadBalanceClient,它们之间的关系如下图所示。
LoadBalancerClient源码解析
为了大家更好地理解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。
ServiceInstanceChooser源码解析
继续查看LoadBalancerClient继承的ServiceInstanceChooser接口源码,具体如下:
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
上述源码中,ServiceInstanceChooser接口定义一个choose()方法,该方法用于根据serviceId选择一个服务实例,即通过服务名选择服务实例。
介绍ILoadBalancer接口
RibbonLoadBalanceClient是LoadBalancerClient的实现类,它用来执行最终的负载均衡请求。其中,RibbonLoadBalanceClient的一个choose()方法用于选择具体的服务实例,其内部是通过getServer()方法交给ILoadBalancer完成的。
ILoadBalancer是一个接口,该接口定义了一系列实现负载均衡的方法。ILoadBalancer接口的实现类结果如下图所示。
BaseLoadBalancer和DynamicServerListLoadBalancer实现的配置
查看BaseLoadBalancer和DynamicServerListLoadBalancer源码,默认情况下实现了以下配置:
5.Ribbon负载均衡策略
IRule接口:
默认情况下,Ribbon使用的负载均衡策略是轮询,实际上,Ribbon提供了很多负载均衡算法,其中IRule接口就是所有负载均衡算法的父接口,它的实现类结构如下图所示。
AbstractLoadBalancerRule抽象类
AbstractLoadBalancerRule是负载均衡策略的抽象类,该抽象类中定义了负载均衡器ILoaderBalancer对象,该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略。
实现负载均衡算法的实现类
- RoundRobinRule:实现了按照线性轮询的方式依次选择服务的功能。
- WeightedResponseTimeRule: 它是对RoundRobinRule的扩展,会根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越大,被选中的概率越高。
- ZoneAvoidanceRule:它是PredicateBasedRule的具体实现类,其内部通过使用ZoneAvoidancePredicate和AvailabilityPredicate判断是否选择某一个服务,前者用于判断服务所在区域的性能是否可用,后者用于过滤掉连接数过多的服务。
-
l AvailabilityFilteringRule : 使用 AvailabilityPredicate 过滤由于多次访问故障而处于断路器跳闸状态的服务 ,还有并发的连接数超过阀值的服务,然后 对剩余的服务列表进行轮询 。
-
l RandomRule :该策略实现了从服务清单中随机选择一个服务的功能。
- lBestAvailableRule:用于先过滤掉多次访问故障而处于断路跳闸状态的服务,然后选择一个并发量最小的服务
-
l ClientConfigEnableRoundRobinRule : 该类是一个 抽象类 ,该类本身没有实现什么特殊的处理逻辑,我们也不会直接使用该策略,但是通过 BestAvailableRule 和继承该策略默认实现了 线性轮询 ,它的内部定义了一个 RoundRobinRule 策略.
-
l PredicateBasedRule :继承了 ClientConfigEnableRoundRobinRule ,其内部会先通过 chooseRoundRobinAfterFiltering() 方法筛选服务清单,然后 以线性轮询的方式从过滤后的服务清单中选择一个服务 。