首页 > 其他分享 >记录Arthas在一次性能调优过程中实践

记录Arthas在一次性能调优过程中实践

时间:2023-07-19 17:25:38浏览次数:48  
标签:java kt 实践 okhttp3 调优 线程 Arthas undertow null

背景
  使用jmeter对系统进行压力测试,该业务流程请求大致调用:jmeter压力机 ——>  A系统 ——>  B系统 ——> A系统.   A 系统作为基础平台,请求先到A系统,然后转到具体的B业务系统,B接口逻辑中需要调用A系统查询基础数据。

问题描述
  当使用高并发访问系统时,整个系统卡住,A系统和B系统的所有接口都不能访问。

问题定位
  1、通过grafana,查看A、B服务器资源(CPU、内存、带宽、磁盘等)均没有到达瓶颈,所以应该不是机器资源的问题,排除;
  2、查看A、B系统JVM垃圾回收情况 jstat -gc pid 1000 50,查看50次垃圾回收情况,基本没啥变化,暂时排除fullgc;
  3、查看A、B系统线程栈:jstack -l pid |grep java.lang.Thread.State | awk '{print $2$3$4$5}' | sort | uniq -c,发现有较多的runnable线程,使用jstack打印线程栈,看看都是什么线程?

"XNIO-1 task-32" #3995 prio=5 os_prio=0 tid=0x00007f52e8077000 nid=0x3ed4 runnable [0x00007f526b97a000]
   java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:171)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at okio.InputStreamSource.read(JvmOkio.kt:90)
    at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:129)
    at okio.RealBufferedSource.indexOf(RealBufferedSource.kt:427)
    at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:320)
    at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29)
    at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:178)
    at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:106)
    at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:79)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
    at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
    at feign.okhttp.OkHttpClient.execute(OkHttpClient.java:180)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:121)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:91)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
    ...... 
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423)
    at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
    at java.lang.Thread.run(Thread.java:750)

问题分析

  为什么出现这么多挂起的Socket连接线程?
  1、网上查资料说未设置超时时间Read Timeout会导致Socket挂起,参考:JDK-8075484,检查A、B系统的nacos里的配置了设置超时时间。

  (1)检查nacos里配置,发现有配置,7200000,2小时

  (2)配置了,但是有没有生效呢?spring会使用自动配置类来实例化这些配置,通过idea在jar包里一顿查找,最后找到了Feign相关的配置类:org.springframework.cloud.openfeign.FeignClientProperties

  readTimeout在org.springframework.cloud.openfeign.FeignClientProperties属性config里,config是一个hashMap,想到可以使用arthas的vmtool来查看readTimeout的值

[arthas@32383]$ vmtool --action getInstances --className org.springframework.cloud.openfeign.FeignClientProperties -x 4
@FeignClientProperties[][
    @FeignClientProperties[
        defaultToProperties=@Boolean[true],
        defaultConfig=@String[default],
        config=@HashMap[
            @String[default]:@FeignClientConfiguration[
                loggerLevel=null,
                connectTimeout=@Integer[3000],
                readTimeout=@Integer[7200000],
                retryer=null,
                errorDecoder=null,
                requestInterceptors=null,
                defaultRequestHeaders=null,
                defaultQueryParameters=null,
                decode404=null,
                decoder=null,
                encoder=null,
                contract=null,
                exceptionPropagationPolicy=null,
                capabilities=null,
                queryMapEncoder=null,
                metrics=null,
                followRedirects=null,
            ],
        ],
        decodeSlash=@Boolean[true],
    ],
]

注意:-x 4 是展开第四层,FeignClientProperties是第一层,config是第二层,FeignClientConfiguration是第三层,readTimeout就是第四层。

  2、排除了未设置readTimeout,还有什么其他原因呢?系统为什么卡住呢,是不是工作线程配置的太少,然后检查Springboot web容器里的工作线程数,这里我走了点弯路,我想当然的认为A、B系统里使用内置的Tomcat,然后检查了Spring boot 里Tomcat的工作线程配置,但是问题仍然未解决?然后就继续来看这个jatsck日志,发现栈的起始调用有大量的io.undertow日志,这是什么呢?经过上网一顿搜索,原来Springboot默认内置了三种web容器:tomcat、jetty、undertow,参考:undertow吊打tomcat,undertow处理高并发场景的性能 优于tomcat和tomcat,因此很多系统默认选择undertow作为web容器。通过pom或者启动日志可以确认。

  

  通过查看A、B系统pom配置,确认使用的都是undertow容器。

  3、既然确认使用了undertow容器,那看看系统里配置undertow的工作线程是多少?检查nacos里配置

  

  通过arthas thread命令查看A系统总线程数191,B系统总线程数183,也没有超过worker-threads,是不是配置没有生效呢?继续在idea里寻找undertow的自动配置类,一顿查找找到了:

org.springframework.boot.autoconfigure.web.ServerProperties,层级关系

ServerProperties
  undertow
    threads
      io
      worker

这里发现了问题:配置类的Io线程和工作线程的属性是:io和worker,不是io-threads,worker-threads,通过vmtool查看目前系统的工作线程数和Io线程数:

[arthas@5850]$ vmtool --action getInstances --className org.springframework.boot.autoconfigure.web.ServerProperties --express 'instances[0].undertow.threads'
@Threads[
    io=@Integer[4],
    worker=@Integer[32],
]
或者
[arthas@5850]$ vmtool --action getInstances --className org.springframework.boot.autoconfigure.web.ServerProperties -x 4
@ServerProperties[][
    @ServerProperties[
        port=@Integer[8083],
        address=null,
        ....
        undertow=@Undertow[
            ...
            threads=@Threads[
                io=@Integer[4],
                worker=@Integer[32],
            ],
            options=@Options[
                socket=@LinkedHashMap[isEmpty=true;size=0],
                server=@LinkedHashMap[isEmpty=true;size=0],
            ],
        ],
    ],
]

通过查看实例属性,工作线程数和Io线程数是默认值:io数等于核数,工作线程数=io数*8,所以目前系统里真正可用的工作线程数就是32。而nacos里配置的undertow的工作线程数由于变量不正确导致没有生效。

  4、目前A、B系统里工作线程都是32,如何确认这32各工作线程都被使用了呢?还是通过jstack日志来确认。由于undertow产生的工作线程名称是名字的,一般是:XNIO-n task开始,n可以取1,2,3...;IO线程名称:XNIO-n I/O,n可以取1,2,3...。那我们直接去jstack里取统计一下:

[root@nodeA ~]# jstack -l 5850 |grep "XNIO-1 I/O" |wc -l
4
[root@nodeA ~]# jstack -l 5850 |grep "XNIO-1 task" |wc -l
32

通过日志来看,IO线程和工作线程确实占满了。

综上分析,当jmeter发起高并发请求时,A系统工作线程迅速打满32各工作线程来访问B系统,B系统当然也会启动32各工作线程来应对这些请求,但是B系统这32个线程需要向A系统再发起32个请求来查询基础数据,但是此时A系统没有额外的工作线程来响应B系统的请求,这样B系统向A系统发起的socket连接就会挂起,进而导致A系统调用B系统的32个线程也被挂起;就形成了目前系统所有接口无法访问的现象。那什么时候才会返回呢,看到上面配置的超时时间:7200000ms(2小时)后超时断开,请求才会返回。这里超时时间设置也不合理,2个小时太长了,黄花菜都凉了,应该限制在几秒以内,当然这需要优化接口的性能,这里先不修改超时时间配置,等优化代码后再调整。所以,调整undertow工作线程数配置

改完之后,通过arthas查看,A、B系统的工作线程数都增加了,再次通过jmeter压测,吞吐量增加了,系统也不再卡住了。

 

标签:java,kt,实践,okhttp3,调优,线程,Arthas,undertow,null
From: https://www.cnblogs.com/cac2020/p/17566193.html

相关文章

  • 让代码优雅起来:记一次代码微重构实践
    一、需求开发修改代码一次需求开发时碰到如下所示方法代码:privateOrderShoudSettlementAmountgetOrderShoudSettlementAmount(OrderDTOorderMain,List<SettlementDetail>details){OrderShoudSettlementAmountsettlementAmount=newOrderShoudSettlementAmount();......
  • springcloud - kafka实践
    springcloud可以通过KafkaTemplate来发布消息,让后消费者使用来订阅@KafkaListener主题消息。一、添加依赖1<dependencyManagement>2<dependencies>3<dependency>4<groupId>org.springframework.cloud</groupId>5<artifactId&g......
  • Python日志模块:实战应用与最佳实践
    本文详细解析了Python的logging模块,从基本介绍到实际应用和最佳实践。我们通过具体的代码示例解释了如何高效地使用这个模块进行日志记录,以及如何避免常见的陷阱,旨在帮助读者更好地掌握这个强大的工具。一、Python日志模块简介日志的概念及其在软件开发中的作用在开发过程中,......
  • springboot - kafka实践
    Kafka是一个开源的分布式流处理平台,由Apache软件基金会开发和维护。它是一种高性能、可持久化、可扩展的消息队列系统,常用于解决大规模数据传输和处理的问题。以下是Kafka的一些核心概念和主要特点:消息和主题:Kafka基于发布订阅模式,消息被发布到一个或多个主题(Topic)中。每条消......
  • Java的SPI机制实践
    JavaSPI机制概述先给出结论:“Java的SPI是一种服务发现机制,用于约定接口和动态发现实现类,体现了分层解耦的思想”。Java的SPI机制常用于框架扩展或组件替换,最常见的JavaSPI应用就是JDBCDriver,JDK提供了java.sql.Driver接口,却将具体的实现交给了相应的数据库驱动,比如:在mysql-co......
  • 六月学习之Haproxy ACL实践(基于路径调度)
    2、ACL场景实践2.3、ACL案例-基于路径调度#根据用户请求的URL。调度到不同的后端集群用户通过/static调度到172.16.1.7:80用户请求/user调度到172.16.1.8:802.3.1、配置后端节点#web1:/static站点定义cat/etc/nginx/conf.d/www.qingchen.com.confserver{listen80;......
  • Sealos Web UI 公有云部署实践
    不管部署任何开源的产品,首先看他的官网文档或者github的readme、wiki等等这是 sealosgithub readmehttps://github.com/labring/sealos/blob/main/deploy/cloud/README.md  一、准备一台服务器你可以使用自己本地电脑安装vmware创建一台虚拟机,操作系统Ubuntu我这里......
  • Zipkin链路监控实践
    Zipkin是一种开源的分布式链路追踪系统,可以用于监控和跟踪微服务架构中的请求调用链。它可以帮助定位和解决分布式系统中的延迟问题,提供对请求的可视化跟踪和监控。一、引入依赖      使用项目来启动Zipkin,创建一个springbootweb项目添加zipkin依赖1<groupId>co......
  • 重新整理 .net core 实践篇———承载[外篇]
    前言简单介绍一下承载。正文名称叫做承载,其实就是.netcore定义的一套长期运行的服务的规范。这个服务可以是web服务,也可以是其他服务,比如tcp,或者一些监控服务。这里以监控服务为例子:publicclassPerformanceMetrics{ privatestaticreadonlyRandom_random=newRa......
  • RLChina2022-实践课三:强化学习算法
    MDP算法MDP被定义为一个元组(S,A,P,r,R)S:所有状态集合A:在环境力里面智能体所作动作的集合P:状态转移函数P(s'|s,a),智能体在当前s下,执行a之后,转移到是s'的概率R:奖励函数R(s,a),表示在环境s下执行动作a之后获得的立即奖励,有时候还需要知道s'是多少才能共同决定奖励是多少。......