SpringCloud-负载均衡器 Ribbon

上一篇 SpringCloud-声明式服务调用 Feign 中介绍了如何使用 Feign 来完成服务调用。因为 Feign 本身已经集成了 Ribbon ,所以也具有负载均衡的能力。那么本篇将使用 RestTemplate + Ribbon 来实现服务调用和负载均衡策略。

Ribbon 简介

Ribbon 是管理HTTP和TCP服务客户端的负载均衡器。Ribbon 具有一些列带有名称的客户端,也就是带有名称的Ribbon 客户端。每个客户端由可配置的组件构成,负责一类服务的调用请求。Spring Cloud 通过RibbonClientConfiguration 为每个Ribbon 客户端创建一个ApplicationContext 上下文来进行组件装配。Ribbon 作为 Spring Cloud的负载均衡机制的实现,可以与OpenFeign 和 RestTemplate 进行无缝集成,让二者也具有负载均衡的能力。

负载均衡策略

策略类
命名
备注
RoundRobinRule
轮训策略
按顺序循环选择 Server
RandomRule
随机策略
随机选择 Server
RetryRule
重试策略
在一个配置时问段内当选择 Server 不成功,则一直尝试选择一个可用的 Server
BestAvailableRule
最低并发策略
逐个考察 Server,如果 Server 断路器打开,则忽略,再选择其中并发连接最低的 Server
AvailabilityFilteringRule
可用过滤策略
过滤掉一直连接失败并被标记为 circuit tripped 的 Server,过滤掉那些高并发连接的 Server(active connections 超过配置的网值)
ResponseTimeWeightedRule
响应时间加权策略
根据 Server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO等,这些因素直接影响着响应时间
ZoneAvoidanceRule
区域权衡策略
综合判断 Server 所在区域的性能和 Server 的可用性轮询选择 Server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 Server

环境准备

类别
JDK
1.8.0_162
SOFABoot/SpringBoot
3.0.0/2.0.x.RELEASE
SpringCloud
Finchley.RC1
IDE
IDEA

工程背景

本节将会创建一个 sofa-eureka-consumer-Ribbon 工程,通过 Spring Cloud 提供的负载均衡器 Ribbon 实现服务的负载均衡,并对 Ribbon 中的负载均衡策略进行验证。

新建 sofa-eureka-consumer-ribbon

本工程继续使用《SpringCloud-Eureka 服务注册》中的父工程来构建。

右击 sofa-eureka-parent 父工程 -> New -> Module,这里选择 Maven 工程;

  • artifactId:sofa-eureka-consumer-ribbon

前面我们已经对feign进行的实际操作,因此本节使用 Ribbon + RestTemplate 组合实现具体的负载均衡实验。

修改 pom 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<parent>
<artifactId>sofa-eureka-parent</artifactId>
<groupId>com.alipay.sofa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>sofa-eureka-consumer-ribbon</artifactId>

<dependencies>
<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-netflix-ribbon</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

</dependencies>

配置文件

1
2
3
4
5
6
7
8
9
server:
port: 8889
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: eureka-consumer-ribbon

启动类

这里需要引入 @EnableEurekaClient 注解,表示当前是一个客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
@EnableEurekaClient
public class SofaEurekaConsumerRibbonApplication {

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(SofaEurekaConsumerRibbonApplication.class, args);
}
}

@LoadBalanced : Spring Cloud 为客户端负载均衡创建了特定的注解,被该注解修饰的 RestTemplate Bean实例,Spring Cloud 就会让 RestTemplate 使用相关的负载均衡策略,默认情况下使用的就是 Ribbon。

资源类

这里我们通过 restTemplate 去访问 Provider 提供的服务,需要注意,这里为了演示作用,直接将资源 Url 固定写成:http://HELLOSOFASERVICE/helloHELLOSOFASERVICE 为 Provider 提供的服务的实例名称,也就是 Eureka 服务端界面上对应的 Application。

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class RibbonController {

@Autowired
private RestTemplate restTemplate;

@RequestMapping("/hello")
public String hello(){
return restTemplate.getForObject("http://HELLOSOFASERVICE/hello",String.class);
}
}

启动服务

这里正常先后启动 服务注册中心 sofa-eureka-server-center ;服务提供方 sofa-eureka-provider ,服务提供方为了方便演示,这里启动4个实例,对应的端口分别为:8081,8082,8083,8084,如下:

然后启动当前 sofa-eurek-consumer-ribbon 工程。默认情况下,不指定任何负载均衡策略,使用的是轮询策略。

浏览器输入 http://localhost:8889/hello ,调用10次:

1
2
3
4
5
6
7
8
9
10
11
12
Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8084 And hostname is HelloSOFAService

Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8084 And hostname is HelloSOFAService

Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService

从结果来看,默认策略应该是轮询(不用情况下,调用顺序不一定是1-2-3-4,但是以每4组为一组来看,存在周期性)。

负载均衡策略设置

全局设置

全局设置就是自己定义一个配置类,然后在配置类中指定具体的负载均衡策略。在com.alipay.sofa.cloud.configuration 包下面新建一个配置类,这里使用的策略是随机策略:

1
2
3
4
5
6
7
8
@Configuration
public class RibbonGlobalLoadBalancingConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}

浏览器输入 http://localhost:8889/hello ,调用10次:

1
2
3
4
5
6
7
8
9
10
11
12
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8084 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8084 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService

Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8084 And hostname is HelloSOFAService

Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService

从结果来看,具有随机属性。

针对单个服务的 Ribbon 负载均衡策略

新建一个 RibbonRandomLBConfiguration 配置类,这里有个前提是需要删除 全局配置类 。

1
2
3
4
5
6
@Configuration
public class RibbonRandomLBConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}

修改启动类,增加 @RibbonClient 注解,并且通过 configuration 指定负载均衡策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="HELLOSOFASERVICE",configuration = RibbonRandomLBConfiguration.class)
public class SofaEurekaConsumerRibbonApplication {

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(SofaEurekaConsumerRibbonApplication.class, args);
}
}

浏览器输入 http://localhost:8889/hello ,调用10次:

1
2
3
4
5
6
7
8
9
10
11
12
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService

Hello SOFA! Now Port is 8084 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8082 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService

Hello SOFA! Now Port is 8081 And hostname is HelloSOFAService
Hello SOFA! Now Port is 8083 And hostname is HelloSOFAService

从结果来看,具有随机属性。

@RibbonClient 注解属性中,name 并非是一个数组,也就是说只能指定一个服务实例。那么基于上述情况,如果还存在另外一个服务,比如 SOFABOOTHELLOSERVICE ,那么对于此服务的调用会是什么情况呢?

先向注册中心注册两个服务:HELLOSOFABOOTSERVICE 和 HELLOSOFASERVICE

修改 RibbonController ,增加一个 /helloBoot 资源地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class RibbonController {

@Autowired
private RestTemplate restTemplate;

@RequestMapping("/hello")
public String hello(){
return restTemplate.getForObject("http://HELLOSOFASERVICE/hello",String.class);
}

@RequestMapping("/helloBoot")
public String helloBoot(){
return restTemplate.getForObject("http://HELLOSOFABOOTSERVICE/hello",String.class);
}
}

重启启动当前服务。

浏览器中输入:http://localhost:8889/hello ,验证结果满足随机调用。

浏览器中输入:http://localhost:8889/helloBoot ,验证结果满足轮询调用。

基于配置文件的负载均衡策略设置

个人感觉基于配置文件配置方式更加直观,而且对于多个服务对应不同的负载策略设置也更加清晰,下面对HELLOSOFASERVICE 和 HELLOSOFABOOTSERVICE 均使用随机策略。

1
2
3
4
5
6
7
HELLOSOFASERVICE:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

HELLOSOFABOOTSERVICE:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

启动类中删除以下注解配置:

1
@RibbonClient(name = "HELLOSOFASERVICE", configuration = RibbonRandomLBConfiguration.class)

重启启动当前服务。

浏览器中输入:http://localhost:8889/hello ,验证结果满足随机调用。
浏览器中输入:http://localhost:8889/helloBoot ,验证结果满足随机调用。

作者

卫恒

发布于

2018-12-31

更新于

2022-04-23

许可协议

评论