使用 Spring Cloud LoadBalancer 实现客户端负载均衡

作者:Grey

原文地址:

博客园:使用 Spring Cloud LoadBalancer 实现客户端负载均衡

CSDN:使用 Spring Cloud LoadBalancer 实现客户端负载均衡

背景

Spring Cloud G 版发布时提到,

Spring Cloud Netflix 那套组件很多都进入了维护期,如下表所示

同时,针对一些组件,Spring 官方给出了一些替代方案

针对 spring-cloud-ribbon 这个负载均衡组件,Spring 官方提出的替换解决方案是 Spring Cloud Loadbalancer。本文主要通过一个示例介绍了 Spring Cloud Loadbalancer 的基础使用。

环境

  • JDK 1.8+

  • Maven 3.5+

  • Spring Boot 版本:2.7.5

  • Spring Cloud 版本:2021.0.5

项目结构和说明

  • spring-cloud-loadbalanced-usage:父项目名称

    • server : 服务端端模块

      • src/
      • pom.xml
    • client : 客户端模块
      • src/
      • pom.xml
    • pom.xml:父项目 pom 配置

流程

整个过程如下示例图

注:Spring Cloud Loadbalancer 是在客户端实现负载均衡策略。

代码说明

服务端主要暴露一个服务,未做特殊配置

    @GetMapping("/greeting")
public String greet() {
log.info("Access /greeting");
int randomNum = rand.nextInt(greetings.size());
return greetings.get(randomNum);
}

客户端的核心配置如下,主要是针对负载均衡配置:

package git.snippet.client.config;

import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux; import java.util.Arrays;
import java.util.List; @Configuration
@LoadBalancerClient(name = "server")
public class WebClientConfig { @LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
} @Bean
@Primary
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new ServiceInstanceListSupplier() {
@Override
public String getServiceId() {
return "server";
} @Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays.asList(
new DefaultServiceInstance(getServiceId() + "1", getServiceId(), "localhost", 8090, false), new DefaultServiceInstance(getServiceId() + "2", getServiceId(), "localhost", 9092, false), new DefaultServiceInstance(getServiceId() + "3", getServiceId(), "localhost", 9999, false)));
}
};
}
}

其中 @LoadBalancerClient(name = "server") 指定了 服务端的名称;

getServiceId()指定了服务端的服务 ID;

serviceInstanceListSupplier()方法中列出了三个服务端实例的地址;

new DefaultServiceInstance(getServiceId() + "1", getServiceId(), "localhost", 8090, false)
new DefaultServiceInstance(getServiceId() + "2", getServiceId(), "localhost", 9092, false)
new DefaultServiceInstance(getServiceId() + "3", getServiceId(), "localhost", 9999, false)

有了这个配置,在客户端的 Controller 中,做如下注入即可

package git.snippet.client.controller;

import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono; @RestController
public class HiController {
private final WebClient.Builder loadBalancedWebClientBuilder;
private final ReactorLoadBalancerExchangeFilterFunction lbFunction; public HiController(WebClient.Builder loadBalancedWebClientBuilder, ReactorLoadBalancerExchangeFilterFunction lbFunction) {
this.loadBalancedWebClientBuilder = loadBalancedWebClientBuilder;
this.lbFunction = lbFunction;
} @RequestMapping("/hi")
public Mono<String> hi(@RequestParam(value = "name", defaultValue = "Mary") String name) {
return loadBalancedWebClientBuilder.build().get().uri("http://server/greeting").retrieve().bodyToMono(String.class).map(greeting -> String.format("%s, %s!", greeting, name));
} @RequestMapping("/hello")
public Mono<String> hello(@RequestParam(value = "name", defaultValue = "John") String name) {
return WebClient.builder().filter(lbFunction).build().get().uri("http://server/greeting").retrieve().bodyToMono(String.class).map(greeting -> String.format("%s, %s!", greeting, name));
}
}

启动并测试

首先启动 server,注意:

启动 server 的时候,需要启动多实例,且每个实例要定义 VM options

实例一定义 VM options 为 -Dserver.port=8090

实例二定义 VM options 为 -Dserver.port=9092

实例三定义 VM options 为 -Dserver.port=9999

如果使用 IDEA Intellij,配置方式如下

三个 Server 启动后,接下来启动 client,运行 ClientApplication,启动完毕后,多次访问: http://localhost:8888/hi

可以通过每个 server 实例的运行日志,查看到每个实例都轮流获取到了请求,实现了负载均衡。

完整代码

见:spring-cloud-loadbalancer-usage

参考文档

Spring Cloud Greenwich.RELEASE is now available

Client-Side Load-Balancing with Spring Cloud LoadBalancer

最新文章

  1. C++之路进阶——codevs1789(最大获利)
  2. WebResource.axd 404 错误
  3. 一次PostgreSql数据迁移,使用nodejs来完成
  4. php 表单的活用
  5. 《University Calculus》-chape4-导数的应用-洛必达法则
  6. 【转】从头到尾彻底理解KMP
  7. .Net 4.5 Task
  8. 数组有没有 length()这个方法? String 有没有 length()这 个方法?
  9. [.NET] 《Effective C#》快速笔记 - C# 中的动态编程
  10. mysql常用sql命令
  11. NYOJ--128--前缀式计算(表达式求值)
  12. zoj1002 Fire Net
  13. Windows下swoole扩展的编译安装部署
  14. 微信小程序在开发中遇到的问题与解决方法
  15. 3.Python3变量与基本数据类型
  16. P2292 [HNOI2004]L语言
  17. [leetcode]64. Minimum Path Sum最小路径和
  18. Android 获取加速传感器的值,并去除杂音
  19. SSD安装记录
  20. cookie之困

热门文章

  1. 给ShardingSphere提了个PR,不知道是不是嫌弃我
  2. vacuum和vacuum full的处理过程
  3. 字符类数据类型和oracle字符类型的区别
  4. Docker 入门指南
  5. 走进Redis-扯扯集群
  6. 论文解读(RvNN)《Rumor Detection on Twitter with Tree-structured Recursive Neural Networks》
  7. typora收费了,最后一个免费版提供下载
  8. Node Exporter监控指标
  9. service的dns记录
  10. 2_CSS