SpringCloud下基于Ribbon的负载均衡实践
负载均衡大体可以分为两类:集中式、进程内。前者也被称为服务端负载均衡,其一般位于服务集群的前端,统一接收、处理、转发客户端的请求。典型地包括F5硬件、LVS、Nginx等技术方案;而后者也被称为客户端负载均衡,其是在客户端侧根据某种策略选择合适的服务实例直接进行请求,其典型代表有Ribbon
环境搭建
搭建服务提供者
我们在服务提供者payment中添加一个Controller,如下所示
@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端口。如下所示
搭建服务消费者
在服务消费者order中,我们同样也会引入spring-cloud-starter-consul-discovery依赖。由于该依赖间接依赖了spring-cloud-starter-netflix-ribbon,如下图所示。故无需显式引入spring-cloud-starter-netflix-ribbon依赖
在服务消费者order中,先声明一个restTemplate实例,然后通过Controller调用payment服务
@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端口。如下所示
本版本Ribbon默认采用ZoneAvoidanceRule区域敏感性策略,测试效果如下符合预期
负载均衡策略
Ribbon通过实现IRule接口内置了多种负载均衡策略
RoundRobinRule:随机策略。随机选择服务实例 RandomRule:轮询策略。依次选择服务实例 RetryRule:重试策略 BestAvailableRule:最低并发策略。选择正在请求的并发量最小的服务实例。如果服务实例处于熔断状态,则忽略该服务实例 AvailabilityFilteringRule:可用性敏感策略。过滤掉 处于熔断状态 或 正在请求的并发量高的服务实例 ZoneAvoidanceRule:区域敏感性策略。复合判断服务实例所在区域的性能和服务实例的可用性 WeightedResponseTimeRule:响应时间加权策略。根据各服务实例的响应时间计算权重,响应时间越长,权重越低,选择该服务实例的概率越低
策略配置
全局配置
在服务消费者order中,我们可以配置全局的负载均衡策略。这里以RoundRobinRule随机策略为例。首先通过Java配置类声明一个该策略的实例对象
@Configuration
public class RandomRuleConfig {
@Bean
public IRule randomRule() {
return new RandomRule();
}
}
需要注意的是,该Java配置类不能被@ComponentScan注解扫描到,否则该配置类就会被所有的Ribbon客户端所共享。换言之,Ribbon策略实例的Java配置类不要放在@ComponentScan注解所在的当前包及其子包下即可。如下所示,我们所有策略实例的Java配置类均放在ribbon包下,而SpringBoot启动类在order包下
通过@RibbonClients注解的defaultConfiguration属性来设置其全局的负载均衡策略,如下所示
@SpringBootApplication
// 使用Consul作为注册中心时使用
@EnableDiscoveryClient
// 设置Ribbon全局负载均衡策略为随机策略
@RibbonClients(defaultConfiguration = RandomRuleConfig.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
效果如下所示,符合预期
根据所调用的服务进行配置
可以通过@RibbonClient注解,对所调用的服务分别配置相应的策略。用法如下所示
@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注解整合到一处
// 调用payment服务时,采用随机策略; 调用bill服务时,采用响应时间加权策略
@RibbonClients({
@RibbonClient(name = "payment",configuration = RandomRuleConfig.class),
@RibbonClient(name = "bill",configuration = WeightedResponseTimeRuleConfig.class)
})
基于配置文件的策略配置
事实上,不仅可以通过注解配置负载均衡策略。还可以直接通过配置文件进行配置,这样可以直接免去相应均衡策略实例的Java配置类。可通过 [调用的服务名].ribbon.NFLoadBalancerRuleClassName 配置项进行配置。示例如下所示
# 调用payment服务时,采用随机策略
payment:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# 调用bill服务时,采用响应时间加权策略
bill:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
自定义负载均衡策略
通过查看IRule接口实现类,不难发现IRule接口是一个均衡策略接口。而具体的实现类则是通过抽象类AbstractLoadBalancerRule进行拓展的。所以,如果我们期望自定义均衡策略,可以直接继承实现AbstractLoadBalancerRule类即可,而不用从IRule接口进行拓展。如下即是我们实现的一个负载均衡策略,具体通过实现choose方法完成服务实例的选择。这里为了简便起见,我们的目标为总是使用某端口的服务实例
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 serverList = lb.getAllServers();
// 获取端口为8005的服务实例
Server target = serverList.stream()
.filter( server -> server.getPort()==8005 )
.findAny()
.orElse(null);
return target;
}
}
在使用上,其与Ribbon内置的均衡策略使用并无二致。要么通过 Java配置类+注解 的方式,要么通过配置文件的方式。当然对于前者而言,Java配置类不要放在@ComponentScan注解所在的当前包及其子包下
package com.aaron.SpringCloud1.ribbon;
@Configuration
public class CustomRuleConfig {
@Bean
public IRule customRule() {
return new CustomRule();
}
}
测试结果如下,符合预期
参考文献
Spring微服务实战 John Carnell著 凤凰架构 周志明著