前期回顾:

【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: nullat 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: ***serviceuri: 'lb://***service'order: 0filters: []predicates:- args:pattern: /xxx/**name: Path
- id: ***service-limiteruri: lb://***serviceorder: 0predicates:- Path= /xxx/activity/**filters:- name: GatewayRequestRateLimiterargs: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;}@Overridepublic 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为500httpResponse.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   #替换原默认过滤器RequestRateLimiterargs:key-resolver: "#{@remoteAddrKeyResolver}"redis-rate-limiter.replenishRate: 20 # 令牌桶填充的速率 秒为单位redis-rate-limiter.burstCapacity: 20 # 令牌桶总容量redis-rate-limiter.requestedTokens: 1 # 每次请求获取的令牌数   

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

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

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

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

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

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

  1. 这可能是全网Spring Cloud Gateway限流最完整的方案了!

        作者:aneasystone     https://www.aneasystone.com/ 话说在 Spring Cloud Gateway 问世之前,Spring Cloud 的微服务世 ...

  2. 不得不了解系列之限流

    点击关注公众号,Java干货及时送达 来源:https://my.oschina.net/qiangmzsx/blog/4277685 限流简介 现在说到高可用系统,都会说到高可用的保护手段:缓存.降 ...

  3. 【Java编程系列】Java自定义标签-Tag

    热门系列: [Java编程系列]WebService的使用 [Java编程系列]在Spring MVC中使用工具类调用Service层时,Service类为null如何解决 [Java编程系列]Spr ...

  4. 【Java编程系列】log4j配置日志按级别分别生成日志文件

    热门系列: [Java编程系列]WebService的使用 [Java编程系列]在Spring MVC中使用工具类调用Service层时,Service类为null如何解决 [Java编程系列]Spr ...

  5. Re0:Java编程系列-3 进阶排序思维分析与对比

    Re0:Java编程系列 作者参加校招,在复习Java的同时,决定开一打系列博客.复习的同时,作者希望能留下材料,方便也服务一些新入门的小伙伴. 本系列文章从基础入手,由简单的功能函数开始,再扩展为类 ...

  6. 【Java编程系列】java用POI、Itext生成并下载PPT、PDF文件

    热门系列: [Java编程系列]WebService的使用 [Java编程系列]在Spring MVC中使用工具类调用Service层时,Service类为null如何解决 [Java编程系列]Spr ...

  7. 【Java编程系列】Minio实现文件上传下载

    热门系列: [Java编程系列]Amazon S3实现文件上传下载 目录 热门系列: 1.前言 2.Minio实战代码 2.1 Minio环境部署 2.2 Minio的Sdk对接实现 2.2.1 Mi ...

  8. 【Java编程系列】Java判断世界各时区的夏令时、冬令时

    热门系列: [Java编程系列]java用POI.Itext生成并下载PPT.PDF文件 [Java编程系列]二进制如何表示小数?0.3+0.6为什么不等于0.9?纳尼!!! 程序人生,精彩抢先看 目 ...

  9. 51信用卡 限流 实践

    为什么需要限流 我们都知道,构建高并发的系统有三大利器:缓存.降级.限流.通过使用缓存,可以让用户在获取数据链路的过程变的更短.获取数据的速度变得更快,从而提升系统的吞吐量,通过使用降级手短,可以把非 ...

最新文章

  1. python-pcl官网 应用、特征、过滤Filter教程翻译
  2. Java --Lamda表达式
  3. leetcode算法题--反转链表★
  4. subShell与代码块
  5. 修改nullMyEclipse 设置文件的默认编码
  6. 学习笔记(49):Python实战编程-place布局
  7. 如何优化 App 的启动耗时?
  8. django-多级联动课堂版0912
  9. 微型计算机pentium或celeron,计算机综合测试答案.doc
  10. 2012-2013年度大总结
  11. 双管道(CreatePipe)与本地cmd.exe进程通信(附源代码及编译好的程序,免费下载)
  12. Redhat之NIS
  13. Java中IO的快速复习(代码+注释)
  14. 第二十三讲 常用技术标准【2021年软考-高级信息系统项目管理师】
  15. 语音特征:spectrogram、Fbank(fiterbank)、MFCC
  16. 云网融合驱动数据中心技术聚变
  17. spark sql uv_使用Spark Streaming SQL进行PV/UV统计-阿里云开发者社区
  18. 锦江展焕新演绎,憬黎公寓住造理想
  19. android心跳包作用,Android开发Im总结-5:心跳包
  20. 学会不被洗脑 很重要!

热门文章

  1. 无监督对比学习之MOCO 《Momentum Contrast for Unsupervised Visual Representation Learning》
  2. FileReader对象
  3. 网页设计作业,网页制作作业HTML5+CSS大作业——汽车专题网页设计(1页) dreamweaver作业静态HTML网页设计模板
  4. 计算机科学与技术课程教学,浅谈计算机科学与技术课程教学
  5. 使用CSS实现鼠标悬浮标题出现动态下划线
  6. 初级程序员要了解的行业冷知识
  7. Reflex WMS入门系列十五:Reflex系统上对已经Confirm的Receipt能继续做收货么?
  8. 如何用AI抠图?这几种抠图方法既省心又省力
  9. 杭电oj HDOJ 1018 Big Number(斯特林公式求大数阶乘的位数)
  10. python 正则匹配png_正则表达式筛选出jpg、png的图片url