Spring Cloud Gateway 內(nèi)存溢出的解決方案
記 Spring Cloud Gateway 內(nèi)存溢出查詢過程
環(huán)境配置:
- org.springframework.boot : 2.1.4.RELEASE
- org.springframework.cloud :Greenwich.SR1
事故記錄:
由于網(wǎng)關(guān)存在 RequestBody 丟失的情況,顧采用了網(wǎng)上的通用解決方案,使用如下方式解決:
@Bean public RouteLocator tpauditRoutes(RouteLocatorBuilder builder) { return builder.routes().route("gateway-post", r -> r.order(1) .method(HttpMethod.POST) .and() .readBody(String.class, requestBody -> {return true;}) # 重點(diǎn)在這 .and() .path("/gateway/**") .filters(f -> {f.stripPrefix(1);return f;}) .uri("lb://APP-API")).build(); }
測(cè)試環(huán)境,Spring Cloud Gateway 網(wǎng)關(guān)功能編寫完成。開始進(jìn)行測(cè)試環(huán)境壓測(cè)。
正常采用梯度壓測(cè)方式,最高用戶峰值設(shè)置為400并發(fā)。經(jīng)歷兩輪時(shí)長(zhǎng)10分鐘左右壓測(cè),沒有異常情況出現(xiàn)。
中午吃飯時(shí)間,設(shè)置了1個(gè)小時(shí)的時(shí)間進(jìn)行測(cè)試。
回來的時(shí)候系統(tǒng)報(bào)出如下異常
2019-08-12 15:06:07,296 1092208 [reactor-http-server-epoll-12] WARN io.netty.channel.AbstractChannelHandlerContext.warn:146 - An exception '{}' [enable DEBUG level for full stacktrace] was thrown by a user handler's exceptionCaught() method while handling the following exception: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 16777216 byte(s) of direct memory (used: 503316487, max: 504889344) at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:640) at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:594) at io.netty.buffer.PoolArena$DirectArena.allocateDirect(PoolArena.java:764) at io.netty.buffer.PoolArena$DirectArena.newChunk(PoolArena.java:740) at io.netty.buffer.PoolArena.allocateNormal(PoolArena.java:244) at io.netty.buffer.PoolArena.allocate(PoolArena.java:214) at io.netty.buffer.PoolArena.allocate(PoolArena.java:146) at io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:324) at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185) at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:176) at io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:137) at io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator$MaxMessageHandle.allocate(DefaultMaxMessagesRecvByteBufAllocator.java:114) at io.netty.channel.epoll.EpollRecvByteAllocatorHandle.allocate(EpollRecvByteAllocatorHandle.java:72) at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:793) at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe$1.run(AbstractEpollChannel.java:382) at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163) at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404) at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:315) at io.
當(dāng)時(shí)一臉懵逼,馬上開始監(jiān)控 Jvm 堆棧,減少jvm的內(nèi)存空間,提升并發(fā)數(shù)以后,重啟項(xiàng)目重新壓測(cè),
項(xiàng)目啟動(dòng)參數(shù)如下:
java -jar -Xmx1024M /opt/deploy/gateway-appapi/cloud-employ-gateway-0.0.5-SNAPSHOT.jar ↓↓↓↓修改為↓↓↓↓ java -jar -Xmx512M /opt/deploy/gateway-appapi/cloud-employ-gateway-0.0.5-SNAPSHOT.jar
縮減了一半內(nèi)存啟動(dòng),等待問題復(fù)現(xiàn)。等待3分鐘問題再次復(fù)現(xiàn),但是同時(shí)Jvm卻的進(jìn)行了Full GC。
EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT 275456.0 100103.0 484864.0 50280.2 67672.0 64001.3 9088.0 8463.2 501 11.945 3 0.262 275968.0 25072.3 484864.0 47329.3 67672.0 63959.4 9088.0 8448.8 502 11.970 4 0.429
沒錯(cuò),在出現(xiàn)問題的時(shí)候,系統(tǒng)出現(xiàn)了Full Gc,但是OU并沒有達(dá)到觸發(fā)的原因。
結(jié)合日志中的 direct memory,想到了Jvm 中的堆外內(nèi)存。
使用 -XX:MaxDirectMemorySize 可以進(jìn)行設(shè)置 Jvm 堆外內(nèi)存大小,當(dāng) Direct ByteBuffer 分配的堆外內(nèi)存到達(dá)指定大小后,即觸發(fā)Full GC。
該值是有上限的,默認(rèn)是64M,最大為 sun.misc.VM.maxDirectMemory()。
結(jié)合所有情況,表明堆外內(nèi)存使用存在內(nèi)存溢出的情況。
報(bào)錯(cuò)內(nèi)容為Netty框架,新增以下配置,開啟Netty錯(cuò)誤日志打印:
-Dio.netty.leakDetection.targetRecords=40 #設(shè)置Records 上限 -Dio.netty.leakDetection.level=advanced #設(shè)置日志級(jí)別
項(xiàng)目啟動(dòng),沒任何問題,開啟壓測(cè)后服務(wù)報(bào)出如下異常:
2019-08-13 14:59:01,656 18047 [reactor-http-nio-7] ERROR io.netty.util.ResourceLeakDetector.reportTracedLeak:317 - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information. Recent access records: #1: org.springframework.core.io.buffer.NettyDataBuffer.release(NettyDataBuffer.java:301) org.springframework.core.io.buffer.DataBufferUtils.release(DataBufferUtils.java:420) org.springframework.core.codec.StringDecoder.decodeDataBuffer(StringDecoder.java:208) org.springframework.core.codec.StringDecoder.decodeDataBuffer(StringDecoder.java:59) org.springframework.core.codec.AbstractDataBufferDecoder.lambda$decodeToMono$1(AbstractDataBufferDecoder.java:68) reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:107) reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:331) reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:123) reactor.core.publisher.FluxJust$WeakScalarSubscription.request(FluxJust.java:101) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onSubscribe(MonoCollectList.java:90) reactor.core.publisher.FluxJust.subscribe(FluxJust.java:70) reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) reactor.core.publisher.MonoCollectList.subscribe(MonoCollectList.java:59) reactor.core.publisher.MonoFilterFuseable.subscribe(MonoFilterFuseable.java:44) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:56) reactor.core.publisher.MonoSubscriberContext.subscribe(MonoSubscriberContext.java:47) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoPeek.subscribe(MonoPeek.java:71) reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55) reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:331) reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:123) reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252) reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:372) reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:196) reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:337) reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:333) reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:453) reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:141) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:191) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930) io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:677) io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:612) io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:529) io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:491) io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905) java.lang.Thread.run(Unknown Source) #2: io.netty.buffer.AdvancedLeakAwareByteBuf.nioBuffer(AdvancedLeakAwareByteBuf.java:712) org.springframework.core.io.buffer.NettyDataBuffer.asByteBuffer(NettyDataBuffer.java:266) org.springframework.core.codec.StringDecoder.decodeDataBuffer(StringDecoder.java:207) org.springframework.core.codec.StringDecoder.decodeDataBuffer(StringDecoder.java:59) org.springframework.core.codec.AbstractDataBufferDecoder.lambda$decodeToMono$1(AbstractDataBufferDecoder.java:68) reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:107) reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:331) reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:123) reactor.core.publisher.FluxJust$WeakScalarSubscription.request(FluxJust.java:101) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onSubscribe(MonoCollectList.java:90) reactor.core.publisher.FluxJust.subscribe(FluxJust.java:70) reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) reactor.core.publisher.MonoCollectList.subscribe(MonoCollectList.java:59) reactor.core.publisher.MonoFilterFuseable.subscribe(MonoFilterFuseable.java:44) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:56) reactor.core.publisher.MonoSubscriberContext.subscribe(MonoSubscriberContext.java:47) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoPeek.subscribe(MonoPeek.java:71) reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55) reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:331) reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:123) reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252) reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:372) reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:196) reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:337) reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:333) reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:453) reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:141) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:191) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930) io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:677) io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:612) io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:529) io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:491) io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905) java.lang.Thread.run(Unknown Source) #3: io.netty.buffer.AdvancedLeakAwareByteBuf.slice(AdvancedLeakAwareByteBuf.java:82) org.springframework.core.io.buffer.NettyDataBuffer.slice(NettyDataBuffer.java:260) org.springframework.core.io.buffer.NettyDataBuffer.slice(NettyDataBuffer.java:42) org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory.lambda$null$0(ReadBodyPredicateFactory.java:102) reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:46) reactor.core.publisher.MonoCollectList.subscribe(MonoCollectList.java:59) reactor.core.publisher.MonoFilterFuseable.subscribe(MonoFilterFuseable.java:44) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:56) reactor.core.publisher.MonoSubscriberContext.subscribe(MonoSubscriberContext.java:47) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoPeek.subscribe(MonoPeek.java:71) reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55) reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:331) reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505) reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:123) reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252) reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:372) reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:196) reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:337) reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:333) reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:453) reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:141) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:191) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337) io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345) io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930) io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:677) io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:612) io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:529) io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:491) io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905) java.lang.Thread.run(Unknown Source)
在 #3 中,我發(fā)現(xiàn)了一個(gè)眼熟的類,ReadBodyPredicateFactory.java ,還記得最開始的時(shí)候使用 readbody 配置么?
這里就是進(jìn)行 cachedRequestBodyObject 的寫入類,
追蹤一下Readbody源碼
/** * This predicate is BETA and may be subject to change in a future release. A * predicate that checks the contents of the request body * @param inClass the class to parse the body to * @param predicate a predicate to check the contents of the body * @param <T> the type the body is parsed to * @return a {@link BooleanSpec} to be used to add logical operators */ public <T> BooleanSpec readBody(Class<T> inClass, Predicate<T> predicate) { return asyncPredicate(getBean(ReadBodyPredicateFactory.class) .applyAsync(c -> c.setPredicate(inClass, predicate))); }
異步調(diào)用的 ReadBodyPredicateFactory.applyAsync() 和 錯(cuò)誤日志中的
org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory.lambda$null$0(ReadBodyPredicateFactory.java:102)
指向方法一致。查看源碼102行:
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())) );
此處 Spring Cloud Gateway 通過 dataBuffer.slice 切割出了新的 dataBuffer,但是通過 Netty 的內(nèi)存檢測(cè)工具判斷,此處的 dataBuffer 并沒有被回收。
錯(cuò)誤如下,日志很多容易被忽視。
ERROR io.netty.util.ResourceLeakDetector.reportTracedLeak:317 - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
找到問題那就要解決才行,嘗試修改源碼
@Override @SuppressWarnings("unchecked") public AsyncPredicate<ServerWebExchange> applyAsync(Config config) { return exchange -> { Class inClass = config.getInClass(); Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); Mono<?> modifiedBody; // We can only read the body from the request once, once that // happens if we // try to read the body again an exception will be thrown. The below // if/else // caches the body object as a request attribute in the // ServerWebExchange // so if this filter is run more than once (due to more than one // route // using it) we do not try to read the request body multiple times if (cachedBody != null) { try { boolean test = config.predicate.test(cachedBody); exchange.getAttributes().put(TEST_ATTRIBUTE, test); return Mono.just(test); } catch (ClassCastException e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Predicate test failed because class in predicate " + "does not match the cached body object", e); } } return Mono.just(false); } else { // Join all the DataBuffers so we have a single DataBuffer for // the body return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> { // Update the retain counts so we can read the body twice, // once to parse into an object // that we can test the predicate against and a second time // when the HTTP client sends // the request downstream // Note: if we end up reading the body twice we will run // into // a problem, but as of right // now there is no good use case for doing this DataBufferUtils.retain(dataBuffer); // Make a slice for each read so each read has its own // read/write indexes Flux<DataBuffer> cachedFlux = Flux .defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } }; # 新增如下代碼 DataBufferUtils.release(dataBuffer); return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), messageReaders) .bodyToMono(inClass).doOnNext(objectValue -> { exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue); exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux); }).map(objectValue -> config.predicate.test(objectValue)); }); } }; }
Spring Cloud Gateway 在配置的架構(gòu)中,版本為2.1.1,修改以上代碼后,啟動(dòng)項(xiàng)目測(cè)試,問題沒有復(fù)現(xiàn),正常運(yùn)行。
同樣這個(gè)問題,也可以選擇升級(jí) Spring Cloud Gateway 版本,在官方2.1.2版本中,此處代碼已被重構(gòu),升級(jí)后測(cè)試也完全正常。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot?使用AOP?+?Redis?防止表單重復(fù)提交的方法
Spring?Boot是一個(gè)用于構(gòu)建Web應(yīng)用程序的框架,通過AOP可以實(shí)現(xiàn)防止表單重復(fù)提交,本文介紹了在Spring?Boot應(yīng)用程序中使用AOP和Redis來防止表單重復(fù)提交的方法,需要的朋友可以參考下2023-04-04SpringBoot webSocket實(shí)現(xiàn)發(fā)送廣播、點(diǎn)對(duì)點(diǎn)消息和Android接收
這篇文章主要介紹了SpringBoot webSocket實(shí)現(xiàn)發(fā)送廣播、點(diǎn)對(duì)點(diǎn)消息和Android接收,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03完美解決idea沒有tomcat server選項(xiàng)的問題
這篇文章主要介紹了完美解決idea沒有tomcat server選項(xiàng)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01淺談讓@Value更方便的Spring自定義轉(zhuǎn)換類
Spring為大家內(nèi)置了不少開箱即用的轉(zhuǎn)換類,如字符串轉(zhuǎn)數(shù)字、字符串轉(zhuǎn)時(shí)間等,但有時(shí)候需要使用自定義的屬性,則需要自定義轉(zhuǎn)換類了2021-06-06Java實(shí)現(xiàn)調(diào)用ElasticSearch?API的示例詳解
這篇文章主要為大家詳細(xì)介紹了Java調(diào)用ElasticSearch?API的效果資料,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2023-03-03Spring cloud Gateway簡(jiǎn)介及相關(guān)配置方法
這篇文章主要介紹了Spring cloud Gateway簡(jiǎn)介及相關(guān)配置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04