一、概述

负载均衡是分布式架构的重点,负载均衡机制决定着整个服务集群的性能与稳定。

负载均衡,英文名称为 Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行

负载均衡解决的是将一个客户端的流量以某种符合最大化资源利用率的方式均摊到服务端所提供的所有实例上的问题。在这个问题的场景中,后端服务的实例是通过水平扩展的方式来提供高可用的。

1、负载均衡分类

在微服务架构中,负载均衡是必须使用的技术,通过它来实现系统的高可用、集群扩容等功能。负载均衡可以分为两种:服务端负载均衡和客户端负载均衡。

(1)服务器负载均衡

通常所说的负载均衡指服务器负载均衡,由服务端来决定调用哪个节点,可通过硬件设备或软件来实现,硬件比如:F5、Array 等,软件比如:LVS、Nginx等,类似的架构图如下:

java负载均衡的三种方式是 负载均衡springcloud_客户端

通过硬件或软件实现负载均衡均会维护一个服务端清单,利用心跳检测等手段进行清单维护,保证清单中都是可以正常访问的服务节点。当用户发送请求时,会先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法(轮训、随机、加权轮训)从可用的服务端列表中取出一台服务端的地址,接着进行转发,降低系统的压力

(2)客户端负载均衡

客户端实际上是指服务调用者。在 SpringCloud 中调用者本身集成负载均衡,由调用者决定来调用哪个节点的服务,这就是客户端负载均衡。

SpringCloud Ribbon 是基于客户端的负载均衡工具,它可以将面向服务的 REST 模板请求自动转换成客户端负载均衡的服务调用。

java负载均衡的三种方式是 负载均衡springcloud_负载均衡_02

Ribbon 维护了一个服务列表,如果服务实例注销或死掉,Ribbon 能够自行将其剔除。Ribbon 提供了客户端负载均衡的功能,Ribbon 利用从注册中心中读取到的服务信息列表(存储在本地即客户端中),在调用某个服务时,根据负载均衡算法直接请求到具体的微服务实例,常用的负载均衡算法有: 轮循、随机、加权轮循、加权随机、地址哈希等方法。如下图是Ribbon负载均衡的流程图:

java负载均衡的三种方式是 负载均衡springcloud_负载均衡_03

(3)客户端和服务端负载均衡的区别

两者主要区分点在于服务清单的存放位置:在客户端负载均衡中,客户端会存储一份服务端清单,它是通过从注册中心进行抓取得到的,同时也需要对此进行维护;而在服务端负载均衡中,服务端自己会维护一份服务端清单

二、Ribbon 简介

Spring Cloud Ribbon是一个基于 HTTP 和 TCP 协议客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。

1、Ribbon 组成

java负载均衡的三种方式是 负载均衡springcloud_负载均衡_04

2、Ribbon 负载均衡的策略(IRule 接口的实现)

java负载均衡的三种方式是 负载均衡springcloud_客户端_05

负载均衡常用策略方式:默认为轮训的策略方式、随机、重试(如果获取服务失败,在指定时间内进行重试,获取可用服务)、过滤掉出故障的服务,选择一个最小并发量的服务

3、Ribbon 负载均衡配置方式

java负载均衡的三种方式是 负载均衡springcloud_客户端_06


(1)代码配置

1)在服务消费者下面创建 RibbonRule.java 来设置负载均衡策略

java负载均衡的三种方式是 负载均衡springcloud_微服务_07


2)在配置类中

将 IRule 接口的实例注入 IOC

代码如下

import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RibbonRule { @Bean public IRule ribbonRule(){ // 设置为随机方式 return new RandomRule(); } }

3)然后在消费者启动类上通过注解 @RibbonClient 完成对服务设置负载均衡策略

@SpringBootApplication @EnableDiscoveryClient@RibbonClient(name = "alibaba-server-hellocloud", configuration = RibbonRule.class) // 设置负载均衡策略 public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }

4)可以使用 @RibbonClients 针对多个服务名进行策略的指定

@SpringBootApplication @EnableDiscoveryClient@RibbonClients(value = { @RibbonClient(name = "alibaba-server-hellocloud",configuration = MyConfiguration.class), @RibbonClient(name = "alibaba-server-helloouc",configuration = MyConfiguration.class) }) public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }

(2)配置文件配置

alibaba-server-hellocloud: # 服务名1 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule alibaba-server-helloouc: # 服务名2 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

配置文件通过 服务名.ribbon.NFLoadBalancerRuleClassName 来进行配置,每一个只能对一个服务名进行配置,如果有多个服务名就要配置多个

二、服务名多实例的类型

在spring cloud微服务架构中,基本上每个拆分的微服务都会部署多个运行实例。每个服务多个实例,能够提高吞吐量和可用性,使得流量均匀分布,防止单台瓶颈,也避免单点故障。

Ribbon 维护了一个服务列表,服务列表以服务的应用名为区分当调用某个服务名的服务时,Ribbon 会根据负载均衡算法直接请求到该服务名下某个具体的微服务实例

因此负载均衡适用的同一微服务名下的微服务实例有两种类型:

1、不同工程具有同一服务名

java负载均衡的三种方式是 负载均衡springcloud_java负载均衡的三种方式是_08


上述两个不同的工程,因为具有相同的服务名 alibaba-server-hello,因此是属于同一服务名下的不同实例


除了应用名一样外,

被调用的服务名对应的各个工程实例的同一接口的访问地址应该也要相同


服务名在配置文件 application.yml 或 boostrap.yml 中声明。

(1)alibaba-server-hellocloud 服务配置:

java负载均衡的三种方式是 负载均衡springcloud_客户端_09


(2)alibaba-server-helloworld 服务配置:


java负载均衡的三种方式是 负载均衡springcloud_java负载均衡的三种方式是_10

2、同一工程部署在不同端口上

将某个工程部署在不同端口上,这种情况下不同服务实例除了端口不同外其他的配置都相同,因此具有相同的服务名,属于同一服务名下的不同实例
同一工程部署不同实例的做法如下:

java负载均衡的三种方式是 负载均衡springcloud_客户端_11

给 alibaba-server-helloworld 工程添加第2个实例的流程如下:

(1)alibaba-server-helloworld 服务的配置文件中不能设置端口号

服务配置:

java负载均衡的三种方式是 负载均衡springcloud_微服务_12


因为 alibaba-server-helloworld 服务的每个实例只有端口号不同,所以

在配置文件中不能设置端口号

,否则每个实例的端口号将会相同,因而不能创建部署于不同端口的多实例。(配置文件的端口号设置优先于在 VM Options 中通过 -Dserver.port = xxxx 设置端口号)

(2)打开Edit Configurations

java负载均衡的三种方式是 负载均衡springcloud_负载均衡_13


(3)点击“ + ”号,选择Spring Boot,添加实例

java负载均衡的三种方式是 负载均衡springcloud_客户端_14


(4)填写实例名称、实例的主类以及实例的端口号

主类选择 alibaba-server-helloworld 服务的主类 WorldApplication;
在 VM options 中设置实例的端口号 -Dserver.port = 8005

java负载均衡的三种方式是 负载均衡springcloud_负载均衡_15

(5)点击 Apply 后,新的实例即可创建完成

能够看到 alibaba-server-helloworld 服务有两个分别运行在 8005 和 8006 端口的实例

java负载均衡的三种方式是 负载均衡springcloud_微服务_16

(6)查看 Nacos 服务列表

能够看到在服务列表中 alibaba-server-helloworld 服务有两个实例

java负载均衡的三种方式是 负载均衡springcloud_微服务_17

3、设置 Nacos 服务实例的权重比例

若负载均衡采用按服务实例的权重进行选择调用时,就必须为服务实例设置权重。Ribbon 本身是没有权重的概念的,那么如何才能实现代用权重的负载均衡呢?我们在 nacos 中的服务器的集群有一个权重的概念,当给服务器设置了权重,那么流量就可以根据权重比例分配到服务器上
假如在微服务 alibaba-server-hellocloud 下有两个服务实例,为其分别设置权重 0.3 和 0.7。

(1)查看详情

java负载均衡的三种方式是 负载均衡springcloud_java负载均衡的三种方式是_18

(2)编辑实例,修改权重


java负载均衡的三种方式是 负载均衡springcloud_微服务_19

(3)设置权重分别为 0.7 和 0.3


java负载均衡的三种方式是 负载均衡springcloud_客户端_20

经过如上设置,则如果有 10 次请求,基本上都是会打到 8005 端口上的,因为 8005 的权重最高,打到它的接口数量应该是最多的。

三、客户端负载均衡

1、RestTemplate + @LoadBalanced

(1)在 RestTemplate 配置类里面在注入 RestTemplate 的方法上面添加注解 @LoadBalanced 即可实现负载均衡调用

使用 RestTemplate 来实现服务之间的调用,RestTemplate 只需要加上 @LoadBalanced 注解后,就既可以用服务名称来调用接口了,也能实现 Ribbon 的负载均衡策略
@LoadBalanced 原理:

RestTemplate 在发送请求的时候会被 ClientHttpRequestInterceptor 拦截,LoadBalancerInterceptor 是 ClientHttpRequestInterceptor 的实现类,它的作用就是用于 RestTemplate 的负载均衡,LoadBalancerInterceptor 将负载均衡的核心逻辑交给了 loadBalancer。
@LoadBalanced 注解是属于Spring,而不是 Ribbon 的,Spring 在初始化容器的时候,如果检测到 Bean 被 @LoadBalanced 注解,Spring 会为其设置 LoadBalancerInterceptor 的拦截器。

@Component 
public class RestTemplateConfig {
    @Bean
    @LoadBalanced  // 负载均衡注解
    public RestTemplate restTemplate(){
        return new RestTemplate();
   } 
  }

(2)控制器使用负载均衡

@RestController

public class ConsumerController {
    @Autowired
    private LoadBalancerClient loadBalancerClient; // 负载均衡客户端,能够获取服务实例信息
    @Autowired
    private RestTemplate restTemplate; // 注入 RestTemplate 

    @GetMapping("/diaoyong")
    public  ResponseEntity<String> msg(){
        
        // 1.第一种方式(利用 loadBalancerClient 通过服务名获取所有服务实例,然后再使用 restTemplate)
        ServiceInstance serviceInstance = loadBalancerClient.choose("alibaba-server-helloworld");
        String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())+"/hello/lhj/hello";
        // 不可以使用注入的 restTemplate,必须使用 new 的 restTemplate,
        // 因为 restTemplate 的配置类使用负载均衡,通过 loadBalancerClient.choose 选择的服务实例已经确定,就无法负载均衡
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);

        // 2.第二种方式(利用 @LoadBalanced,可在 restTemplate 里使用应用名字)
        // 可以使用注入的 restTemplate,因为通过服务名获得的实例可以有多个,因此可以负载均衡
        String response = restTemplate.getForObject("http://alibaba-server-helloworld/hello/lhj/hello", String.class);
        log.info("response={}",response);
        return response;
        
        System.out.println(response.getStatusCode());
        System.out.println(response.getBody());
        System.out.println(response.getHeaders());
        return response;
    }
}

第一种方式我们可以自定义负载均衡策略
第二种方式使用默认负载均衡策略。

2、Ribbon

(1)不需要单独引入 Ribbon 依赖

注:我们这里不需要添加 Ribbon 的依赖,因为 Nacos Client、Eureka Client 等包里面帮我们引入 Ribbon 相关的依赖

1)nacos Client:

java负载均衡的三种方式是 负载均衡springcloud_java负载均衡的三种方式是_21


2)Eureka Client


java负载均衡的三种方式是 负载均衡springcloud_客户端_22

(2)设置 Ribbon 的负载均衡策略

1)在服务消费者下面创建 RibbonRule 类来设置负载均衡策略

java负载均衡的三种方式是 负载均衡springcloud_java负载均衡的三种方式是_23

注:注意这个类的放置的位置是有规则的,它不能放在 @ComponentScan 或者是 @SpringBootApplication 扫描的包里,否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的了。@SpringBootApplication 启动类上的注解,他扫描的的是启动类所在的包已经它的子包,也就是说不要将 Ribbon 配置类与主启动类同包,所以我们必须在启动类所在包的外面新建一个包去放该类

2)在配置类中自定义负载均衡策略,代码如下

import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyRibbonRule { @Bean public IRule ribbonRule(){ // 此处将ribbon默认使用的轮询策略改为轮询策略 return new RoundRobinRule(); } }

(3)在消费者启动类上通过注解 @RibbonClient 完成对服务负载均衡策略配置

@SpringBootApplication @EnableDiscoveryClient@RibbonClient(name = "alibaba-server-hellocloud", configuration = MyRibbonRule.class) // 设置负载均衡策略 public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }

(4)controller 中多次调用 alibaba-server-helloworld 服务

此处 Ribbon 的使用有两种方式:
1)Ribbon + RestTemplate + @LoadBalanced

Resttemplate 配置类中使用负载均衡注解 @LoadBalanced

@configurationpublic class RestTemplateConfig { @Bean @LoadBalanced // 负载均衡注解 public RestTemplate restTemplate(){ return new RestTemplate(); } }

Contorller 中使用:

@RestController public class ConsumerController { @Autowired private RestTemplate restTemplate; @GetMapping("/diaoyong") public String msg(){ // 在调用的时候负载均衡选择实例去调用 String response = restTemplate.getForObject("http://alibaba-server-helloworld/hello/lhj/hello", String.class); }

2)Ribbon + LoadBalancerClient

Resttemplate 配置类(不能添加 @LoadBalanced)

@configurationpublic class RestTemplateConfig { // 因为负载均衡由 loadBalancerClient 客户端完成,所以不需要加 @LoadBalanced 注解 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }

Contorller 中使用:

@RestController public class ConsumerController { @Autowired private RestTemplate restTemplate; @Autowired LoadBalancerClient loadBalancerClient; @GetMapping("/diaoyong") public String msg(){ // 先通过 loadBalancerClient 负载均衡客户端获得微服务实例,再调用 ServiceInstance serviceInstance = loadBalancerClient.choose("cloud-payment-service"); // 获得实例的主机和端口号 String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()); String response = restTemplate.getForObject(url + "/hello/lhj/hello", String.class); }

(6)调用 4 次 alibaba-server-helloworld 服务查看效果

可以发现两个实例分别调用两次,而且是轮流调用,也就是说 Ribbon 设置的轮循策略生效。

java负载均衡的三种方式是 负载均衡springcloud_客户端_24


java负载均衡的三种方式是 负载均衡springcloud_java负载均衡的三种方式是_25

2、Feign 与 OpenFeign

在使用 Ribbon+RestTemplate 时,利用 RestTemplate 对 http 请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用 Spring cloud Ribbon 时,自动封装服务调用客户端的开发量。

Feign 集成了 Ribbon

Feign 集成了 Ribbon,并且通过轮询实现了客户端的负载均衡。而与 Ribbon 不同的是,通过 feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

Feign 和 OpenFeign 两者区别

Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端 Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用 Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
OpenFeign 是 Spring Cloud 在 Feign 的基础上支持了 SpringMVC 的注解,如 @RequesMapping 等等。OpenFeign 的 @Feignclient 可以解析 SpringMVc 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。