负载均衡大体可以分为两类:集中式、进程内。前者也被称为服务端负载均衡,其一般位于服务集群的前端,统一接收、处理、转发客户端的请求。典型地包括F5硬件、LVS、Nginx等技术方案;而后者也被称为客户端负载均衡,其是在客户端侧根据某种策略选择合适的服务实例直接进行请求,其典型代表有Ribbon
环境搭建
搭建服务提供者
我们在服务提供者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端口。如下所示
搭建服务消费者
在服务消费者order中,我们同样也会引入spring-cloud-starter-consul-discovery依赖。由于该依赖间接依赖了spring-cloud-starter-netflix-ribbon,如下图所示。故无需显式引入spring-cloud-starter-netflix-ribbon依赖
在服务消费者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 {
@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配置类声明一个该策略的实例对象
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包下
通过@RibbonClients注解的defaultConfiguration属性来设置其全局的负载均衡策略,如下所示
1 2 3 4 5 6 7 8 9 10 11 12
| @SpringBootApplication
@EnableDiscoveryClient
@RibbonClients(defaultConfiguration = RandomRuleConfig.class) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); }
}
|
效果如下所示,符合预期
根据所调用的服务进行配置
可以通过@RibbonClient注解,对所调用的服务分别配置相应的策略。用法如下所示
1 2 3 4 5 6 7 8 9 10 11 12
| @SpringBootApplication
@EnableDiscoveryClient
@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
| @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: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
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();
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(); }
}
|
测试结果如下,符合预期
参考文献
- Spring微服务实战 John Carnell著
- 凤凰架构 周志明著