【Spring Cloud 四】Ribbon负载均衡

Ribbon负载均衡

系列文章目录

【Spring Cloud一】微服务基本知识
【Spring Cloud 三】Eureka服务注册与服务发现


背景

目前公司项目使用的注册中心主要是Spring Cloud Alibaba的Nacos做的注册中心和配置中心。并且Nacos使用了Ribbon作为默认的负载均衡器。但是相当于将Ribbon的负载均衡给透明化了,日常开发明面上是看不到Ribbon的。所以出于提升自己的角度,博主对Ribbon进行了理论学习并进行了实践。

一、什么是Ribbon

Spring Cloud Ribbon它是一个基于HTTP和TCP的客户端负载均衡工具,它是基于Netflix Ribbon实现的。通过Spring Cloud的封装,可以让我们轻松的面向服务的REST模板请求自动转换为客户端负载均衡的服务调用。

Ribbon主要干什么工作:它用在服务消费者需要调用多个相同功能的服务提供者实例时,帮助决定选择哪个服务提供者实例来完成调用。Ribbon可以通过多种负载均衡策略(随机、轮询、加权 、iphash)等方式,将请求分配到不同的服务实例上,以实现负载均衡和高可用。

二、为什么要有Ribbon

总体来说Ribbon是为了解决在微服务架构中服务嗲用的负载均衡和容错问题而引入的。

Ribbon解决了以下关键问题:

  1. 负载均衡:通过不同势力之间分配负载,Ribbon可以使系统更加稳定和高效,防止某个实例过载而导致服务不可用。
  2. 容错和自动恢复:如果调用的服务实例发生故障或不可用,Ribbon会自动尝试从其他健康的实例中选择一个进行请求处理,这种机制可以提高系统的可靠性和健壮性。
  3. 多种负载均衡策略:可以允许根据实际需求进行定制,更好的满足不同的应用场景。

三、使用Ribbon进行负载均衡

补充:Ribbon作为服务消费者的负载均衡器,有两种使用方式,一种是和RestTemplate相结合,另一种是和OpenFeign相结合。OpenFeign已经默认集成了Ribbon。

例系统中总共有四个服务,一个是Eureka来做服务注册和服务发现,两个服务提供者,一个服务消费者。

Eureka已经在这篇博客进行了实践【Spring Cloud 三】Eureka服务注册与服务发现,本篇博客不在进行一步一步搭建。

服务提供者A代码

pom文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wangwei</groupId>
    <artifactId>provider-a</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>provider-a</name>
    <description>provider-a</description>
    <properties>
        <java.version>8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

yml配置文件

yaml 复制代码
server:
  port: 8080

spring:
    application:
      name: provider


eureka:
  client:
    service-url: 
      defaultZone: http://localhost:8761/eureka #eureka服务端和客户端的交互地址,不写的话默认是 8761,集群的话地址用,隔开。
    register-with-eureka: true #设置为fasle 不往eureka-server注册
    fetch-registry: true #应用是否拉取服务列表到本地
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,时间越短脏读越少 性能相应的消耗回答


  instance: #实例的配置
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
    hostname: localhost #主机名称或者服务ip
    prefer-ip-address: true #以ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 10 #服务实例的续约时间间隔

启动类

java 复制代码
@SpringBootApplication
@EnableEurekaClient
public class ProviderAApplication {

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

}

controller

java 复制代码
@RestController
public class ProviderController {
    @GetMapping("hello")
    public String hello(){
        return "我是服务提供者A";
    }
}

服务提供者B

pom文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wangwei</groupId>
    <artifactId>provider-b</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>provider-b</name>
    <description>provider-b</description>
    <properties>
        <java.version>8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

yml配置文件

yaml 复制代码
server:
  port: 8081

spring:
  application:
    name: provider


eureka:
  client:
    service-url: 
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #设置为fasle 不往eureka-server注册
    fetch-registry: true #应用是否拉取服务列表到本地
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,时间越短脏读越少 性能相应的消耗回答


  instance: #实例的配置
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
    hostname: localhost #主机名称或者服务ip
    prefer-ip-address: true #以ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 10 #服务实例的续约时间间隔

启动类

java 复制代码
@SpringBootApplication
@EnableEurekaClient
public class ProviderBApplication {

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

}

controller

java 复制代码
@RestController
public class ProviderController {
    @GetMapping("hello")
    public String hello(){
        return "我是服务提供者B";
    }
}

服务消费者

pom文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wangwei</groupId>
    <artifactId>consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consumer</name>
    <description>consumer</description>
    <properties>
        <java.version>8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

yml文件

yaml 复制代码
server:
  port: 8082

spring:
  application:
    name: consumer


eureka:
  client:
    service-url: 
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #设置为fasle 不往eureka-server注册
    fetch-registry: true #应用是否拉取服务列表到本地
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,时间越短脏读越少 性能相应的消耗回答


  instance: #实例的配置
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
    hostname: localhost #主机名称或者服务ip
    prefer-ip-address: true #以ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 10 #服务实例的续约时间间隔



ribbon:
    eager-load:
        enabled: false #ribbon只有自己的话是不能做服务发现,需要借助eureka。如果为false表示懒加载,请求调用的时候开启。如果为true表示程序启动的时候开启
    eureka:
        enabled: true
    http: #我们使用ribbon用的是RestTemplate发请求。底层是java.net.HttpUrlConnection发的请求, 很方便但是不支持连接池
        client:  # 发送请求的工具有很多如:httpClient 它支持连接池,效率更好。如果你想该请求的工具,需要添加对应的依赖。
          enabled: false
    okhttp:   #这个也是请求工具,移动端用的比较多, 轻量级请求
        enabled: false

启动类

java 复制代码
@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {

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

    /**
     * 这个RestTemplate
     * 添加上了LoadBalanced 它会被ribbon来操作
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
 }

controller

java 复制代码
@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("testRibbon")
    public String testRibbon(String serviceName){
        String result = restTemplate.getForObject("http://" + serviceName + "/hello", String.class);
        return result;
    }
}

运行测试

先运行Eureka服务,再运行服务提供者最后运行服务消费者。

调用接口的实现效果:Ribbon默认的负载均衡策略是轮询,及A------B------A------B。

四、Ribbon的负载均衡策略

Ribbon核心接口

根据负载均衡策略从所有对应的服务中选取一个服务

Ribbon提供的负载均衡算法

  1. com.netflix.loadbalancer.RoundRobinRule 轮询
  2. com.netflix.loadbalancer.RandomRule 随机
  3. com.netflix.loadbalancer.WeightedResponseTimeRule 权重轮询
  4. com.netflix.loadbalancer.AvailabilityFilteringRule 会先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务按照轮询的策略进行访问。
  5. com.netflix.loadbalancer.WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则。
  6. com.netflix.loadbalancer.BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务。
  7. com.netflix.loadbalancer.RetryRule 先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用服务。

配置此消费者嗲用任何服务都用某种负载均衡策略

示例:这里设置的是随机策略 ,这样的话这个消费者调用所有的服务负载均衡策略都是随机策略。

启动类中添加,当然你也可以是新建一个类并添加上@Configuration注解。

java 复制代码
    /**
     * @description:往容器中方一个rule对象
     * 你访问任何一个提供者 都是这个算法
     * @author: wangwei
     * @date: 2023/7/31 8:49
     * @param: []
     * @return: com.netflix.loadbalancer.IRule
     **/
    @Bean
    public IRule myRule(){
        return new RandomRule();
    }

}

访问不同的服务使用不同的算法规则

在yml文件中添加:

yaml 复制代码
#访问不同的服务可以使用不同的算法规则
provider: #服务的应用名
  ribfatkunbon:
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #算法的全限定类名

示例:

yaml 复制代码
server:
  port: 8082

spring:
  application:
    name: consumer


eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #设置为fasle 不往eureka-server注册
    fetch-registry: true #应用是否拉取服务列表到本地
    registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,时间越短脏读越少 性能相应的消耗回答


  instance: #实例的配置
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
    hostname: localhost #主机名称或者服务ip
    prefer-ip-address: true #以ip的形式显示具体的服务信息
    lease-renewal-interval-in-seconds: 10 #服务实例的续约时间间隔

#访问不同的服务可以使用不同的算法规则
provider: #服务的应用名
  ribfatkunbon:
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #算法的全限定类名

ribbon:
    eager-load:
        enabled: false #ribbon只有自己的话是不能做服务发现,需要借助eureka。如果为false表示懒加载,请求调用的时候开启。如果为true表示程序启动的时候开启
    eureka:
        enabled: true
    http: #我们使用ribbon用的是RestTemplate发请求。底层是java.net.HttpUrlConnection发的请求, 很方便但是不支持连接池
        client:  # 发送请求的工具有很多如:httpClient 它支持连接池,效率更好。如果你想该请求的工具,需要添加对应的依赖。
          enabled: false
    okhttp:   #这个也是请求工具,移动端用的比较多, 轻量级请求
        enabled: false

总结

  1. 通过对Ribbon的理论和实践的学习,让我对Ribbon的理解不再停留知道的层面上,而是切切实实的实践过。
  2. 相对整体和系统的对Ribbon进行了了解和学习,并且和其他微服务组件进行了链接。
相关推荐
Rain的Java大神之路3 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
需要8263 天前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
需要8263 天前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
苏渡苇4 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会4 天前
Nacos—注册中心
spring cloud·微服务
苏渡苇5 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会5 天前
微服务--分布式基础
java·spring·spring cloud·微服务
孙启超6 天前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
行百里er7 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
砍材农夫7 天前
spring|spring web|拦截器、过滤器、aop
java·spring boot·spring·spring cloud