【SpringCloud】负载均衡 (Ribbon & Feign)【精讲】

本文详细介绍了SpringCloud中负载均衡的概念,对比了Ribbon和nginx的区别,并通过实例展示了如何使用Ribbon实现服务消费者对多个服务提供者的负载均衡。接着,介绍了Feign在负载均衡中的简化使用。此外,文章还探讨了多种负载均衡策略,包括轮询、随机、响应时间加权等,并提供了自定义负载均衡策略的模板和注入方式。最后,给出了自定义负载均衡策略的代码示例。
摘要由CSDN通过智能技术生成

一、什么是负载均衡

1、简介

在这里插入图片描述
所谓的负载均衡(loadBalance)就是一种分压机制,当客户端产生大量的请求(流量)时,如果将所有的请求都分配给同一个服务器,那么会带来相应延迟、宕机… 的风险。所以需要将请求(流量)通过某种规则进行分散处理(将请求分配给多台服务器),从而实现给服务器降压。常用的负载均衡机制有:线性轮询、按权重负载、按流量负载、随机负载。

负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能可靠性


2、Ribbon 与 nginx 的区别

负载均衡又分为服务端的负载均衡客户端的负载均衡

  • nginx 是客户端所有请求统一交给 nginx,由 nginx 进行实现负载均衡请求转发,属于服务器端负载均衡。
  • Ribbon 是从 eureka 注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮询负载均衡策略,属于客户端端负载均衡。

Ribbon 适合与在微服务中 RPC/REST 调用实现本地服务负载均衡,比如 Dubbo、SpringCloud 中都是采用本地负载均衡。Nginx 适合于服务器端实现负载均衡,比如 Tomcat 。


二、使用 Ribbon 实现负载均衡

Ribbon通过自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相对繁琐。

我们的的目标是实现三个 注册中心、三个数据库、三个服务提供者、一个服务消费者

1、实现三个注册中心

参考:EurekaServer端/Eureka注册中心 的集群搭建(快速入门)

2、实现三个数据库

因为我们想要实现三个 服务提供者 ,同时实现了 三个的数据库 ,既然是分步式开发,那么每个 服务提供 者都应存在自己的数据库。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、实现三个服务提供者

我们希望通过三个相同功能的 服务提供者 来实现负载均衡
在这里插入图片描述

第一步:搭建 springcloud-provider-dept-8081

参考:EurekaServer端/Eureka注册中心 的集群搭建(快速入门)的 【三】

第二步:搭建 springcloud-provider-dept-8082

参考:EurekaServer端/Eureka注册中心 的集群搭建(快速入门)的 【三】

第三步:搭建 springcloud-provider-dept-8083

参考:EurekaServer端/Eureka注册中心 的集群搭建(快速入门)的 【三】

第四步:修改 服务提供者的端口号

因为三个服务提供者需要同时开启,我们没有开虚拟机,所以需要给它们分配不同的端口号,来实现同时启动。

springcloud-provider-dept-8081(application.ymal):

server:
  port: 8081

springcloud-provider-dept-8082(application.ymal):

server:
  port: 8082

springcloud-provider-dept-8083(application.ymal):

server:
  port: 8083

第五步:统一Application名称

我们要将三个服务提供者的 application.yaml 中的 spring. application.name 改为 springcloud-provider-dept 。

spring:
  application:
    name: springcloud-provider-dept

这样Eureka在识别服务注册时,就会将相同的application.nam的服务识别到一个服务列表中,那么服务消费者通过applicationName(微服务名)进行访问时,就可以实现负载均衡。如下(提前演示):【我只开启了springcloud-provider-dept-8081和springcloud-provider-dept-8082】
在这里插入图片描述

第六步:修改数据库连接的url
每个提供者都应存在自己的数据库,所以我们需要修改每个提供者对应的数据库

springcloud-provider-dept-8081(application.ymal):

url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC

springcloud-provider-dept-8082(application.ymal):

url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC

springcloud-provider-dept-8082(application.ymal):

url: jdbc:mysql://localhost:3306/db03?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC

第七步:修改与端口相关的信息(非必须)

在这里插入图片描述

4、实现一个服务消费者

第一步:创建服务消费者

参考:springcloud 学习环境搭建 中的【五】

我们需要修改一处:将原本 通过微服务地址实现访问 改为通过微服务的服务名进行访问。这是因为在Eureka中三个服务提供者会被注册到一个Application列表中。

@RestController
public class ConsumerController {

//    private static final String URL_HEAD = "http://localhost:8081/";

    //我们应该通过微服务的服务名进行访问微服务中的服务,而不是通过一个确定的微服务地址(因为我们之前配置了注册中心)
    private static final String URL_HEAD = "http://SPRINGCLOUD-PROVIDER-DEPT";

    @Autowired
    private RestTemplate restTemplatel;...

}

在这里插入图片描述


第二步:添加依赖

<!--ribbon 依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
<!--        eureka 客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

第三步:开启负载均衡注解

在服务消费者中我们是通过 RestTemplate 来实现基于 HTTP 的REST 类型的通信的,但是这种通信并没有负载均衡的功能。我们需要通过 @LoadBalanced 开启负载均衡的功能。

package com.tiger.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class BeanConfig {

    //配合通过Ribbon实现负载均衡(@LoadBalanced)
    @Bean
    @LoadBalanced //开启负载均衡
    public RestTemplate restTemplateBean(){
        return new RestTemplate();
    }
}


5、测试

因为小编的电脑不是16G的,无法同时开启 三个注册中心、三个服务提供者和一个服务消费者,所以我就开启一个注册中心、两个服务提供者和一个服务消费者作为演示。

访问:http://eurekaserver7001.com:7001/,查看服务注册情况
在这里插入图片描述

访问 http://localhost:8080/lists 第一次,获取department表中的信息
在这里插入图片描述


访问http://localhost:8080/lists 第二次,获取department表中的信息
在这里插入图片描述

三、使用 Feign 实现负载均衡

Feign 是在 Ribbon的基础上进行了一次改进,Feign 中是集成了 Ribbon,是一个使用起来更加方便的 HTTP 客户端。

Feign采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建http请求。

第一步:创建 Feign 接口(DeptClientService)

我们需要创建一个客户端的service,该service中的方法需要与服务端的controller相对应。

@FeignClient(value = “SPRINGCLOUD-PROVIDER-DEPT”):其作用就是取代在Ribbon中使用RestTemplate访问服务端中的controller对应的路由。在Feign中会根据该注解的value内容,自动拼接成服务端的访问地址(例如:http://SPRINGCLOUD-PROVIDER-DEPT/dept/insert/dName)。

package com.tiger.service;

import com.tiger.pojo.Department;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;

@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
    @GetMapping("/dept/insert/{name}")
    boolean addDept(@PathVariable("name") String dName);

    @GetMapping("/dept/select/{id}")
    Department queryOne(@PathVariable("id") int dNum);

    @GetMapping("/dept/select")
    List<Department> queryAll();
}


第二步:通过 DeptClientService 接口调用服务

package com.tiger.controller;

import com.tiger.pojo.Department;
import com.tiger.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController

public class ConsumerController {

    @Autowired
    private DeptClientService deptClientService;

    @RequestMapping("/add/{name}")
    public boolean addDept(@PathVariable("name") String dName){

        return this.deptClientService.addDept(dName);
    }

    @RequestMapping("/list/{id}")
    public Department queryOne(@PathVariable("id") int dNum){

        return this.deptClientService.queryByNum(dNum);
    }

    @RequestMapping("/lists")
    public List<Department> queryAll(){

        return this.deptClientService.queryAll();
    }
}

第三步:主启动类上添加 Feign 支持注解

@EnableFeignClients(basePackages = “com.tiger”):这个注解的作用是开启客户端 feign 支持。

这个注解的参数 basePackages 什么时候加上,是有要求的。当 第二步 中的service写在当前的服务(A)中,那么就不用添加 basePackages 也会自动扫描(@SpringBootApplication 可以扫描)。当 第二步 中的service被抽离出来,写在了其他的服务(B)中,时候就需要使用basePackages属性字段去指明应用程序A在启动的时候需要扫描服务B中的标注了@FeignClient注解的接口的包路径。

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SpringcloudConsumerDeptFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerDeptFeignApplication.class, args);
    }
}

测试

我们开启 springcloud-eureka-7001springcloud-provider-dept-8081springcloud-provider-dept-8082springcloud-consumer-dept-feign
在这里插入图片描述

访问:http://localhost:8080/list/1,很明显实现了负载均衡。
在这里插入图片描述

在这里插入图片描述


四、常用的负载均衡策略

在这里插入图片描述

1、RoundRobinRule 轮询策略(默认策略)

此策略是Ribbon的默认策略,是按照顺序,依次对服务列表中所有的服务进行访问。

2、RoundRobinRule 随机策略

就和这个策略的名字一样,是对user的3个服务的随机调用,所以不存在规律,如下源码中int index = this.chooseRandomInt(serverCount);通过随机数来选择下标,所以对服务列表中的服务的调用是随机的。

如果你要使用这种策略,你可以通过 @Bean 将其放在IoC容器中。

@Configuration
public class TigerRuleConfig{

    @Bean
    public IRule myRule(){
        //使用此负载均衡策略
        return new RoundRobinRule();
    }
}

IOC注入时的注意事项:

第一

负载均衡策略的IOC注入是有要求的,它应该被应用程序上下文的@ComponentScan注解扫描到(不能再主程序的同级目录下),否则将被原有的载均衡策略所覆盖。所以我们应该在主程序的上级目录中进行自定义策略与IOC的注入。
在这里插入图片描述
第二

我们需要在主程序上是有@RibbonClient (name="", configuration=Xxx.class )实现对原有的策略的覆盖

name :服务提供者的 Application
在这里插入图片描述

configuration :IOC注入时候所用到的类。

@SpringBootApplication
@EnableEurekaClient

//这样就可以指定 SPRINGCLOUD-PROVIDER-DEPT服务采用 TigerRuleConfig 对应的 ribbon 策略
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = TigerRuleConfig.class)
public class SpringcloudConsumerDept8080Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerDept8080Application.class, args);
    }

}

3、WeightedResponseTimeRule 响应时间加权重策略

根据服务列表中服务的响应时间来分配权重,响应时间越长的服务,权重越低,那么被调用的概率也就越低。相反,响应时间越短的服务,权重越高,被调用的概率也就越高
响应时间加权重策略的实现分为两步:
① WeightedResponseTimeRule实现类中默认情况下每隔30秒会统计一次每个服务的权重,在此30秒内,用的是轮询策略。
② 30秒之后,会根据统计的结果来分配每个实例的权重,然后根据权重来分配调用次数。

如果你要使用这种策略,你可以通过 @Bean 将其放在IoC容器中。

@Configuration
public class TigerRuleConfig{

    @Bean
    public IRule myRule(){
        //使用此负载均衡策略
        return new WeightedResponseTimeRule();
    }
}

IOC注入时的注意事项: 同上


4、RetryRule 重试策略

重试策略是指通过轮询策略选出一个实例,然后去访问,如果此实例为null或者已经失效,那么会重试其他的实例,answer = this.subRule.choose(key);【源码】 会根据轮询策略选择一个实例,然后if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline)【源码】判断如果实例为null或者失效,那么会重新选择。

如果你要使用这种策略,你可以通过 @Bean 将其放在IoC容器中。

@Configuration
public class TigerRuleConfig{

    @Bean
    public IRule myRule(){
        //使用此负载均衡策略
        return new RetryRule();
    }
}

IOC注入时的注意事项: 同上

5、BestAvailableRule 最低并发策略

会根据每个服务实例的并发数量来决定,访问并发数最少的那个服务,int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); 【源码】会获得当前遍历的实例的并发数,然后和其他的实例的并发数进行判断,最终访问并发量最少的那个实例。

如果你要使用这种策略,你可以通过 @Bean 将其放在IoC容器中。

@Configuration
public class TigerRuleConfig{

    @Bean
    public IRule myRule(){
        //使用此负载均衡策略
        return new BestAvailableRule();
    }
}

IOC注入时的注意事项: 同上


6、AvailabilityFilteringRule 可用过滤策略

此策略会过滤掉一直失败并被标记为circuit tripped的服务,而且会过滤掉那些高并发的服务。

如果你要使用这种策略,你可以通过 @Bean 将其放在IoC容器中。

@Configuration
public class TigerRuleConfig{

    @Bean
    public IRule myRule(){
        //使用此负载均衡策略
        return new AvailabilityFilteringRule();
    }
}

IOC注入时的注意事项: 同上



五、自定义负载均衡策略

我们随便点开一个负载均衡策略(RandomRule),发现其继承了一个 AbstractLoadBalancerRule 方法,这就意味着,我们完全可以照着源码中的策略编写自己的策略,然后注入到IOC容器中即可。
在这里插入图片描述

1、自定义负载均衡策略模版

package com.tigerRule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;

public class MyRule extends AbstractLoadBalancerRule {
    public MyRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;
            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                List<Server> upList = lb.getReachableServers(); // 获取可访问的服务器
                List<Server> allList = lb.getAllServers(); // 获取全部的服务器
                int serverCount = allList.size(); //获取全部服务器的数量
                if (serverCount == 0) {
                    return null;
                }

                //==================通过你【自定义】的规则获取【当前】要执行的服务器的【索引】====================
                int index = 0;

                //略 (编写你自己的规则) ....

                //==========================================================================================
                
                server = (Server)upList.get(index); // 当前要执行的服务
                if (server == null) {
                    Thread.yield(); // 使当前线程由执行状态,变成为就绪状态,让出cpu时间
                } else {
                    if (server.isAlive()) {
                        return server;
                    }
                    server = null;
                    Thread.yield();
                }
            }
            return server;
        }
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

2、覆盖原有策略

我们可以在主程序上是有@RibbonClient 实现对原有的策略的覆盖

name :服务提供者的 Application
在这里插入图片描述

configuration :自定义策略注入IOC时候所用到的类。

@SpringBootApplication
@EnableEurekaClient

//这样就可以为指定的Ribbon Client:SPRINGCLOUD-PROVIDER-DEPT服务采用TigerRuleConfig对应的ribbon配置
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = TigerRuleConfig.class)
public class SpringcloudConsumerDept8080Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerDept8080Application.class, args);
    }

}

注意事项

我们自定义的负载均衡策略的IOC注入是有要求的,它应该被应用程序上下文的@ComponentScan注解扫描到(不能再主程序的同级目录下),否则将被原有的载均衡策略所覆盖。所以我们应该在主程序的上级目录中进行自定义策略与IOC的注入。
在这里插入图片描述



✈ ❀ 希望平凡の我,可以给你不凡の体验 ☂ ✿ …
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值