在Spring Boot API Gateway中实现Sticky Session

news2024/12/29 16:37:55

文章目录

  • 小结
  • 问题
  • 在API Gateway中实现Sticky Session
  • 在同一个API Gateway中同时支持Sticky Session和RoundRobinLoadBalancer
  • 参考

小结

在Kubernetes微服务的云环境中,如何在Spring Boot API Gateway中实现Sticky Session,当服务请求被某一个服务器处理后,所有后续的请求都被转发到被第一次进行处理的同一个服务器再进行处理,这里进行了尝试,取得了想要的结果。

问题

Spring Boot API Gateway中实现Sticky Session在Spring Boot官方文档并没有特别详细的描述,看来看去语焉不详,如下:
https://docs.spring.io/: 3.9. Request-based Sticky Session for LoadBalancer

解决这个问题不仅要自定义负载均衡策略和方法,并需要Spring Boot API Gateway能够用某种方法取得服务器实例的ID并将每一个收到的服务请求处理并转发到具有相应服务器实例ID的服务器。实际上在Github上已经有大神给出了解决方案,具体地址如下:
Github: tengcomplex/spring-cloud-gateway-lb-sticky-session

在API Gateway中实现Sticky Session

实现的环境为Kubernetes微服务的云环境,这里需要使用cookie,并使用Eureka服务发现模块。具体思路如下:

  • StickySessionLoadBalancer实现ReactorServiceInstanceLoadBalancer,相当于自定义了一个负载均衡策略
  • Spring Boot API Gateway收到http服务请求,StickySessionLoadBalancercookie中找服务器实例ID: 自定义一个scg-instance-idcookie的键值
  • 如果scg-instance-idcookie被找到,而且是一个有效的服务器实例ID,那么这个服务请求就会被路由到这个具有服务器实例ID的服务器实例进行处理
  • 反之,如果没有找到scg-instance-idcookie的键值,或者服务器实例ID无效(有可能服务器已经宕机),那么委托ReactorServiceInstanceLoadBalancer 重新选择一个服务器,并将服务请求转发那个服务器
  • 无论以上路由如何选择,Spring Boot API Gateway会将服务器实例ID更新到cookie中去,scg-instance-id为的键值

sticky session
注:以上图片是Sticky SessionSpring Boot API Gateway路由示意图,来源于Github: Question: Sticky session in routes with load balancer #1176

首先,在Spring Boot API Gateway的模块中定义LoadBalancerClients

@EnableEurekaClient
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan({"com.aa.bb.configuration"})
//
@LoadBalancerClients({
        @LoadBalancerClient(value = "APPLICATION", configuration = com.aa.bb.configuration.StickySessionLoadBalancerConfiguration.class)})

这里StickySessionLoadBalancerConfiguration.class在有以下StickySessionLoadBalancerBean创建。

  @Bean
  @Lazy
  public ReactorLoadBalancer<ServiceInstance> leastConnSticky(Environment environment,
      LoadBalancerClientFactory loadBalancerClientFactory) {
    String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    L.debug("name: {}", name);
    return new StickySessionLoadBalancer(loadBalancerClientFactory.getProvider(name, ServiceInstanceListSupplier.class),
        name);
  }

通常情况下Spring Boot API Gateway有关路由的配置是在application.yml,这里在Spring Boot API Gateway的模块中定义过滤器,并使用程序定义路由:

    @Value("com.aa.bb.frontendUriAPPLICATION:lb://APPLICATION")
    private String frontendUriAPPLICATION;

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {

        GatewayFilter gatewayFilter = customFilterSticky(clientFactory, properties);
        return builder.routes()
                .route("frontend",
                        r -> r.path("/application/**")
                                .filters(f -> f.filter(gatewayFilter))
                                .uri(frontendUri))
                                .build();
    }

以上是由以下程序定义,此函数返回GatewayFilter,注意这里不是GlobalFilter, 否则无法在同一个API Gateway中同时支持Sticky SessionRoundRobinLoadBalancer

    @Bean
    public GatewayFilter customFilterSticky(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        return new ReactiveLoadBalancerStickySessionFilter(clientFactory, properties);
    }

客户端服务请求被以上过滤器拦截后,交给了以下具体的由ReactiveLoadBalancerStickySessionFilter实现的过滤器方法filter处理 ,其中ReactiveLoadBalancerStickySessionFilterGatewayFilter的实现:

@Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
    String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
    L.debug("Filtering, url: {}, schemePrefix: {}", url, schemePrefix);
    if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
      L.debug("Not choosing, go further in the chain");
      return chain.filter(exchange);
    }
    // preserve the original url
    addOriginalRequestUrl(exchange, url);
    L.trace("{} url before: {}", ReactiveLoadBalancerStickySessionFilter.class.getSimpleName(), url);

    return choose(exchange).doOnNext(response -> {

      if (!response.hasServer()) {
        throw NotFoundException.create(true, "Unable to find instance for " + url.getHost());
      }
      URI uri = exchange.getRequest().getURI();
      // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
      // if the loadbalancer doesn't provide one.
      String overrideScheme = null;
      if (schemePrefix != null) {
        overrideScheme = url.getScheme();
      }
      DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(), overrideScheme);
      URI requestUrl = reconstructURI(serviceInstance, uri);
      L.debug("Url chosen: {}", requestUrl);
      exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
    }).then(chain.filter(exchange));
  }

接下来具体的choose(exchange)方法跳到以下进行处理:

  private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
    URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
    L.debug("We are choosing, uri: {}", uri);
    ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(uri.getHost(),
        ReactorLoadBalancer.class, ServiceInstance.class);
    if (loadBalancer == null) {
      throw new NotFoundException("No loadbalancer available for " + uri.getHost());
    }
    L.debug("Using loadbalancer {}", loadBalancer.getClass().getSimpleName());
    Mono<Response<ServiceInstance>> ret = loadBalancer.choose(createRequest(exchange));
    ret.subscribe(r -> L.debug("We have {}", r.getServer().getUri()));
    return ret;
  }

以上loadBalancer.choose(createRequest(exchange)方法调用具体的定制化的StickySessionLoadBalancerchoose方法进行处理:

  @SuppressWarnings("rawtypes")
  @Override
  public Mono<Response<ServiceInstance>> choose(Request request) {
    ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
        .getIfAvailable(NoopServiceInstanceListSupplier::new);
    return supplier.get().next().flatMap(list -> getInstanceResponse(list, request));
  }

那么,最后的重点,也就是核心的核心在Spring Boot API Gateway中实现Sticky Session的实现在这里,在此实现了服务请求拦截,选择Sticky Session决定的服务器,并更新cookie的操作:

  @SuppressWarnings("rawtypes")
  private Mono<Response<ServiceInstance>> getInstanceResponse(List<ServiceInstance> instances, Request request) {
    if (instances.isEmpty()) {
      L.warn("如果没有可用的服务: {}", this.serviceId);
      return Mono.just(new EmptyResponse());
    }
    L.debug("request: {}, instances: {}", request.getClass().getSimpleName(), instances);
    Object context = request.getContext();
    L.debug("Context class name: {}", context.getClass().getSimpleName());
    if (!(context instanceof ServerWebExchange)) {
      throw new IllegalArgumentException("The context must be a ServerWebExchange");
    }
    ServerWebExchange exchange = (ServerWebExchange) context;
    L.debug("exchange: {}", exchange);
    // 检查 exchange 有一个 cookie 指向了一个可用的 ServiceInstance,这里使用`scg-instance-id`为`cookie`的键值
    return serviceInstanceFromCookie(exchange, instances)
        // 如果ServiceInstance存在, 那么路由到这个ServiceInstance
        .map(instance -> Mono.just((Response<ServiceInstance>) new DefaultResponse(instance)))
        // 否则使用ReactorServiceInstanceLoadBalancer委托选择一个服务器
        .orElseGet(() -> delegate.choose(request))
        // 无论如何,需要更新`cookie`键值为`scg-instance-id`的值
        .doOnNext(response -> setCookie(exchange, response));
  }

以上是实现的全部过程,在控制台可以看到以下输出:

DEBUG 2023-09-19 11:47:28.008 [reactor-http-nio-6] - Mapping [Exchange: POST http://127.0.0.1:8080/application/Process] to Route{id='frontend', uri=com.aa.bb.frontendUri:lb://APPLICATION, order=0, predicate=Paths: [/application/**], match trailing slash: true, gatewayFilters=[com.aa.bb.configuration.ReactiveLoadBalancerStickySessionFilter@97002113], metadata={}}

在同一个API Gateway中同时支持Sticky Session和RoundRobinLoadBalancer

以上提到了在同一个API Gateway中同时支持Sticky SessionRoundRobinLoadBalancer, 需要使用GatewayFilter,而不是GlobalFilter
例如这里需要同时支持RoundRobinLoadBalancer,那么,类似的可以自定义一个返回ReactorLoadBalancerRoundRobinLoadBalancer,实际上是一个标准的实现,应该会有更好办法,为了简便,暂时使用:

  @Bean
  @Lazy
  public ReactorLoadBalancer<ServiceInstance> leastConn(Environment environment,
      LoadBalancerClientFactory loadBalancerClientFactory) {
    String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    L.debug("client name: {}", name);
    return new RoundRobinLoadBalancer(loadBalancerClientFactory.getProvider(name, ServiceInstanceListSupplier.class),
        name);
  }
}

同前,再定义一个GatewayFilter, 不赘述。
同理,在Spring Boot API Gateway的模块中定义LoadBalancerClients

@LoadBalancerClients({
        @LoadBalancerClient(value = "APPLICATION_B", configuration = com.aa.bb.configuration.RoundRobinSessionLoadBalancerConfiguration.class)})

Spring Boot API Gateway的主模块中如下操作:

    @Value("com.aa.bb.frontendUriAPPLICATION_B:lb://APPLICATION_B")
    private String frontendUriAPPLICATION_B;
    @Bean
    public RouteLocator customRouteLocatorSAMMSCP(RouteLocatorBuilder builder) {

        GatewayFilter gatewayFilter = customFilter(clientFactory, properties);
        return builder.routes()
                .route("frontendapplicationb",
                        r -> r.path("/Application_B/**")
                                .filters(f -> f.filter(gatewayFilter))
                                .uri(frontendUriAPPLICATION_B))
                .build();
    }
    @Bean
    public GatewayFilter customFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        return new RoundRobinLoadBalancerFilter(clientFactory, properties);
    }

与先前类似,使用RoundRobinLoadBalancerFilter实现的过滤器方法处理 ,其中RoundRobinLoadBalancerFilterGatewayFilter的实现,在这里,RoundRobinLoadBalancerFilter不需要做任何处理,因为RoundRobinLoadBalancer已经由Spring Boot API Gateway标准包实现过了。

参考

Github: Question: Sticky session in routes with load balancer #1176
Github: LoadBalancer: Add Sticky LB implementation. #689
Github: tengcomplex/spring-cloud-gateway-lb-sticky-session
Github: POC for a session/cookie based sitcky load balancer implementation. #764
Saturn Cloud: Spring Cloud Gateway Route with Multiple Instances and Sticky Session
Stackoverflow: Sticky session loadbalancing in spring Microservices [closed]
Stackoverflow: How to use a Spring Cloud Gateway Custom Filter to filter every request?
Stackoverflow: No found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations:
Stackoverflow: Sticky session loadbalancing in spring Microservices [closed]
Stackoverflow: Request-based Sticky Session configuration with Spring Cloud LoadBalancer
CSDN: 基于springcloud3.0.1版本Loadbalancer切换默认负载均衡策略
51 CTO: Spring Cloud LoadBalancer–自定义负载均衡策略–方法/实例
https://docs.spring.io/: 3.9. Request-based Sticky Session for LoadBalancer
https://docs.spring.io/: 3.2. Switching between the load-balancing algorithms3.2. Switching between the load-balancing algorithms
https://docs.spring.io/: 2.1. The @EnableDiscoveryClient Annotation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1022834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

晶码存档&改造【01】模板导入租户登录

app101改造之前 想搞一个设备导入功能&#xff0c; 想象中 实际上 再改改样式 关于数据的导入导出&#xff1a; 可见博客 APEX数据源加载实现Excel表数据导入及自定义存储过程_王小小鸭的博客-CSDN博客https://blog.csdn.net/clover_oreo/article/details/132575970?csdn_s…

华为HCIA(五)

Vlan id 在802.1Q中 高级ACL不能匹配用户名和源MAC 2.4G频段被分为14个交叠的&#xff0c;错列的20MHz信道&#xff0c;信道编码从1到14&#xff0c;邻近的信道之间存在一定的重叠范围 STA通过Probe获取SSID信息 Snmp报文 网络管理设备异常发生时会发送trap报文 D类地址是…

【git】超详细使用指令

git指令 暂存区工作区提交到暂存区暂存区覆盖到工作区暂存区移除文件暂存区提交到版本库 git储藏场景条件 版本库版本库回退 忽略文件新建.gitignore文件&#xff0c;填入相应配置忽略文件 分支创建分支命令切换分支命令查看所有分支 分支合并切回要合入的分支上合并其他分支过…

数据结构--希尔排序

目录 希尔排序的定义 给一个序列进行希尔排序 希尔排序的算法实现 算法性能分析 1.时间复杂度 2.稳定性 3.适用性 知识点回顾 希尔排序的定义 给一个序列进行希尔排序 第一趟d可以是元素个数/2 相距为4的子表 对子表进行直接插入排序&#xff08;由小到大&#xf…

在Windows10中打开控制面板的5种方法以及如何设置环境变量

在Windows10中打开控制面板的5种方法 方法一&#xff1a;在Windows10中通过运行命令打开控制面板方法二&#xff1a;从Windows 10中的桌面访问控制面板方法三&#xff1a;在Windows10中使用搜索启动控制面板方法四&#xff1a;在Windows10桌面上创建控制面板快捷方式方法五&…

Proxyman Premium for Mac:网络调试和分析工具的终极选择

如果你在寻找一款强大而高效的网络调试和分析工具&#xff0c;那么Proxyman Premium for Mac绝对值得你的关注。这款工具专为Mac用户设计&#xff0c;旨在帮助开发人员、网络管理员和技术专业人员解决网络相关问题&#xff0c;同时提供一系列强大的功能来满足他们的需求。 Pro…

嵌入式软件测试

1.etest的使用 里面有“包教包会.pptx”按照ppt完成安装及题目下载和测试. 1&#xff09;安装etest &#xff08;解压 &#xff0c;注意盘根目录下解压&#xff09; 2&#xff09;安装vspd &#xff08;解压 &#xff09; 3&#xff09;使用vspd端口配置 &#xff08;…

[ MySQL] — 事务管理

什么是事务&#xff1f; 事务就是一组DML语句组成&#xff0c;这些语句在逻辑上存在相关性&#xff0c;这一组DML语句要么全部成功&#xff0c;要么全部失败&#xff0c;是一个整体。MySQL提供一种机制&#xff0c;保证我们达到这样的效果。事务还规定不同的客户端看到的数据是…

易点易动系统帮助制药企业提供智能化固定资产管理方案

制药企业作为大宗重资产行业,其生产车间内各种设备及仪器往往复杂、种类繁多、数量众多。高效管理这些固定资产对企业生产作业来说显得尤为重要。但是,传统手写账目或excel电子表格等管理模式已不能适应如今海量资产的管理需求。易点易动专业从事固定资产管理领域多年,通过自主…

Vue的第二章节之模版语法(带你感受来自Vue模版语法的魅力)

目录 ​编辑 前言 一、了解模版语法 1. 什么是模版语法 2. 应用场景 3. 对开发的作用 二、插值 1. 文本 2. HTMLj解析 3. 表达式 三、指令 1. v-if/v-else-if/v-else的使用 2. v-show v-show与v-if的区别 3. v-for v-for的使用 扩展&#xff08;下拉框&#x…

selenium中ActionChains方法详细讲解

前言 本文将介绍Selenium中的ActionChains类及其使用方法&#xff0c;帮助您模拟用户在网页上的鼠标和键盘操作。了解ActionChains的常用方法和示例代码&#xff0c;可轻松实现移动鼠标、点击元素、拖拽元素等操作。通过本文的学习&#xff0c;您能更好地应用ActionChains解决自…

Oracle for Windows安装和配置——Oracle for Windows软件安装

2. Oracle for Windows安装和配置 2.1. Oracle for Windows软件安装 2.1.1. 准备Oracle软件 1&#xff09;下载或拷贝安装软件 下载地址:otn.oracle.com或my oracle support。下载文件列表。具体如图2.1.1-1所示。 图2.1.1-1 下载文件列表 --说明&#xff1a; 1&#xff0…

flink-connector-mysql-cdc

FlinkCDC是一个实现CDC(Change Data Capture)思想的数据同步工具。 借助于Flink CDC Connector &#xff0c;它监听数据库的二进制日志文件&#xff0c;来获取数据库变更的数据&#xff0c;以此实现数据同步。 1. 下载Flink 及 对应版本的 Flink CDC FlinkCDC 支持的 Connect…

升级:远程桌面软件玩游戏指南

你有没有遇到过这样的场景&#xff1a;你想玩一款特定的游戏&#xff0c;但却受到设备功能的限制&#xff1f;这就是游戏远程桌面的概念变得非常宝贵的地方。从本质上讲&#xff0c;它允许您远程利用高端游戏计算机的功能&#xff0c;使您能够在自己的设备上玩游戏。 可以考虑…

四、JAVA语言基础

JAVA语言基础 JAVA语言基础1.1.Java主类结构1.1.1.包声明1.1.2.声明成员变量和局部变量1.1.3.编写主方法1.1.4.导入API类库 —————————————————————————————————————————————————— JAVA语言基础 很多人认为在学习Java语言之…

uni-app 之 文字分两行显示超出用省略号表示

uni-app 之 文字分两行显示超出用省略号表示 vue 将一大串文字分两行显示超出用省略号表示 通过css设置文字强制不换行超出用省略号表示: {white-space: nowrap; 文本强制不换行&#xff1b;text-overflow:ellipsis; 文本溢出显示省略号&#xff1b;overflow:hidden; 溢出的部…

QTabWidget 类 (选项卡部件)

1、 QTabWidget 类直接继承自 QWidget。该类提供了一个选项卡栏(QTabBar)和一个相应的页 面区域&#xff0c;用于显示与每个选项卡相对应的页面。 与 QStackedLayout 布局原理相同&#xff0c;只有 当前页面(即可见页面)是可见的&#xff0c;所有其他页面都不可见&#xff0c;用…

数字藏品交易系统有哪些特点?

数字藏品交易系统具有以下特点&#xff1a; 去中心化&#xff1a;数字藏品交易系统通常采用去中心化的架构&#xff0c;不依赖于单一的中央机构或权威来管理交易。这意味着交易可以在全球范围内进行&#xff0c;无需中间人。 区块链技术&#xff1a;大多数数字藏品交易系统借助…

小区物业管理缴费报修活动报名商城小程序开源版开发

小区物业管理缴费报修活动报名商城小程序开源版开发 以下是小区物业管理缴费报修活动报名商城小程序开源版的功能列表&#xff1a; 用户注册和登录小区信息展示&#xff08;包括小区简介、周边设施等&#xff09;物业公告显示和发布功能小区物业费用查询和缴费功能基础设施报…

解决安装wsl时候报错的问题: WslRegisterDistribution failed with error: 0x800701bc

在Win10 或者Win 11上安装wsl的时候&#xff0c;可能会遇到如下的错误信息&#xff1a; Error: 0x800701bc WSL 2 ?????????????????? https://aka.ms/wsl2kernel Press any key to continue... 这个是由于系统内置的WSL内核过低导致的&#xff0c;可以到如…