0%

SpringCloud下基于Ribbon的负载均衡实践

负载均衡大体可以分为两类:集中式、进程内。前者也被称为服务端负载均衡,其一般位于服务集群的前端,统一接收、处理、转发客户端的请求。典型地包括F5硬件、LVS、Nginx等技术方案;而后者也被称为客户端负载均衡,其是在客户端侧根据某种策略选择合适的服务实例直接进行请求,其典型代表有Ribbon

abstract.png

环境搭建

搭建服务提供者

我们在服务提供者payment中添加一个Controller,如下所示

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("pay")
public class PaymentController {

@GetMapping("/hello")
public String hello(@RequestParam String name) {
String msg = "[Payment Service-"+ serverPort +"]: " + name;
return msg;
}

}

这里使用Consul作为注册中心。创建三个payment服务的实例,分别使用8004、8005、8006端口。如下所示

figure 1.jpeg

搭建服务消费者

在服务消费者order中,我们同样也会引入spring-cloud-starter-consul-discovery依赖。由于该依赖间接依赖了spring-cloud-starter-netflix-ribbon,如下图所示。故无需显式引入spring-cloud-starter-netflix-ribbon依赖

figure 2.jpeg

在服务消费者order中,先声明一个restTemplate实例,然后通过Controller调用payment服务

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
27
28
29
30
31
32
33
34
35
36
37
38
@Configuration
public class RestTemplateConfig {

/**
* @LoadBalanced 注解作用:
* 1. 基于服务名调用的restTemplate实例
* 2. 支持负载均衡
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

}

...

@RestController
@RequestMapping("order")
public class OrderController2 {

// 使用 注册中心的服务名
public static final String PAYMENT_URL = "http://payment";

@Qualifier("restTemplate")
@Autowired
private RestTemplate restTemplate;

@GetMapping("/test2")
public String test2(@RequestParam String name) {
String msg = restTemplate.getForObject(PAYMENT_URL +"/pay/hello?name={1}", String.class, name);
String result = "[Order Service #test2]: " + msg;
return result;
}

}

创建order服务的实例,使用82端口。如下所示

figure 3.jpeg

本版本Ribbon默认采用ZoneAvoidanceRule区域敏感性策略,测试效果如下符合预期

figure 4.jpeg

负载均衡策略

Ribbon通过实现IRule接口内置了多种负载均衡策略

figure 5.jpeg

  • RoundRobinRule:随机策略。随机选择服务实例
  • RandomRule:轮询策略。依次选择服务实例
  • RetryRule:重试策略
  • BestAvailableRule:最低并发策略。选择正在请求的并发量最小的服务实例。如果服务实例处于熔断状态,则忽略该服务实例
  • AvailabilityFilteringRule:可用性敏感策略。过滤掉 处于熔断状态 或 正在请求的并发量高的服务实例
  • ZoneAvoidanceRule:区域敏感性策略。复合判断服务实例所在区域的性能和服务实例的可用性
  • WeightedResponseTimeRule:响应时间加权策略。根据各服务实例的响应时间计算权重,响应时间越长,权重越低,选择该服务实例的概率越低

策略配置

全局配置

在服务消费者order中,我们可以配置全局的负载均衡策略。这里以RoundRobinRule随机策略为例。首先通过Java配置类声明一个该策略的实例对象

1
2
3
4
5
6
7
8
9
@Configuration
public class RandomRuleConfig {

@Bean
public IRule randomRule() {
return new RandomRule();
}

}

需要注意的是,该Java配置类不能被@ComponentScan注解扫描到,否则该配置类就会被所有的Ribbon客户端所共享。换言之,Ribbon策略实例的Java配置类不要放在@ComponentScan注解所在的当前包及其子包下即可。如下所示,我们所有策略实例的Java配置类均放在ribbon包下,而SpringBoot启动类在order包下

figure 6.jpeg

通过@RibbonClients注解的defaultConfiguration属性来设置其全局的负载均衡策略,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
// 使用Consul作为注册中心时使用
@EnableDiscoveryClient
// 设置Ribbon全局负载均衡策略为随机策略
@RibbonClients(defaultConfiguration = RandomRuleConfig.class)
public class OrderApplication {

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

}

效果如下所示,符合预期

figure 7.jpeg

根据所调用的服务进行配置

可以通过@RibbonClient注解,对所调用的服务分别配置相应的策略。用法如下所示

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
// 使用Consul作为注册中心时使用
@EnableDiscoveryClient
// 调用payment服务时,采用随机策略
@RibbonClient(name ="payment", configuration = RandomRuleConfig.class)
public class OrderApplication {

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

}

对于多个配置,还可以通过@RibbonClients注解整合到一处

1
2
3
4
5
// 调用payment服务时,采用随机策略; 调用bill服务时,采用响应时间加权策略
@RibbonClients({
@RibbonClient(name = "payment",configuration = RandomRuleConfig.class),
@RibbonClient(name = "bill",configuration = WeightedResponseTimeRuleConfig.class)
})

基于配置文件的策略配置

事实上,不仅可以通过注解配置负载均衡策略。还可以直接通过配置文件进行配置,这样可以直接免去相应均衡策略实例的Java配置类。可通过 [调用的服务名].ribbon.NFLoadBalancerRuleClassName 配置项进行配置。示例如下所示

1
2
3
4
5
6
7
8
9
# 调用payment服务时,采用随机策略
payment:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

# 调用bill服务时,采用响应时间加权策略
bill:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

自定义负载均衡策略

通过查看IRule接口实现类,不难发现IRule接口是一个均衡策略接口。而具体的实现类则是通过抽象类AbstractLoadBalancerRule进行拓展的。所以,如果我们期望自定义均衡策略,可以直接继承实现AbstractLoadBalancerRule类即可,而不用从IRule接口进行拓展。如下即是我们实现的一个负载均衡策略,具体通过实现choose方法完成服务实例的选择。这里为了简便起见,我们的目标为总是使用某端口的服务实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.aaron.SpringCloud1.ribbon;

public class CustomRule extends AbstractLoadBalancerRule {

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}

@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//获取服务列表
List<Server> serverList = lb.getAllServers();

// 获取端口为8005的服务实例
Server target = serverList.stream()
.filter( server -> server.getPort()==8005 )
.findAny()
.orElse(null);

return target;
}

}

在使用上,其与Ribbon内置的均衡策略使用并无二致。要么通过 Java配置类+注解 的方式,要么通过配置文件的方式。当然对于前者而言,Java配置类不要放在@ComponentScan注解所在的当前包及其子包下

1
2
3
4
5
6
7
8
9
10
11
package com.aaron.SpringCloud1.ribbon;

@Configuration
public class CustomRuleConfig {

@Bean
public IRule customRule() {
return new CustomRule();
}

}

测试结果如下,符合预期

figure 8.jpeg

参考文献

  1. Spring微服务实战 John Carnell著
  2. 凤凰架构 周志明著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝