日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

skywalking 引起 spring-cloud-gateway 的内存溢出 skywalking的bug

發布時間:2023/12/10 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 skywalking 引起 spring-cloud-gateway 的内存溢出 skywalking的bug 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家好,我是烤鴨:

???又是個線上問題記錄,這次坑慘了,開源軟件也不是萬能的,還是要做好壓測和灰度。

問題

上游反饋大量超時,不止某一個服務,查看服務沒有問題,猜測是網絡或者環境問題。

想到網關接入了skywaling(已接入24小時),回滾后問題消失。

堆內存在某個時間點后上升且無法回收。

Full GC 時間變得特別長…這個就是上游超時的原因

環境

cloud版本

<groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-gateway</artifactId> <version>2.2.3.RELEASE</version>

skywalking 版本

<artifactId>java-agent-sniffer</artifactId> <groupId>org.apache.skywalking</groupId> <version>8.9.0</version>

復現

說實話,當時我本地起了,壓測了一天,并沒有出現OOM的情況,事實證明,還是量不夠大。

后來同事找到了病根(下面兩種情況原因是一樣的)

  • TracingContext中的activeSpanStack在某些場景下沒有清空,就是下面這個。
  • 或者是

  • IgnoredTracerContext的 stackDepth 不等于0
  • 最終的結果都是 ContextManager.Context/RuntimeContext 未清空,導致內存泄露

    調試

    可以參考這篇文章 https://www.jianshu.com/p/ba9254f38fa5

    因為我只想調試網關相關包,把下載失敗的包和編譯失敗的包都注釋掉了,再在網關項目的導入module。

    導入完了,結構如下圖,該注釋的注釋,能編譯打包就行。

    斷點打在 gateway-2.1.x-plugin的幾個攔截器,可以看到debug成功

    源碼解析

    剩下就跟著代碼一步一步走了。

    幾個攔截器的順序是 NettyRoutingFilterInterceptor -> HttpClientFinalizerSendInterceptor -> HttpClientFinalizerResponseConnectionInterceptor

    可以看到 NettyRoutingFilterInterceptor 初次進入會執行 ContextManager.createLocalSpan

    創建span對象(全鏈路用到的流轉對象,感興趣的可以看看谷歌的dapper https://blog.csdn.net/ruizhikai_ztq/article/details/123663633)

    @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,MethodInterceptResult result) throws Throwable {ServerWebExchange exchange = (ServerWebExchange) allArguments[0];EnhancedInstance enhancedInstance = getInstance(exchange);AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/RoutingFilter");if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) {ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField());}span.setComponent(SPRING_CLOUD_GATEWAY); }

    createLocalSpan,這里的兩個實現,是否忽略trace,由于我引入了

    apm-trace-ignore-plugin-8.9.0.jar 這個包,實現會走 ignore的,也就是復現里的第二種情況

    這個方法里有一個棧深度 stackDepth 字段,只要創建span就會自增,刪除span就會自減。

    @Override public AbstractSpan createLocalSpan(String operationName) {stackDepth++;return NOOP_SPAN; }@Override public boolean stopSpan(AbstractSpan span) {stackDepth--;if (stackDepth == 0) {ListenerManager.notifyFinish(this);}return stackDepth == 0; }

    一般來說的話,方法的Interceptor的 beforeMethod 會執行

    ContextManager.createLocalSpan();

    afterMethod 會執行

    AbstractSpan span = ContextManager.activeSpan(); ContextManager.stopSpan(span);

    但是很多中間件的某些場景都是異步的,尤其網關是響應式的,所以入口和出口也可能在不同的類里。

    比如網關的 createLocalSpan 是在 NettyRoutingFilterInterceptor

    而 stopSpan 是在 HttpClientFinalizerSendInterceptor

    再看下上面的 stopSpan 方法的調用的地方

    stopSpan 方法返回值是根據 stackDepth 是否為0來的,如果 stackDepth != 0,返回false

    那這種就有點危險了,如果有方法觸發了 createLocalSpan 而后續沒有執行 stopSpan 就會出現內存無法回收。

    比如只執行了 NettyRoutingFilterInterceptor 而沒有執行 HttpClientFinalizerSendInterceptor

    網關異常代碼

    這種問題很長時間都沒有人反饋,說明還是小眾的。主要是我們寫的不規范也有一定的原因。(不要問,問就是開源全鍋)

    public class CorsResponseHeaderFilter implements GlobalFilter{@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.defer(() -> {exchange.getResponse().getHeaders().entrySet().stream().filter(kv -> !CollectionUtils.isEmpty(kv.getValue())).filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS)|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_MAX_AGE))).forEach(kv -> {kv.setValue(new ArrayList<String>() {{add(kv.getValue().get(0));}});});return chain.filter(exchange);}));}}

    這段代碼主要是解決網關跨域的問題,記得有一些后臺頁面對返回的頭有限制,所以做了這個邏輯處理,過濾一些響應頭和指定格式。

    乍一看沒啥問題,問題就出現在 Mono.defer,一般我們使用的多的是Mono.just。

    看一下官方文章這倆有啥區別 https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#create-java.util.function.Consumer-

    簡單介紹一下常用的api:

    Mono.just 餓漢式:立即執行

    Mono.defer 懶漢式:發布之后,等待訂閱者來執行(有延遲)

    Mono.create 完全自主控制:發布之后,自己添加/移除監聽器,并且手動寫回調

    問題解決

    饒了一大圈,在本應該skywalking 的gateway interceptor 走完了之后,stackDepth 為0。

    而 Mono.defer 操作,又進入了 NettyRoutingFilterInterceptor,執行了 createLocalSpan,stackDepth ++,再之后的CONTEXT就無法remove了,造成內存泄漏。

    訪問兩次之后就會出現這種情況了。

    同事已經給修了。

    https://github.com/apache/skywalking-java/pull/133

    同一個鏈路上 ServerWebExchange.getAttributes() 是一直有的,進入的時候放一次,再次進入判斷一下如果是同一個鏈路的話,就不再執行后面的代碼了(避免重復創建span)

    總結

    開源項目就是有這樣的魅力,發現其中問題,再fix提交。

    不過線上運行也確實是坑啊,之前有別的網關已經接過了,沒問題,就直接上了。

    但是每個網關項目本身也不一樣,一個小小的過濾器有這么大的能量。

    額外說一句,一定要灰度!!!

    總結

    以上是生活随笔為你收集整理的skywalking 引起 spring-cloud-gateway 的内存溢出 skywalking的bug的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。