SpringCloud 使用 LoadBalance 实现客户端负载均衡

SpringCloud 从 2020.0.1 版本开始,从 Eureka 中移除了 Ribbon 组件,使用 LoadBalance 组件来代替 Ribbon 实现客户端负载均衡。LoadBalance 组件相对于 Ribbon 来说,仅支持两种负载均衡策略:【轮询策略】和【随机策略】,估计后续会增加更多的负载均衡算法策略吧,从我个人的使用经验来说,其实 Ribbon 的负载均衡功能挺好用的。

本篇博客以目前最新版本的 SpringCloud 2021.0.3 版本为例,通过代码的方式来介绍使用 LoadBalance 组件实现客户端负载均衡的用法,在本篇博客的最后会提供源代码的下载。


一、搭建工程

采用 Maven 搭建 springcloud_loadbalance 父工程,下面包含 5 个子工程:

  • 1 个 Eureka 注册中心
  • 1 个 Consumer 服务消费者
  • 3 个 Provider 服务提供者

搭建后的效果如下图所示:

image

对于 springcloud_loadbalance 父工程 pom 文件内容如下所示:

<?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
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jobs</groupId>
    <artifactId>springcloud_loadbalance</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>eureka_app</module>
        <module>provider_app1</module>
        <module>provider_app2</module>
        <module>provider_app3</module>
        <module>consumer_app</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!--spring boot-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.11</version>
        <relativePath/>
    </parent>

    <!--Spring Cloud-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

二、注册中心搭建

在本篇博客的 Demo 中,仅搭建一个 Eureka 注册中心就能够满足使用了。

image

首先在 pom 文件中引入 spring-cloud-starter-netflix-eureka-server 的依赖。

<?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
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud_loadbalance</artifactId>
        <groupId>com.jobs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka_app</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--eureka-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

然后在启动来上,添加 @EnableEurekaServer 注解

package com.jobs.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

// 启用EurekaServer
@EnableEurekaServer
@SpringBootApplication
public class EurekaApp {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApp.class,args);
    }
}

然后配置 Eureka 的 application 的 yml 文件。需要注意的是:需要将 Eureka 的 hostname 在本机的 hosts 文件中建立与本机 ip 的映射关系,因为后续在配置服务消费者和服务提供者的注册中心地址时,使用 Eureka 的 hostname 比较方便一些。

# eureka 的默认端口是 8761,这里使用默认端口
server:
  port: 8761

eureka:
  instance:
    # 配置主机名,请在【hosts】文件中进行ip映射配置,
    # 因为后续的服务消费者和服务提供者需要使用这里的 hostname 配置其注册的 eureka 地址
    hostname: eureka-server
  client:
    service-url:
      # 该 eureka 注册中心对外提供的注册地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    # 是否将自己的路径注册到 eureka 上,单个 eureka 不需要把自己注册的自己上
    register-with-eureka: false
    # 是否需要从 eureka 中抓取路径,单个 eureka 不需要把自己从自己上获取服务的 url 地址
    fetch-registry: false
  server:
    # 关闭 eureka 自我保护,当注册的服务未及时发送心跳时,自动移除服务
    enable-self-preservation: false
    # eureka 检查服务是否存在的时间间隔(毫秒)
    eviction-interval-timer-in-ms: 5000

# 单个 eureka 节点,因为不需要注册到自己身上,所以下面不需要配置
spring:
  application:
    name: eureka-server

三、服务提供者搭建

本篇博客使用的服务提供者代码,跟上一篇博客的代码基本相同,只不过搭建了 3 个服务提供者,在接口返回的 json 结果中增加了 provider 字段,内容是服务提供者的 application 名称和配置的端口,用于验证负载均衡的调用效果,这里以 provider-app1 为例进行介绍。

image

首先 pom 文件需要引入 spring-cloud-starter-netflix-eureka-client 的依赖。

<?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
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud_loadbalance</artifactId>
        <groupId>com.jobs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>provider_app1</artifactId>

    <dependencies>
        <!--spring boot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

编写 provider-app1 的 application.yml 配置文件

server:
  port: 8201

eureka:
  instance:
    # 配置主机名
    hostname: provider-service1
    # 显示 ip 地址,代替显示主机名
    prefer-ip-address: true
    # 所注册服务实例名称的显示形式
    instance-id: ${eureka.instance.hostname}:${server.port}
    # 每隔 3 秒发一次心跳包
    lease-renewal-interval-in-seconds: 3
    # 如果 15 秒没有发送心跳包,就让 eureka 把自己从服务列表中移除
    lease-expiration-duration-in-seconds: 15
  client:
    service-url:
      # 将当前 springboot 服务注册到 eureka 中
      defaultZone: http://eureka-server:8761/eureka
    # 是否将自己的路径注册到 eureka 上
    register-with-eureka: true
    # 是否需要从 eureka 中抓取路径
    fetch-registry: true

# provider 集群需要使用相同的 application 名称
spring:
  application:
    name: provider-App

需要注意的是:本篇博客使用 3 个服务提供者搭建集群,因此 3 个服务提供者必须使用相同的 application 名称,这里统一配置为 provider-app ,最后列出服务提供者所提供的接口内容:

package com.jobs.provider.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RequestMapping("/provider")
@RestController
public class ProviderController {

    @Autowired
    private Environment env;

    @RequestMapping("/getdata/{id}")
    public Map GetData(@PathVariable("id") int id) {

        //获取当前服务提供者的 hostname 和 port
        String providerName = env.getProperty("eureka.instance.hostname")
                + ":" + env.getProperty("server.port");

        Map result = new HashMap();
        result.put("status", 0);
        result.put("msg", "success");
        result.put("provider", providerName);
        result.put("get_id_value", id);
        result.put("version", UUID.randomUUID().toString());

        return result;
    }
}

四、服务消费者搭建

这里是本篇博客介绍的重点,因为 SpringCloud 使用 LoadBalance 实现负载均衡,就是在服务消费者这里实现的。

image

首先需要引入 spring-cloud-starter-netflix-eureka-client 的依赖,其 pom 文件跟服务提供者相同,这里不再列出。

这里先列出服务消费者的 application.yml 配置文件内容

server:
  port: 8100

eureka:
  instance:
    # 配置主机名
    hostname: consumer-service
    # 显示 ip 地址,代替显示主机名
    prefer-ip-address: true
    # 所注册服务实例名称的显示形式
    instance-id: ${eureka.instance.hostname}:${server.port}
    # 每隔 3 秒发一次心跳包
    lease-renewal-interval-in-seconds: 3
    # 如果 15 秒没有发送心跳包,就让 eureka 把自己从服务列表中移除
    lease-expiration-duration-in-seconds: 15
  client:
    service-url:
      # 将当前 springboot 服务注册到 eureka 中
      defaultZone: http://eureka-server:8761/eureka
    # 是否将自己的路径注册到 eureka 上
    register-with-eureka: true
    # 是否需要从 eureka 中抓取路径
    fetch-registry: true

# consumer 使用的 application 名称
spring:
  application:
    name: consumer-App

# SpringCloud 从 2020.0.1 版本之后,客户端负载均衡抛弃了 ribbon
# 采用 SpringCloud LoadBalancer 默认使用的是轮询负载均衡算法
# 要想使用随机算法,也很简单,本篇博客的 demo 也就介绍,您也可以查看官网
# https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer

SpringCloud 的 LoadBalance 仅支持 2 种负载均衡策略:轮询(默认)和随机,后续应该会支持更多,先介绍轮询策略的使用吧。

只需要在 RestTemplate 的 Bean 对象上增加 @LoadBalanced 注解即可,其实上一篇博客的 demo 就已经使用了。

package com.jobs.consumer.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;

/*
    SpringCloud 从 2020.0.1 版本开始,从 eureka 中移除了 ribbon
    使用 SpringCloud LoadBalance 替代 ribbon 进行客户端负载均衡
    目前 SpringCloud LoadBalance 仅支持两种负载均衡策略:【轮询策略】和【随机策略】
*/

@Configuration
public class RestTemplateConfig {

    //增加该注解后,即可支持客户端负载均衡
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {

        return new RestTemplate();
    }
}

然后使用 RestTemplate 调用服务提供者的接口时,使用服务提供者的 application 名称,从注册中心获取服务提供者集群的所有服务地址,自动在本地通过负载均衡的算法,决定要调用那一个服务地址。

需要注意的是:服务提供者的名称最好从 Eureka 注册中心界面中复制,这么不容易出错。

下面列出服务消费者调用服务提供者接口的代码

package com.jobs.consumer.controller;

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 org.springframework.web.client.RestTemplate;

import java.util.Map;

@RequestMapping("/consumer")
@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/getdata/{id}")
    public Map GetData(@PathVariable("id") int id) {

        //使用服务在 eureka 上注册的 application 名称代替 ip 和端口号
        String url = "http://PROVIDER-APP/provider/getdata/" + id;
        Map result = restTemplate.getForObject(url, Map.class);
        return result;
    }
}

注意:上面采用 PROVIDER-APP 代替了服务提供者的 ip 和 端口,PROVIDER-APP 是从注册中心界面中复制过来的。

到此为止,服务的消费者采用轮询的客户端负载均衡方式,调用服务提供者接口的代码示例,已经搭建完毕,可以进行测试验证。


五、采用随机负载均衡策略

服务的消费者如果想采用随机的负载均衡策略,非常简单,可以参考以下官网地址:

https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer

需要通过以下两个步骤即可实现:

首先需要定义随机的算法对象,并通过 @Bean 将其加载到 Spring 容器中,具体如下:

package com.jobs.consumer.config;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class RandomLoadBalancerConfig {

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {

        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

然后在 RestTemplate 配置类上方,使用 @LoadBalancerClient 或 @LoadBalancerClients 注解,针对具体服务配置具体的客户端负载均衡算法策略,由于本篇博客只有一个服务提供者集群,因此这里使用 @LoadBalancerClient 注解进行演示:

package com.jobs.consumer.config;

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

/*
    SpringCloud 从 2020.0.1 版本开始,从 eureka 中移除了 ribbon
    使用 SpringCloud LoadBalance 替代 ribbon 进行客户端负载均衡
    目前 SpringCloud LoadBalance 仅支持两种负载均衡策略:【轮询策略】和【随机策略】
*/

//使用 @LoadBalancerClient 或 @LoadBalancerClients 注解,针对具体服务配置具体的客户端负载均衡算法策略
@LoadBalancerClient(name = "PROVIDER-APP", configuration = RandomLoadBalancerConfig.class)
@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {

        return new RestTemplate();
    }
}

重启消费者程序后,再调用服务提供者 PROVIDER-APP 所提供的接口时,采用的就是随机的客户端负载均衡策略。



OK,到此为止,有关 SpringCloud 在 2020.0.1 版本之后,使用 LoadBalance 代替 Ribbon 实现客户端负载均衡的实现方式,已经介绍完毕。目前官网只支持【轮询】和【随机】两种算法,希望后续能够像 Ribbon 一样支持更多的负载均衡算法。

本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springcloud_loadbalance.zip

posted @ 2022-08-27 19:50  乔京飞  阅读(11254)  评论(0编辑  收藏  举报