【Java编程系列】gateway限流实践时发生的问题和解决方案

news2025/2/24 8:54:46

前期回顾:

【Java编程系列】Springcloud-gateway自带限流方案实践篇


1、实践中发生的问题

主要有以下几个问题:

1、限流返回的响应数据无法自定义

(LogFormatUtils.java:91) - [7b93af46-20] Completed 429 TOO_MANY_REQUESTS

返回后显示的情况如下:


2、默认的限流器(RequestRateLimiterGatewayFilterFactory)会发生异常

有一部分情况会出现:Error [java.lang.UnsupportedOperationException]

详细信息如下:

2023-04-25 12:27:10.628 [NGIEGzOnuguYdozn] ERROR (HttpWebHandlerAdapter.java:295) - [7533b14d-23] Error [java.lang.UnsupportedOperationException] for HTTP POST "***/activity/interaction", but ServerHttpResponse already committed (429 TOO_MANY_REQUESTS)
2023-04-25 12:27:10.641 [NGIEGzOnuguYdozn] ERROR (Loggers.java:319) - [id: 0x7533b14d, L:/0:0:0:0:0:0:0:1:18085 - R:/0:0:0:0:0:0:0:1:48745] Error starting response. Replying error status
java.lang.UnsupportedOperationException: null
	at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:91)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ springfox.boot.starter.autoconfigure.SwaggerUiWebFluxConfiguration$CustomWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ HTTP POST "/***/***/interact" [Exception***Handler]
Stack trace:
		at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:91)
		at org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory.lambda$null$0(RequestRateLimiterGatewayFilterFactory.java:120)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118)
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121)
		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1705)
		at reactor.core.publisher.MonoReduceSeed$ReduceSeedSubscriber.onComplete(MonoReduceSeed.java:156)
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:1939)
		at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.deferredComplete(FluxUsingWhen.java:402)
		at reactor.core.publisher.FluxUsingWhen$CommitInner.onComplete(FluxUsingWhen.java:536)
		at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:81)
		at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:803)
		at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:589)
		at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:569)
		at reactor.core.publisher.FluxFlatMap$FlatMapMain.onComplete(FluxFlatMap.java:455)
		at reactor.core.publisher.FluxArray$ArraySubscription.slowPath(FluxArray.java:137)
		at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:99)
		at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:363)
		at reactor.core.publisher.FluxMerge.subscribe(FluxMerge.java:69)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4110)
		at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onComplete(FluxUsingWhen.java:394)
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:1939)
		at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:359)
		at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onComplete(FluxConcatMap.java:268)
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:1939)
		at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onComplete(MonoFlatMapMany.java:252)
		at io.lettuce.core.RedisPublisher$ImmediateSubscriber.onComplete(RedisPublisher.java:895)
		at io.lettuce.core.RedisPublisher$State.onAllDataRead(RedisPublisher.java:673)
		at io.lettuce.core.RedisPublisher$State$3.read(RedisPublisher.java:587)
		at io.lettuce.core.RedisPublisher$State$3.onDataAvailable(RedisPublisher.java:544)
		at io.lettuce.core.RedisPublisher$RedisSubscription.onDataAvailable(RedisPublisher.java:313)
		at io.lettuce.core.RedisPublisher$RedisSubscription.onAllDataRead(RedisPublisher.java:328)
		at io.lettuce.core.RedisPublisher$SubscriptionCommand.complete(RedisPublisher.java:758)
		at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654)
		at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614)
		at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:565)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
		at java.lang.Thread.run(Thread.java:748)

3、在nacos配置gateway的限流配置不生效问题

在nacos的配置中,配置gateway的限流配置,如下:

- id: ***service
  uri: 'lb://***service'
  order: 0
  filters: []
  predicates:
    - args:
        pattern: /xxx/**
              name: Path
- id: ***service-limiter
  uri: lb://***service
  order: 0
  predicates:
    - Path= /xxx/activity/**
  filters:
    - name: GatewayRequestRateLimiter
      args:
        key-resolver: "#{@remoteAddrKeyResolver}"
        redis-rate-limiter.replenishRate: 20 # 令牌桶填充的速率 秒为单位
        redis-rate-limiter.burstCapacity: 20 # 令牌桶总容量
        redis-rate-limiter.requestedTokens: 1 # 每次请求获取的令牌数 

你会发现这样配置是不会生效的~~~,测试时都有点不理解。。。


2、解决方案

首先,我们分析一下2种情况导致的原因,

第一个问题,因为源码的过滤器RequestRateLimiterGatewayFilterFactory中,会将限流拦截的请求的http status code设置为429,但是具体的内容格式却不是JSON格式,导致我们看到的响应结果如上图所示。

第二个问题,通过报错内容提示,我们可以找到源码的120行:

at org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory.

lambda$null$0(RequestRateLimiterGatewayFilterFactory.java:120)

源码如下:

这里有往header里加参数,但是提示显示,是 ReadOnlyHttpHeaders.add,只读的headers,是不可以添加操作的,所以抛出了UnsupportedOperationException的异常:

 所以,

这2个问题基本都是由于源代码的过滤器所导致,这里要解决问题,我们可以自定义一个过滤器替代一下,代码如下:

package ***.filters;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @Date: 2023/4/25 10:44
 * @Description gateway限流limiter,重写限流原过滤器RequestRateLimiterGatewayFilterFactory
 */
@Component
@Slf4j
public class GatewayRequestRateLimiter extends RequestRateLimiterGatewayFilterFactory {

  private final RateLimiter defaultRateLimiter;

  private final KeyResolver defaultKeyResolver;

  public GatewayRequestRateLimiter(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
    super(defaultRateLimiter, defaultKeyResolver);
    this.defaultRateLimiter = defaultRateLimiter;
    this.defaultKeyResolver = defaultKeyResolver;
  }

  @Override
  public GatewayFilter apply(Config config) {
    KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);
    RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);
    return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {
      String routeId = config.getRouteId();
      if (routeId == null) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        routeId = route.getId();
      }
      String finalRouteId = routeId;
      return limiter.isAllowed(routeId, key).flatMap(response -> {
        for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
          exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
        }
        
        if (response.isAllowed()) {
          return chain.filter(exchange);
        }
        
        ServerHttpResponse httpResponse = exchange.getResponse();
        //修改code为500
        httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        if (!httpResponse.getHeaders().containsKey("Content-Type")) {
          httpResponse.getHeaders().add("Content-Type", "application/json");
        }
        
        //此处无法触发全局异常处理,手动返回
        JSONObject object = new JSONObject();
        object.put("status","429");
        object.put("message","请求已被限流");
        DataBuffer buffer = httpResponse.bufferFactory().wrap(object.toJSONString().getBytes(StandardCharsets.UTF_8));
        return httpResponse.writeWith(Mono.just(buffer));
      });
    });
  }

  private <T> T getOrDefault(T configValue, T defaultValue) {
    return (configValue != null) ? configValue : defaultValue;
  }
}

然后,替换掉原来的gateway中的配置项:

filters:
    - name: GatewayRequestRateLimiter   #替换原默认过滤器RequestRateLimiter
      args:
        key-resolver: "#{@remoteAddrKeyResolver}"
        redis-rate-limiter.replenishRate: 20 # 令牌桶填充的速率 秒为单位
        redis-rate-limiter.burstCapacity: 20 # 令牌桶总容量
        redis-rate-limiter.requestedTokens: 1 # 每次请求获取的令牌数   

然后再来看看效果,返回结果:

第三个问题,是因为该服务的限流配置的过滤器优先级,和要限流的服务本身的路由配置,两者之间的执行前后优先级一样或是限流的在服务本身配置之后了,简单说就是order的值如果更大,就会越迟执行,所以才导致限流配置没生效。。改成如下即可:

- id: ***service-limiter
  uri: lb://***service
  order: -1   ##将优先级改小,即提高优先级
  predicates:
    - Path= /xxx/activity/**
  filters:
    - name: GatewayRequestRateLimiter
      args:
        key-resolver: "#{@remoteAddrKeyResolver}"
        redis-rate-limiter.replenishRate: 20 # 令牌桶填充的速率 秒为单位
        redis-rate-limiter.burstCapacity: 20 # 令牌桶总容量
        redis-rate-limiter.requestedTokens: 1 # 每次请求获取的令牌数 

 好啦,至此限流的优化处理,基本完成啦。。。

最后,对于本文有疑问的地方,欢迎下方留言讨论,喜欢的朋友,请帮忙一键三连~~~

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

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

相关文章

C++第二章:变量和基本内置类型

变量和基本内置类型 一、基本内置类型1.1 算数类型1.2 带符号类型和无符号类型1.3 类型转换含有无符号类型的表达式 1.4 字面值常量整形和浮点型字面值字符和字符串字面值转义序列指定字面值的类型 二、变量2.1 变量的定义初始化列表初始化默认初始化 2.2 变量声明和定义的关系…

【web-ctf】ctf_BUUCTF_web(2)

文章目录 BUUCTF_webSQL注入1. [RCTF2015]EasySQL2. [CISCN2019 华北赛区 Day1 Web5]CyberPunk3. [CISCN2019 总决赛 Day2 Web1]Easyweb4. [GYCTF2020]Ezsqli5. [网鼎杯 2018]Comment 文件上传漏洞1. [WUSTCTF2020]CV Maker2. [NPUCTF2020]ezinclude3. [SUCTF 2019]EasyWeb 文件…

TADK 23.03 release note

主要功能概述&#xff1a; 基于深度学习的应用分类&#xff1a;在原有的基于机器学习的应用分类(AppID)能力基础上&#xff0c;扩展出新的深度学习参考模型和推理引擎。FFEL的raw byte特征提取&#xff1a;增加了流特征提取库(FFEL)对数据包中的raw byte特征提取能力&#xff0…

Meta AI 重磅推出LIMA!媲美GPT-4、无需RLHF就能对齐!

深度学习自然语言处理 原创作者&#xff1a;鸽鸽 昨天Meta AICMU这篇文章一出&#xff0c;twitter都炸了&#xff01; LIMA&#xff0c;只使用1000个精心挑选的样本微调一个 65B LLaMa&#xff0c;无需RLHF&#xff0c;性能媲美甚至优于GPT-4&#xff01; 论文&#xff1a;LIMA…

游戏洞察丨自来水还是井水,后流量时代的私域挑战

流量生意本质上是买卖用户浏览时间的生意&#xff0c;如果用户增长到顶&#xff0c;那就意味着供给到顶。对比 2021 年&#xff0c;2022 年的游戏出海在谷歌和 Facebook 上投入的广告成本几乎翻了一倍。新晋“渠道王者”TikTok 逐渐走进大家的视野。该现象背后的原因在于&#…

解决幂等问题,只需要记住这个口诀!

△Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 417 篇原创分享 作者 l Hollis 来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09; 作为开发人员&#xff0c;我们每天都要开发大量的接口&#xff0c;其中包括了读接口和写接口&#xff0c;而对于写接…

将矩阵按指定对角线转化为一个上三角矩阵numpy.triu()方法

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将矩阵按指定对角线转化为一个上三角矩阵 numpy.triu() 选择题 关于以下代码说法错误的一项是? import numpy as np a np.array([[1,2],[3,4]]) print("【显示】a\n",a) print(&…

应用程序和 API 攻击呈上升趋势

Akamai Technologies 发布了一份新的互联网现状报告&#xff0c;标题为“突破安全漏洞&#xff1a;针对组织的应用程序和 API 攻击的兴起”。 报告显示&#xff0c;亚太地区和日本&#xff08;APJ&#xff09;的金融服务业仍然是该地区受攻击最严重的行业&#xff0c;Web 应用…

大模型推理性能优化之KV Cache解读

0. 引言 做大模型性能优化的一定对KV Cache不陌生&#xff0c;那么我们对这个技术了解到什么程度呢&#xff1f;请尝试回答如下问题&#xff1a; KV Cache节省了Self-Attention层中哪部分的计算&#xff1f;KV Cache对MLP层的计算量有影响吗&#xff1f;KV Cache对block间的数据…

知识点滴 - 什么是膳食结构

膳食结构是指膳食中各类食物的数量及其在膳食中所占的比重&#xff0c;由于影响膳食结构的这些因素是在逐渐变化的&#xff0c;所以膳食结构不是一成不变的&#xff0c;人们可以通过均衡调节各类食物所占的比重&#xff0c;充分利用食品中的各种营养&#xff0c;达到膳食平衡&a…

适配器模式:代码接口的神奇转换

一、概要 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口&#xff0c;使得原本由于接⼝不兼容⽽不能⼀起⼯作的那些类可以⼀起⼯作。通俗来讲&#xff0c;就是通过适配器来连接两个不…

Js 如何实现一个类似 chatGPT 打字机效果

在使用chatGPT的时候,会有一个打字机的效果,以下是分别使用原生Js和Vue实现 原生 JS 实现 如下是示例代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>Printer 打字机效果</title><style>* {margin: 0;bor…

记录--使用率比较低的10个Web API

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 avaScript中有些API可能使用率比较低&#xff0c;下面我们逐一介绍它们的用法和使用场景。 至于标题&#xff0c;主要是想让你进来看看&#xff0c;兄弟们别打我&#xff01; Blob API Blob API 用于处…

电脑技巧:CopyQ剪切板增强工具介绍(附下载)

目录 1、软件简介 2、主要功能介绍 3、使用说明 4、总结 今天给大家再分享一款剪切板增强工具——CopyQ&#xff0c;感兴趣的朋友可以下载试一试&#xff01; 1、软件简介 CopyQ 是一款开源的、跨平台剪贴板管理工具&#xff0c;支持 Windows、macOS、Linux&#xff0c;可…

项目中遇到的一些问题总结(十)

nacos保护阈值 Nacos 中的保护阈值&#xff08;Protection Threshold&#xff09;是用来保护服务实例的一种机制。当某个服务实例出现故障或异常时&#xff0c;服务注册中心 Nacos 会通过心跳检测等方式将其从服务列表中移除&#xff0c;以避免客户端继续向其发送请求。但是&a…

在Linux中为Simulink添加ROS自定义消息类型

在Linux中为Simulink添加ROS自定义消息类型 基于Matlab/Simulink的ROS自定义消息类型的添加方法 ROS与Simulink联合仿真(三):自定义Message 1、下载 ROS Toolbox Interface for ROS Custom Messages 将 roscustommsg.mlpkginstall 文件放入 MATLAB 工作空间 双击 roscustommsg…

Nature -- 人类首个 “泛基因组”旨在编目人类遗传多样性

在人类基因组项目发布第一个人类基因组草图的20多年后,研究人员发布了人类“泛基因组”草图——这预示着一种新的参考基因组的出现,它能捕获到更多的人类遗传多样性信息。 泛基因组变异图由两个元素组成:序列图&#xff0c;其ode表示定向DNA链&#xff0c;双向边表示连通性关系…

学系统集成项目管理工程师(中项)系列25_计算机网络知识

1. OSI七层协议 1.1. 物理层 1.1.1. RS232、V.35、RJ-45、FDDI 1.2. 数据链路层 1.2.1. 【21上选17】 1.2.2. IEEE802.3/.2、HDLC、PPP、ATM 1.3. 网络层 1.3.1. IP、ICMP、IGMP、IPX、ARP 1.3.2. 路由选择 1.3.2.1. 【20下选17】 1.4. 传输层 1.4.1. TCP、UDP、SPX…

越小越好: Q8-Chat,在英特尔至强 CPU 上体验高效的生成式 AI

大语言模型 (LLM) 正在席卷整个机器学习世界。得益于其 transformer 架构&#xff0c;LLM 拥有从大量非结构化数据 (如文本、图像、视频或音频) 中学习的不可思议的能力。它们在 多种任务类型 上表现非常出色&#xff0c;无论是文本分类之类的抽取任务 (extractive task) 还是文…

又一批令人惊艳的 AI 工具,诞生了!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 自 ChatGPT 发布以后&#xff0c;AIGC 行业的热度也一直在持续发酵。几个月过去了&#xff0c;对比之前&#xff0c;各类 AI 工具的热度不减反增&#xff0c;各行各业的人都早开始拥抱 AIG…