Article / 文章中心

微服务4.1 基于 Ribbon 负载均衡详解

发布时间:2022-01-26 点击数:483

前言

参考资料
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 结构开发教程 周阳》

Spring Cloud Ribbon 是根据 Netflix Ribbon 完成的一套客户端负载均衡的工具;供给客户端的软件负载均衡算法和服务调用;


1. Ribbon 基础常识

1.1 Ribbon 是什么

  • Spring Cloud Ribbon 是根据 Netflix Ribbon 完成的一套客户端负载均衡的工具;供给客户端的软件负载均衡算法和服务调用;
  • Ribbon 客户端组件供给一系列完善的装备项如衔接超时,重试等。简略的说,就是在装备文件中列出 Load Balancer(简称LB)后面一切的机器,Ribbon 会主动根据某种规则(如简略轮询,随机衔接等)去衔接这些机器;
  • 能够很容易运用Ribbon完成自界说的负载均衡算法;

1.2 与 Ribbon 交互的三种等级层次

  • Spring DiscoveryClient:供给了对 Ribbon 和 Ribbon 中缓存的注册服务的最低层次拜访;
  • 启用了 RestTemplate 的 Spring DiscoveryClient
  • Netflix Feign 客户端

1.3 Ribbon在作业时分成两步

Ribbon 架构图

  • 第一步先挑选 EurekaServer,它优先挑选在同一个区域内负载较少的 server;

第二步再根据用户指定的战略,在从 server 取到的服务注册列表中挑选一个地址;

1.4 服务的供给者与顾客

  • 供给者:服务供给者将自己注册进注册中心,让顾客发现;在本例中有多个供给者给顾客供给服务;
  • 顾客:顾客运用服务发现,找到供给者并调用供给者服务;在本例中只要一个顾客在多个供给者中选出一个为自己服务;

1.5 Ribbon 中心组件 IRule

IRule 负载均衡

  • 根据特定算法中从服务列表中选取一个要拜访的服务;
  • 界说了负载均衡的方式;
  • 有以下几种负载均衡的完成方式:

    • RoundRobinRule:轮询;
    • RandomRule:随机;
    • RetryRule:先依照 RoundRobinRule 的战略获取服务,假如获取服务失利则在指定时间内会进行重试,获取可用的服务;
    • WeightedResponseTimeRule:对 RoundRobinRule 的扩展,响应速度越快的实例挑选权重越大,越容易被挑选;
    • BestAvailableRule:会先过滤掉由于多次拜访毛病而处于断路器跳闸状态的服务,然后挑选一个并发量最小的服务;
    • AvailabilityFilteringRule:先过滤掉毛病实例,再挑选并发较小的实例;
    • ZoneAvoidanceRule:默许规则,复合判别 server 所在区域的性能和 server 的可用性挑选服务器;


2. 服务顾客获取供给者的三个层次示例

2.1 引进 pom.xml 依靠

<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-ribbonartifactId> dependency>
  • 假如运用 Eureka 作为注册中心,则不必引进该依靠,因为 Eureka 的依靠里包含 ribbon 相关依靠 jar 包;

2.2 运用 Spring DiscoveryClient 查找服务实例

2.2.1 在主程序类上增加注解

  • @EnableDiscoveryClient:表明能够被注册中心发现。是 Sring Cloud 的触发器,其作用是使应用程序能够运用 DiscoveryClient 和 Ribbon 库;

2.2.2 运用 DiscoveryClient 查找信息

在服务顾客的 client 包下;
@Component public class ProviderDiscoveryClient { //主动注入 DiscoveryClient 类,该类用于与 Ribbon 交互 @Autowired private DiscoveryClient discoveryClient; public Provide getProvide(String providerId) {
        RestTemplate restTemplate = new RestTemplate(); //获取服务供给者的一切实例列表,ServiceInstance 用于保存关于服务的特定实例(包含主机名、端口荷 URL) Listinstances = discoveryClient.getInstances("provider-instance-name"); if (instances.size()==0) return null; //检索要调用的服务端点 String serviceUri = String.format("%s/providers/%s",instances.get(0).getUri().toString(), providerId); //运用标准的 Spring REST 模板类去调用服务 ResponseEntity< provider > restExchange =
                restTemplate.exchange(
                        serviceUri,
                        HttpMethod.GET, null, Provider.class, providerId); return restExchange.getBody();
    }
}
  • 这种方法存在以下问题:

    • 没有利用 Ribbon 的客户端负载均衡,调用哪个服务实例需要开发人员界说;
    • 开发人员有必要构建一个用来调用服务的 URL;
    • 实例化 ResTemplate 类,不符合 Spring IoC 标准;
  • 结合本篇《5. 本地负载均衡器的完成(顾客)》即可用到客户端负载均衡,即:开发人员界说了本地负载均衡器来完成了负载均衡;

2.3 运用带有 Ribbon 功用的 Spring RestTemplate 调用服务

2.3.1 在主程序类上增加注解

@SpringBootApplication //只需要这个注解即可 public class Application { @LoadBalanced //告诉 Spring Cloud 创建一个支撑 Ribbon 的 RestTemplate @Bean public RestTemplate getRestTemplate(){ return new RestTemplate();
  } public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }
}
  • Spring Cloud 前期版本中,RestTemplate 类默许主动支撑 Ribbon;
  • 自从 Spring Cloud 发布 Angel 版本后,Spring Cloud 中的 RestTemplate 不再支撑 Ribbon;
  • 因而,后续版本有必要运用 @LoadBalanced 注解显式标示,才能将 Ribbon 和 RestTemplate 一同运用;
  • *RestTemplate 不一定放在主程序类里;也能够在 config 包下新建一个 ApplicationContextConfig 装备类,将 RestTemplate 放在该类里:
@Configuration public class ApplicationContextConfig{ @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate();
    }
}

2.3.2 运用 Ribbon 的 RestTemplate 来调用服务

在服务顾客的 client 包下;
@Component public class ProviderRestTemplateClient { //主动注入即可,不必实例化 @Autowired RestTemplate restTemplate; public Provider getProvider(String providerId){
        ResponseEntityrestExchange =
                restTemplate.exchange( //运用 Eureka 服务 ID 来构建目标 URL "http://provider-instance-name/providers/{providerId}",
                        HttpMethod.GET, null, Provider.class, providerId); return restExchange.getBody();
    }
}
  • 通过运用 RestTemplate 类,Ribbon 将在一切服务实例之间轮询负载均衡一切恳求;

2.4 运用 Netflix Feign 客户端调用服务

Feign 相关常识将在下篇《4.2 根据 Feign 与 OpenFeign 的服务接口调用》中说明,这儿仅把要点放在与上述两种调用供给者服务的区别与比照;

2.4.1 在主程序类上增加注解

@EnableFeignClients:表明启用 Feign 客户端;

2.4.2 界说用于调用服务供给者的 Feign 接口

@FeignClient("provider-instance-name") //标识服务为 feign 的客户端 public interface ProviderFeignClient { //界说端点的路径和动作 @RequestMapping( 
            method= RequestMethod.GET,
            value="/providers/{providerId}",
            consumes="application/json") //界说传入端点的参数,该方法能够由客户端调用以触发组织服务  Provider getProvider(@PathVariable("providerId") String providerId);
}
  • 要是用 ProviderFeignClient 类,开发人员只需要主动装配并运用它即可;


3. 通过 java 装备类自界说负载均衡算法示例(顾客服务)

指切换默许的负载均衡算法,切换后的仍为现成的(与本地负载均衡器有所区别,本地负载均衡器要自己完成);

3.1 编写装备类

  • 留意:自界说装备类不能放在 @ComponentScan 所扫描的当时包下以及子包下,否则自界说的装备类会被一切的Ribbon客户端所共享,达不到自界说的意图;
  • @ComponentScan 注解被封装到主发动类上的 @SpringBootApplication 注解。其默许扫描主发动类所在包及其子包,因而咱们要回来上一级目录新建一个 myRule 目录寄存咱们自界说的负载均衡装备类;

自界说负载均衡算法的文件目录结构

@Configuration public class MySelfRule { @Bean public IRule myRule(){ return new RandomRule();//界说为随机 }
}

3.2 主发动类上增加注解

  • @RibbonClient(name = "provider-instance-name" ,configuration=MySelfRule.class):表明运用自界说负载均衡算法;

    • name:指定服务供给者的实例名称;
    • configuration:指定需要运用哪个装备类的负载均衡;
    • 表明 provider 服务运用 MySelfRule 对应的 Ribbon 装备;
  • 相同,需要对 RestTemplate 类用 @LoadBalanced 注解显现声明;


4. 通过装备自界说负载均衡算法示例(顾客服务)

指切换默许的负载均衡算法,切换后的仍为现成的(与本地负载均衡器有所区别,本地负载均衡器要自己完成);

4.1 修正 bootstrap.yml 装备文件

  • 上述 java 装备类的效果等价于下面这样的装备文件:
#服务供给者的实例名称 provider-instance-name: ribbon: #代表 Ribbon 运用的负载均衡战略,属性的值为:IRule 的完成类 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #其他可用的装备属性 # NFLoadBalancerClassName : 装备 ILoadBalancer 的完成类 # NFLoadBalancerPingClassName : 装备 IPing 的完成类 # NIWSServerListClassName: 装备 ServerList 的完成类 # NIWSServerListFilterClassName: 装备 ServerListtFilter 的完成类

4.2 主程序类

  • 不需要 @RibbonClient 注解;
  • 相同,需要对 RestTemplate 类用 @LoadBalanced 注解显现声明;


5. 本地负载均衡器的完成(顾客)

本地负载均衡器不同于自界说负载均衡算法;前者的负载均衡算法需要自己手动完成,后者仅仅切换成另一种现成的负载均衡算法;

5.1 不运用 RestTemplate

  • 即主程序类不需要对 RestTemplate 类用 @LoadBalanced 注解显现声明;
  • 能够删去也能够注释 @LoadBalanced 注解;

5.2 界说负载均衡接口

能够新建一个包,专门寄存咱们自己写的负载均衡算法;
public interface LoadBalancer{ ServiceInstance instances(ListserviceInstances);
}

5.3 完成负载均衡接口

  • 能够根据事务要求写不同的负载均衡算法,这儿仅供给一种示例;
  • 该示例完成了一种较为简略的原子性的负载均衡算法;
@Component public class MyLB implements LoadBalancer{ private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement(){ int current; int next; do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        }while(!this.atomicInteger.compareAndSet(current,next));
        System.out.println("*****第几次拜访,次数next: "+next); return next;
    } //负载均衡算法:rest接口第几次恳求数 % 服务器集群总数量 = 实际调用服务器方位下标  ,每次服务重发动后rest接口计数从1开始。 @Override public ServiceInstance instances(ListserviceInstances){ int index = getAndIncrement() % serviceInstances.size(); return serviceInstances.get(index);
    }
}

5.4 在 controller 接口中运用本地负载均衡器

  • 类似于本篇《2.2 运用 Spring DiscoveryClient 查找服务实例》;
  • 不同之处在于 2.2 没有负载均衡功用,这儿在 2.2 的基础上,开发人员自己界说了本地负载均衡器,不运用 Ribbon 供给的负载均衡,故《5.1 不运用 RestTemplate》中说到的不必对 RestTemplate 类运用 @LoadBalanced 注解显现声明
@RestController public class OrderController{ //服务供给者示例的名字 public static final String PAYMENT_URL = "http://provider-instance-name"; @Resource private RestTemplate restTemplate; @Resource private LoadBalancer loadBalancer; @Resource private DiscoveryClient discoveryClient; @GetMapping(value = "/provider/mylb") public String getProviderLB(){ //获取服务供给者的一切实例列表 Listinstances = discoveryClient.getInstances("provider-instance-name"); if(instances == null || instances.size() <= 0){ return null;
        } //运用本地负载均衡器选出供给者服务 ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri+"/provider/mylb",String.class);
    }
}