微服务架构 | 4.1 基于 Ribbon 的负载均衡详解

黄沙百战穿金甲,不破楼兰终不还。这篇文章主要讲述微服务架构 | 4.1 基于 Ribbon 的负载均衡详解相关的知识,希望能为你提供帮助。
【微服务架构 | 4.1 基于 Ribbon 的负载均衡详解】@[TOC](4.1 基于 Ribbon 的负载均衡详解)
前言参考资料:
《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在工作时分成两步
微服务架构 | 4.1 基于 Ribbon 的负载均衡详解

文章图片

  • 第一步先选择 EurekaServer,它优先选择在同一个区域内负载较少的 server;
    第二步再根据用户指定的策略,在从 server 取到的服务注册列表中选择一个地址;
1.4 服务的提供者与消费者
  • 提供者:服务提供者将自己注册进注册中心,让消费者发现;在本例中有多个提供者给消费者提供服务;
  • 消费者:消费者使用服务发现,找到提供者并调用提供者服务;在本例中只有一个消费者在多个提供者中选出一个为自己服务;
1.5 Ribbon 核心组件 IRule
微服务架构 | 4.1 基于 Ribbon 的负载均衡详解

文章图片

  • 根据特定算法中从服务列表中选取一个要访问的服务;
  • 定义了负载均衡的方式;
  • 有以下几种负载均衡的实现方式:
    • RoundRobinRule:轮询;
    • RandomRule:随机;
    • RetryRule:先按照 RoundRobinRule 的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务;
    • WeightedResponseTimeRule:对 RoundRobinRule 的扩展,响应速度越快的实例选择权重越大,越容易被选择;
    • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
    • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
    • ZoneAvoidanceRule:默认规则,复合判断 server 所在区域的性能和 server 的可用性选择服务器;
2. 服务消费者获取提供者的三个层次示例 2.1 引入 pom.xml 依赖
< dependency> < groupId> org.springframework.cloud< /groupId> < artifactId> spring-cloud-starter-netflix-ribbon< /artifactId> < /dependency>

  • 如果使用 Eureka 作为注册中心,则不用引入该依赖,因为 Eureka 的依赖里包含 ribbon 相关依赖 jar 包;
2.2 使用 Spring DiscoveryClient 查找服务实例 2.2.1 在主程序类上添加注解
  • @EnableDiscoveryClient:表明可以被注册中心发现。是 Sring Cloud 的触发器,其作用是使应用程序能够使用 DiscoveryClient 和 Ribbon 库;
2.2.2 使用 DiscoveryClient 查找信息
@Component public class ProviderDiscoveryClient //自动注入 DiscoveryClient 类,该类用于与 Ribbon 交互 @Autowired private DiscoveryClient discoveryClient; public Provide getProvide(String providerId) RestTemplate restTemplate = new RestTemplate(); //获取服务提供者的所有实例列表,ServiceInstance 用于保存关于服务的特定实例(包括主机名、端口荷 URL) List< ServiceInstance> instances = 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 功能的 SpringRestTemplate 调用服务 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 来调用服务
@Component public class ProviderRestTemplateClient //自动注入即可,不用实例化 @Autowired RestTemplate restTemplate; public Provider getProvider(String providerId) ResponseEntity< Provider> restExchange = 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 客户端调用服务2.4.1 在主程序类上添加注解
@EnableFeignClients:表示启用 Feign 客户端;
2.4.2 定义用于调用服务提供者的 Feign 接口
@FeignClient("provider-instance-name") //标识服务为 feign 的客户端 public interface ProviderFeignClient //定义端点的路径和动作 @RequestMapping( method= RequestMethod.GET, value="https://www.songbingjia.com/providers/providerId", consumes="application/json") //定义传入端点的参数,该方法可以由客户端调用以触发组织服务 Provider getProvider(@PathVariable("providerId") String providerId);

  • 要是用 ProviderFeignClient 类,开发人员只需要自动装配并使用它即可;
3. 通过 java 配置类自定义负载均衡算法示例(消费者服务)3.1 编写配置类
  • 注意:自定义配置类不能放在 @ComponentScan 所扫描的当前包下以及子包下,否则自定义的配置类会被所有的Ribbon客户端所共享,达不到自定义的目的;
  • @ComponentScan 注解被封装到主启动类上的 @SpringBootApplication 注解。其默认扫描主启动类所在包及其子包,因此我们要返回上一级目录新建一个 myRule 目录存放我们自定义的负载均衡配置类;
微服务架构 | 4.1 基于 Ribbon 的负载均衡详解

文章图片

@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. 通过配置自定义负载均衡算法示例(消费者服务)
#服务提供者的实例名称 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(List< ServiceInstance> serviceInstances);

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(List< ServiceInstance> serviceInstances) 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 = "https://www.songbingjia.com/provider/mylb") public String getProviderLB() //获取服务提供者的所有实例列表 List< ServiceInstance> instances = 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);

最后::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center
微服务架构 | 4.1 基于 Ribbon 的负载均衡详解

文章图片

:::

    推荐阅读