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

ProjectDaedalus

共 4935字,需浏览 10分钟

 ·

2021-11-09 06:47

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

abstract.png

环境搭建

搭建服务提供者

我们在服务提供者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端口。如下所示

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服务

@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.classname);
        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配置类声明一个该策略的实例对象

@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属性来设置其全局的负载均衡策略,如下所示

@SpringBootApplication
// 使用Consul作为注册中心时使用
@EnableDiscoveryClient 
// 设置Ribbon全局负载均衡策略为随机策略
@RibbonClients(defaultConfiguration = RandomRuleConfig.class)
public class OrderApplication 
{
 
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.classargs);
    }

}

效果如下所示,符合预期

figure 7.jpeg

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

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

@SpringBootApplication
// 使用Consul作为注册中心时使用
@EnableDiscoveryClient 
// 调用payment服务时,采用随机策略
@RibbonClient(name ="payment", configuration = RandomRuleConfig.class)
public class OrderApplication 
{
 
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.classargs);
    }

}

对于多个配置,还可以通过@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();
    }

}

测试结果如下,符合预期

figure 8.jpeg

参考文献

  1. Spring微服务实战 John Carnell著
  2. 凤凰架构 周志明著
浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报