阿里问题定位神器 Arthas 的骚操作,定位线上BUG,超给力

原文:https://mp.weixin.qq.com/s/Y58PFhCgbC-GBjj_sHxvDg

 

 

 

 

公司有个渠道系统,专门对接三方渠道使用,没有什么业务逻辑,主要是转换报文和参数校验之类的工作,起着一个承上启下的作用。

最近在优化接口的响应时间,优化了代码之后,但是时间还是达不到要求;有一个诡异的100ms左右的耗时问题,在接口中打印了请求处理时间后,和调用方的响应时间还有差了100ms左右。比如程序里记录150ms,但是调用方等待时间却为250ms左右。

下面记录下当时详细的定位&解决流程(其实解决很简单,关键在于怎么定位并找到解决问题的方法)

定位过程

分析代码

渠道系统是一个常见的spring-boot web工程,使用了集成的tomcat。分析了代码之后,发现并没有特殊的地方,没有特殊的过滤器或者拦截器,所以初步排除是业务代码问题

分析调用流程

出现这个问题之后,首先确认了下接口的调用流程。由于是内部测试,所以调用流程较少。

Nginx -反向代理-> 渠道系统

公司是云服务器,网络走的也是云的内网。由于不明确问题的原因,所以用排除法,首先确认服务器网络是否有问题。

先确认发送端到Nginx Host是否有问题:

 

 

 从ping结果上看,发送端到Nginx主机的延迟是无问题的,接下来查看Nginx到渠道系统的网络。

 

 

 

从ping结果上看,Nginx到渠道系统服务器网络延迟也是没问题的

既然网络看似没问题,那么可以继续排除法,砍掉Nginx,客户端直接再渠道系统的服务器上,通过回环地址(localhost)直连,避免经过网卡/dns,缩小问题范围看看能否复现(这个应用和地址是我后期模拟的,测试的是一个空接口):

 

 

 

从curl日志上看,通过回环地址调用一个空接口耗时也有73ms。这就奇怪了,跳过了中间所有调用节点(包括过滤器&拦截器之类),直接请求应用一个空接口,都有73ms的耗时,再请求一次看看:

 

 

 

更奇怪的是,第二次请求耗时就正常了,变成了3ms。经查阅资料,linux curl是默认开启http keep-alive的(Keep-Alive的介绍可以参考我的另一篇文章)。就算不开启keep-alive,每次重新handshake,也不至于需要70ms。

经过不断分析测试发现,连续请求的话时间就会很短,每次请求只需要几毫秒,但是如果隔一段时间再请求,就会花费70ms以上。

从这个现象猜想,可能是某些缓存机制导致的,连续请求因为有缓存,所以速度快,时间长缓存失效后导致时间长。

那么这个问题点到底在哪一层呢?tomcat层还是spring-webmvc呢?

光猜想定位不了问题,还是得实际测试一下,把渠道系统的代码放到本地ide里启动测试能否复现

但是导入本地Ide后,在Ide中启动后并不能复现问题,并没有70+ms的延迟问题。这下头疼了,本地无法复现,不能Debug,由于问题点不在业务代码,也不能通过加日志的方式来Debug

这时候可以祭出神器Arthas了

Arthas分析问题

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  • 这个类从哪个 jar 包加载的?

  • 为什么会报各种类相关的 Exception?

  • 我改的代码为什么没有执行到?

  • 难道是我没 commit?分支搞错了?

  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?

  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!

  • 是否有一个全局视角来查看系统的运行状况?

  • 有什么办法可以监控到JVM的实时运行状态?

上面是Arthas的官方简介,这次我只需要用他的一个小功能trace。动态计算方法调用路径和时间,这样我就可以定位时间在哪个地方被消耗了。

trace 方法内部调用路径,并输出方法路径上的每个节点上耗时 trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。

有了神器,那么该追踪什么方法呢?由于我对Tomcat源码不是很熟,所以只能从spring mvc下手,先来trace一下spring mvc的入口:

[
arthas@24851
]
$ trace org
.
springframework
.
web
.
servlet
.
DispatcherServlet
 
*
Press
 Q 
or
 
Ctrl
+
C to abort
.
Affect
(
class
-
cnt
:
1
 
,
 method
-
cnt
:
44
)
 cost 
in
 
508
 ms
.
`---ts=2019-09-14 21:07:44;thread_name=http-nio-7744-exec-2;id=11;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@7c136917
    `
---[
2.952142ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
buildLocaleContext
()

`---ts=2019-09-14 21:07:44;thread_name=http-nio-7744-exec-2;id=11;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@7c136917
    `
---[
18.08903ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
doService
()
        
+---[
0.041346ms
]
 org
.
apache
.
commons
.
logging
.
Log
:
isDebugEnabled
()
 
#889
        
+---[
0.022398ms
]
 org
.
springframework
.
web
.
util
.
WebUtils
:
isIncludeRequest
()
 
#898
        
+---[
0.014904ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
getWebApplicationContext
()
 
#910
        
+---[
1.071879ms
]
 javax
.
servlet
.
http
.
HttpServletRequest
:
setAttribute
()
 
#910
        
+---[
0.020977ms
]
 javax
.
servlet
.
http
.
HttpServletRequest
:
setAttribute
()
 
#911
        
+---[
0.017073ms
]
 javax
.
servlet
.
http
.
HttpServletRequest
:
setAttribute
()
 
#912
        
+---[
0.218277ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
getThemeSource
()
 
#913
        
|
   
`---[0.137568ms] org.springframework.web.servlet.DispatcherServlet:getThemeSource()
        |       `
---[
min
=
0.00783ms
,
max
=
0.014251ms
,
total
=
0.022081ms
,
count
=
2
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
getWebApplicationContext
()
 
#782
        
+---[
0.019363ms
]
 javax
.
servlet
.
http
.
HttpServletRequest
:
setAttribute
()
 
#913
        
+---[
0.070694ms
]
 org
.
springframework
.
web
.
servlet
.
FlashMapManager
:
retrieveAndUpdate
()
 
#916
        
+---[
0.01839ms
]
 org
.
springframework
.
web
.
servlet
.
FlashMap
:<
init
>()
 
#920
        
+---[
0.016943ms
]
 javax
.
servlet
.
http
.
HttpServletRequest
:
setAttribute
()
 
#920
        
+---[
0.015268ms
]
 javax
.
servlet
.
http
.
HttpServletRequest
:
setAttribute
()
 
#921
        
+---[
15.050124ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
doDispatch
()
 
#925
        
|
   
`---[14.943477ms] org.springframework.web.servlet.DispatcherServlet:doDispatch()
        |       +---[0.019135ms] org.springframework.web.context.request.async.WebAsyncUtils:getAsyncManager() #953
        |       +---[2.108373ms] org.springframework.web.servlet.DispatcherServlet:checkMultipart() #960
        |       |   `
---[
2.004436ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
checkMultipart
()
        
|
       
|
       
`---[1.890845ms] org.springframework.web.multipart.MultipartResolver:isMultipart() #1117
        |       +---[2.054361ms] org.springframework.web.servlet.DispatcherServlet:getHandler() #964
        |       |   `
---[
1.961963ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
getHandler
()
        
|
       
|
       
+---[
0.02051ms
]
 java
.
util
.
List
:
iterator
()
 
#1183
        
|
       
|
       
+---[
min
=
0.003805ms
,
max
=
0.009641ms
,
total
=
0.013446ms
,
count
=
2
]
 java
.
util
.
Iterator
:
hasNext
()
 
#1183
        
|
       
|
       
+---[
min
=
0.003181ms
,
max
=
0.009751ms
,
total
=
0.012932ms
,
count
=
2
]
 java
.
util
.
Iterator
:
next
()
 
#1183
        
|
       
|
       
+---[
min
=
0.005841ms
,
max
=
0.015308ms
,
total
=
0.021149ms
,
count
=
2
]
 org
.
apache
.
commons
.
logging
.
Log
:
isTraceEnabled
()
 
#1184
        
|
       
|
       
`---[min=0.474739ms,max=1.19145ms,total=1.666189ms,count=2] org.springframework.web.servlet.HandlerMapping:getHandler() #1188
        |       +---[0.013071ms] org.springframework.web.servlet.HandlerExecutionChain:getHandler() #971
        |       +---[0.372236ms] org.springframework.web.servlet.DispatcherServlet:getHandlerAdapter() #971
        |       |   `
---[
0.280073ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
getHandlerAdapter
()
        
|
       
|
       
+---[
0.004804ms
]
 java
.
util
.
List
:
iterator
()
 
#1224
        
|
       
|
       
+---[
0.003668ms
]
 java
.
util
.
Iterator
:
hasNext
()
 
#1224
        
|
       
|
       
+---[
0.003038ms
]
 java
.
util
.
Iterator
:
next
()
 
#1224
        
|
       
|
       
+---[
0.006451ms
]
 org
.
apache
.
commons
.
logging
.
Log
:
isTraceEnabled
()
 
#1225
        
|
       
|
       
`---[0.012683ms] org.springframework.web.servlet.HandlerAdapter:supports() #1228
        |       +---[0.012848ms] javax.servlet.http.HttpServletRequest:getMethod() #974
        |       +---[0.013132ms] java.lang.String:equals() #975
        |       +---[0.003025ms] org.springframework.web.servlet.HandlerExecutionChain:getHandler() #977
        |       +---[0.008095ms] org.springframework.web.servlet.HandlerAdapter:getLastModified() #977
        |       +---[0.006596ms] org.apache.commons.logging.Log:isDebugEnabled() #978
        |       +---[0.018024ms] org.springframework.web.context.request.ServletWebRequest:<init>() #981
        |       +---[0.017869ms] org.springframework.web.context.request.ServletWebRequest:checkNotModified() #981
        |       +---[0.038542ms] org.springframework.web.servlet.HandlerExecutionChain:applyPreHandle() #986
        |       +---[0.00431ms] org.springframework.web.servlet.HandlerExecutionChain:getHandler() #991
        |       +---[4.248493ms] org.springframework.web.servlet.HandlerAdapter:handle() #991
        |       +---[0.014805ms] org.springframework.web.context.request.async.WebAsyncManager:isConcurrentHandlingStarted() #993
        |       +---[1.444994ms] org.springframework.web.servlet.DispatcherServlet:applyDefaultViewName() #997
        |       |   `
---[
0.067631ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
applyDefaultViewName
()
        
|
       
+---[
0.012027ms
]
 org
.
springframework
.
web
.
servlet
.
HandlerExecutionChain
:
applyPostHandle
()
 
#998
        
|
       
+---[
0.373997ms
]
 org
.
springframework
.
web
.
servlet
.
DispatcherServlet
:
processDispatchResult
()
 
#1008
        
|
       
|
   
`---[0.197004ms] org.springframework.web.servlet.DispatcherServlet:processDispatchResult()
        |       |       +---[0.007074ms] org.apache.commons.logging.Log:isDebugEnabled() #1075
        |       |       +---[0.005467ms] org.springframework.web.context.request.async.WebAsyncUtils:getAsyncManager() #1081
        |       |       +---[0.004054ms] org.springframework.web.context.request.async.WebAsyncManager:isConcurrentHandlingStarted() #1081
        |       |       `
---[
0.011988ms
]
 org
.
springframework
.
web
.
servlet
.
HandlerExecutionChain
:
triggerAfterCompletion
()
 
#1087
        
|
       
`---[0.004015ms] org.springframework.web.context.request.async.WebAsyncManager:isConcurrentHandlingStarted() #1018
        +---[0.005055ms] org.springframework.web.context.request.async.WebAsyncUtils:getAsyncManager() #928
        `
---[
0.003422ms
]
 org
.
springframework
.
web
.
context
.
request
.
async
.
WebAsyncManager
:
isConcurrentHandlingStarted
()
 
#928

  

[
jboss@VM_10_91_centos tmp
]
$ curl 
-
w 
"@curl-time.txt"
 http
:
//127.0.0.1:7744/send
success
              http
:
 
200
               dns
:
 
0.001s
          redirect
:
 
0.000s
      time_connect
:
 
0.001s
   time_appconnect
:
 
0.000s
  time_pretransfer
:
 
0.001s
time_starttransfer
:
 
0.115s
     size_download
:
 
7bytes
    speed_download
:
 
60.000B
/
s
                  
----------
        time_total
:
 
0.115s

  

本次调用,调用端时间花费115ms,但是从arthas trace上看,spring mvc只消耗了18ms,那么剩下的97ms去哪了呢?

本地测试后已经可以排除spring mvc的问题了,最后也是唯一可能出问题的点就是tomcat

可是本人并不熟悉tomcat中的源码,就连请求入口都不清楚,tomcat里需要trace的类都不好找。。。

不过没关系,有神器Arthas,可以通过stack命令来反向查找调用路径,以org.springframework.web.servlet.DispatcherServlet作为参数:

stack 输出当前方法被调用的调用路径 很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

[
arthas@24851
]
$ stack org
.
springframework
.
web
.
servlet
.
DispatcherServlet
 
*
Press
 Q 
or
 
Ctrl
+
C to abort
.
Affect
(
class
-
cnt
:
1
 
,
 method
-
cnt
:
44
)
 cost 
in
 
495
 ms
.
ts
=
2019
-
09
-
14
 
21
:
15
:
19
;
thread_name
=
http
-
nio
-
7744
-
exec
-
5
;
id
=
14
;
is_daemon
=
true
;
priority
=
5
;
TCCL
=
org
.
springframework
.
boot
.
web
.
embedded
.
tomcat
.
TomcatEmbeddedWebappClassLoader@7c136917
    
@org
.
springframework
.
web
.
servlet
.
FrameworkServlet
.
processRequest
()
        at org
.
springframework
.
web
.
servlet
.
FrameworkServlet
.
doGet
(
FrameworkServlet
.
java
:
866
)
        at javax
.
servlet
.
http
.
HttpServlet
.
service
(
HttpServlet
.
java
:
635
)
        at org
.
springframework
.
web
.
servlet
.
FrameworkServlet
.
service
(
FrameworkServlet
.
java
:
851
)
        at javax
.
servlet
.
http
.
HttpServlet
.
service
(
HttpServlet
.
java
:
742
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
231
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
apache
.
tomcat
.
websocket
.
server
.
WsFilter
.
doFilter
(
WsFilter
.
java
:
52
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
RequestContextFilter
.
doFilterInternal
(
RequestContextFilter
.
java
:
99
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
HttpPutFormContentFilter
.
doFilterInternal
(
HttpPutFormContentFilter
.
java
:
109
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
HiddenHttpMethodFilter
.
doFilterInternal
(
HiddenHttpMethodFilter
.
java
:
81
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
CharacterEncodingFilter
.
doFilterInternal
(
CharacterEncodingFilter
.
java
:
200
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
apache
.
catalina
.
core
.
StandardWrapperValve
.
invoke
(
StandardWrapperValve
.
java
:
198
)
        at org
.
apache
.
catalina
.
core
.
StandardContextValve
.
invoke
(
StandardContextValve
.
java
:
96
)
        at org
.
apache
.
catalina
.
authenticator
.
AuthenticatorBase
.
invoke
(
AuthenticatorBase
.
java
:
496
)
        at org
.
apache
.
catalina
.
core
.
StandardHostValve
.
invoke
(
StandardHostValve
.
java
:
140
)
        at org
.
apache
.
catalina
.
valves
.
ErrorReportValve
.
invoke
(
ErrorReportValve
.
java
:
81
)
        at org
.
apache
.
catalina
.
core
.
StandardEngineValve
.
invoke
(
StandardEngineValve
.
java
:
87
)
        at org
.
apache
.
catalina
.
connector
.
CoyoteAdapter
.
service
(
CoyoteAdapter
.
java
:
342
)
        at org
.
apache
.
coyote
.
http11
.
Http11Processor
.
service
(
Http11Processor
.
java
:
803
)
        at org
.
apache
.
coyote
.
AbstractProcessorLight
.
process
(
AbstractProcessorLight
.
java
:
66
)
        at org
.
apache
.
coyote
.
AbstractProtocol$ConnectionHandler
.
process
(
AbstractProtocol
.
java
:
790
)
        at org
.
apache
.
tomcat
.
util
.
net
.
NioEndpoint$SocketProcessor
.
doRun
(
NioEndpoint
.
java
:
1468
)
        at org
.
apache
.
tomcat
.
util
.
net
.
SocketProcessorBase
.
run
(
SocketProcessorBase
.
java
:
49
)
        at java
.
util
.
concurrent
.
ThreadPoolExecutor
.
runWorker
(
ThreadPoolExecutor
.
java
:
1149
)
        at java
.
util
.
concurrent
.
ThreadPoolExecutor$Worker
.
run
(
ThreadPoolExecutor
.
java
:
624
)
        at org
.
apache
.
tomcat
.
util
.
threads
.
TaskThread$WrappingRunnable
.
run
(
TaskThread
.
java
:
61
)
        at java
.
lang
.
Thread
.
run
(
Thread
.
java
:
748
)

ts
=
2019
-
09
-
14
 
21
:
15
:
19
;
thread_name
=
http
-
nio
-
7744
-
exec
-
5
;
id
=
14
;
is_daemon
=
true
;
priority
=
5
;
TCCL
=
org
.
springframework
.
boot
.
web
.
embedded
.
tomcat
.
TomcatEmbeddedWebappClassLoader@7c136917
    
@org
.
springframework
.
web
.
servlet
.
DispatcherServlet
.
doService
()
        at org
.
springframework
.
web
.
servlet
.
FrameworkServlet
.
processRequest
(
FrameworkServlet
.
java
:
974
)
        at org
.
springframework
.
web
.
servlet
.
FrameworkServlet
.
doGet
(
FrameworkServlet
.
java
:
866
)
        at javax
.
servlet
.
http
.
HttpServlet
.
service
(
HttpServlet
.
java
:
635
)
        at org
.
springframework
.
web
.
servlet
.
FrameworkServlet
.
service
(
FrameworkServlet
.
java
:
851
)
        at javax
.
servlet
.
http
.
HttpServlet
.
service
(
HttpServlet
.
java
:
742
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
231
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
apache
.
tomcat
.
websocket
.
server
.
WsFilter
.
doFilter
(
WsFilter
.
java
:
52
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
RequestContextFilter
.
doFilterInternal
(
RequestContextFilter
.
java
:
99
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
HttpPutFormContentFilter
.
doFilterInternal
(
HttpPutFormContentFilter
.
java
:
109
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
HiddenHttpMethodFilter
.
doFilterInternal
(
HiddenHttpMethodFilter
.
java
:
81
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
springframework
.
web
.
filter
.
CharacterEncodingFilter
.
doFilterInternal
(
CharacterEncodingFilter
.
java
:
200
)
        at org
.
springframework
.
web
.
filter
.
OncePerRequestFilter
.
doFilter
(
OncePerRequestFilter
.
java
:
107
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
internalDoFilter
(
ApplicationFilterChain
.
java
:
193
)
        at org
.
apache
.
catalina
.
core
.
ApplicationFilterChain
.
doFilter
(
ApplicationFilterChain
.
java
:
166
)
        at org
.
apache
.
catalina
.
core
.
StandardWrapperValve
.
invoke
(
StandardWrapperValve
.
java
:
198
)
        at org
.
apache
.
catalina
.
core
.
StandardContextValve
.
invoke
(
StandardContextValve
.
java
:
96
)
        at org
.
apache
.
catalina
.
authenticator
.
AuthenticatorBase
.
invoke
(
AuthenticatorBase
.
java
:
496
)
        at org
.
apache
.
catalina
.
core
.
StandardHostValve
.
invoke
(
StandardHostValve
.
java
:
140
)
        at org
.
apache
.
catalina
.
valves
.
ErrorReportValve
.
invoke
(
ErrorReportValve
.
java
:
81
)
        at org
.
apache
.
catalina
.
core
.
StandardEngineValve
.
invoke
(
StandardEngineValve
.
java
:
87
)
        at org
.
apache
.
catalina
.
connector
.
CoyoteAdapter
.
service
(
CoyoteAdapter
.
java
:
342
)
        at org
.
apache
.
coyote
.
http11
.
Http11Processor
.
service
(
Http11Processor
.
java
:
803
)
        at org
.
apache
.
coyote
.
AbstractProcessorLight
.
process
(
AbstractProcessorLight
.
java
:
66
)
        at org
.
apache
.
coyote
.
AbstractProtocol$ConnectionHandler
.
process
(
AbstractProtocol
.
java
:
790
)
        at org
.
apache
.
tomcat
.
util
.
net
.
NioEndpoint$SocketProcessor
.
doRun
(
NioEndpoint
.
java
:
1468
)
        at org
.
apache
.
tomcat
.
util
.
net
.
SocketProcessorBase
.
run
(
SocketProcessorBase
.
java
:
49
)
        at java
.
util
.
concurrent
.
ThreadPoolExecutor
.
runWorker
(
ThreadPoolExecutor
.
java
:
1149
)
        at java
.
util
.
concurrent
.
ThreadPoolExecutor$Worker
.
run
(
ThreadPoolExecutor
.
java
:
624
)
        at org
.
apache
.
tomcat
.
util
.
threads
.
TaskThread$WrappingRunnable
.
run
(
TaskThread
.
java
:
61
)
        at java
.
lang
.
Thread
.
run
(
Thread
.
java
:
748
)

  从stack日志上可以很直观的看出DispatchServlet的调用栈,那么这么长的路径,该trace哪个类呢(这里跳过spring mvc中的过滤器的trace过程,实际排查的时候也trace了一遍,但这诡异的时间消耗不是由这里过滤器产生的)?有一定经验的老司机从名字上大概也能猜出来从哪里下手比较好,那就是org.apache.coyote.http11.Http11Processor.service,从名字上看,http1.1处理器,这可能是一个比较好的切入点。下面来trace一下:

[
arthas@24851
]
$ trace org
.
apache
.
coyote
.
http11
.
Http11Processor
 service
Press
 Q 
or
 
Ctrl
+
C to abort
.
Affect
(
class
-
cnt
:
1
 
,
 method
-
cnt
:
1
)
 cost 
in
 
269
 ms
.
`---ts=2019-09-14 21:22:51;thread_name=http-nio-7744-exec-8;id=17;is_daemon=true;priority=5;TCCL=org.springframework.boot.loader.LaunchedURLClassLoader@20ad9418
    `
---[
131.650285ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
service
()
        
+---[
0.036851ms
]
 org
.
apache
.
coyote
.
Request
:
getRequestProcessor
()
 
#667
        
+---[
0.009986ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#668
        
+---[
0.008928ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
setSocketWrapper
()
 
#671
        
+---[
0.013236ms
]
 org
.
apache
.
coyote
.
http11
.
Http11InputBuffer
:
init
()
 
#672
        
+---[
0.00981ms
]
 org
.
apache
.
coyote
.
http11
.
Http11OutputBuffer
:
init
()
 
#673
        
+---[
min
=
0.00213ms
,
max
=
0.007317ms
,
total
=
0.009447ms
,
count
=
2
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#683
        
+---[
min
=
0.002098ms
,
max
=
0.008888ms
,
total
=
0.010986ms
,
count
=
2
]
 org
.
apache
.
coyote
.
ErrorState
:
isError
()
 
#683
        
+---[
min
=
0.002448ms
,
max
=
0.007149ms
,
total
=
0.009597ms
,
count
=
2
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
isAsync
()
 
#683
        
+---[
min
=
0.002399ms
,
max
=
0.00852ms
,
total
=
0.010919ms
,
count
=
2
]
 org
.
apache
.
tomcat
.
util
.
net
.
AbstractEndpoint
:
isPaused
()
 
#683
        
+---[
min
=
0.033587ms
,
max
=
0.11832ms
,
total
=
0.151907ms
,
count
=
2
]
 org
.
apache
.
coyote
.
http11
.
Http11InputBuffer
:
parseRequestLine
()
 
#687
        
+---[
0.005384ms
]
 org
.
apache
.
tomcat
.
util
.
net
.
AbstractEndpoint
:
isPaused
()
 
#695
        
+---[
0.007924ms
]
 org
.
apache
.
coyote
.
Request
:
getMimeHeaders
()
 
#702
        
+---[
0.006744ms
]
 org
.
apache
.
tomcat
.
util
.
net
.
AbstractEndpoint
:
getMaxHeaderCount
()
 
#702
        
+---[
0.012574ms
]
 org
.
apache
.
tomcat
.
util
.
http
.
MimeHeaders
:
setLimit
()
 
#702
        
+---[
0.14319ms
]
 org
.
apache
.
coyote
.
http11
.
Http11InputBuffer
:
parseHeaders
()
 
#703
        
+---[
0.003997ms
]
 org
.
apache
.
coyote
.
Request
:
getMimeHeaders
()
 
#743
        
+---[
0.026561ms
]
 org
.
apache
.
tomcat
.
util
.
http
.
MimeHeaders
:
values
()
 
#743
        
+---[
min
=
0.002869ms
,
max
=
0.01203ms
,
total
=
0.014899ms
,
count
=
2
]
 java
.
util
.
Enumeration
:
hasMoreElements
()
 
#745
        
+---[
0.070114ms
]
 java
.
util
.
Enumeration
:
nextElement
()
 
#746
        
+---[
0.010921ms
]
 java
.
lang
.
String
:
toLowerCase
()
 
#746   +---[0.008453ms] java.lang.String:contains() #746
        
+---[
0.002698ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#775
        
+---[
0.00307ms
]
 org
.
apache
.
coyote
.
ErrorState
:
isError
()
 
#775
        
+---[
0.002708ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#777
        
+---[
0.171139ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
prepareRequest
()
 
#779
        
+---[
0.009349ms
]
 org
.
apache
.
tomcat
.
util
.
net
.
SocketWrapperBase
:
decrementKeepAlive
()
 
#794
        
+---[
0.002574ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#800
        
+---[
0.002696ms
]
 org
.
apache
.
coyote
.
ErrorState
:
isError
()
 
#800
        
+---[
0.002499ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#802
        
+---[
0.005641ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getAdapter
()
 
#803
        
+---[
129.868916ms
]
 org
.
apache
.
coyote
.
Adapter
:
service
()
 
#803
        
+---[
0.003859ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#809
        
+---[
0.002365ms
]
 org
.
apache
.
coyote
.
ErrorState
:
isError
()
 
#809
        
+---[
0.003844ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
isAsync
()
 
#809
        
+---[
0.002382ms
]
 org
.
apache
.
coyote
.
Response
:
getStatus
()
 
#809
        
+---[
0.002476ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
statusDropsConnection
()
 
#809
        
+---[
0.002284ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#838
        
+---[
0.00222ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
isAsync
()
 
#839
        
+---[
0.037873ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
endRequest
()
 
#843
        
+---[
0.002188ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#845
        
+---[
0.002112ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#849
        
+---[
0.002063ms
]
 org
.
apache
.
coyote
.
ErrorState
:
isError
()
 
#849
        
+---[
0.002504ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
isAsync
()
 
#853
        
+---[
0.009808ms
]
 org
.
apache
.
coyote
.
Request
:
updateCounters
()
 
#854
        
+---[
0.002008ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#855
        
+---[
0.002192ms
]
 org
.
apache
.
coyote
.
ErrorState
:
isIoAllowed
()
 
#855
        
+---[
0.01968ms
]
 org
.
apache
.
coyote
.
http11
.
Http11InputBuffer
:
nextRequest
()
 
#856
        
+---[
0.010065ms
]
 org
.
apache
.
coyote
.
http11
.
Http11OutputBuffer
:
nextRequest
()
 
#857
        
+---[
0.002576ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#870
        
+---[
0.016599ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
processSendfile
()
 
#872
        
+---[
0.008182ms
]
 org
.
apache
.
coyote
.
http11
.
Http11InputBuffer
:
getParsingRequestLinePhase
()
 
#688
        
+---[
0.0075ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
handleIncompleteRequestLineRead
()
 
#690
        
+---[
0.001979ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setStage
()
 
#875
        
+---[
0.001981ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
getErrorState
()
 
#877
        
+---[
0.001934ms
]
 org
.
apache
.
coyote
.
ErrorState
:
isError
()
 
#877
        
+---[
0.001995ms
]
 org
.
apache
.
tomcat
.
util
.
net
.
AbstractEndpoint
:
isPaused
()
 
#877
        
+---[
0.002403ms
]
 org
.
apache
.
coyote
.
http11
.
Http11Processor
:
isAsync
()
 
#879
        
`---[0.006176ms] org.apache.coyote.http11.Http11Processor:isUpgrade() #881

日志里有一个129ms的耗时点(时间比没开arthas的时候更长是因为arthas本身带来的性能消耗,所以生产环境小心使用),这个就是要找的问题点。

打问题点找到了,那怎么定位是什么导致的问题呢,又如何解决呢?

继续trace吧,细化到具体的代码块或者内容。trace由于性能考虑,不会展示所有的调用路径,如果调用路径过深,只有手动深入trace,原则就是trace耗时长的那个方法:

[
arthas@24851
]
$ trace org
.
apache
.
coyote
.
Adapter
 service
Press
 Q 
or
 
Ctrl
+
C to abort
.
Affect
(
class
-
cnt
:
1
 
,
 method
-
cnt
:
1
)
 cost 
in
 
608
 ms
.
`---ts=2019-09-14 21:34:33;thread_name=http-nio-7744-exec-1;id=10;is_daemon=true;priority=5;TCCL=org.springframework.boot.loader.LaunchedURLClassLoader@20ad9418
    `
---[
81.70999ms
]
 org
.
apache
.
catalina
.
connector
.
CoyoteAdapter
:
service
()
        
+---[
0.032546ms
]
 org
.
apache
.
coyote
.
Request
:
getNote
()
 
#302
        
+---[
0.007148ms
]
 org
.
apache
.
coyote
.
Response
:
getNote
()
 
#303
        
+---[
0.007475ms
]
 org
.
apache
.
catalina
.
connector
.
Connector
:
getXpoweredBy
()
 
#324
        
+---[
0.00447ms
]
 org
.
apache
.
coyote
.
Request
:
getRequestProcessor
()
 
#331
        
+---[
0.007902ms
]
 java
.
lang
.
ThreadLocal
:
get
()
 
#331
        
+---[
0.006522ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setWorkerThreadName
()
 
#331
        
+---[
73.793798ms
]
 org
.
apache
.
catalina
.
connector
.
CoyoteAdapter
:
postParseRequest
()
 
#336
        
+---[
0.001536ms
]
 org
.
apache
.
catalina
.
connector
.
Connector
:
getService
()
 
#339
        
+---[
0.004469ms
]
 org
.
apache
.
catalina
.
Service
:
getContainer
()
 
#339
        
+---[
0.007074ms
]
 org
.
apache
.
catalina
.
Engine
:
getPipeline
()
 
#339
        
+---[
0.004334ms
]
 org
.
apache
.
catalina
.
Pipeline
:
isAsyncSupported
()
 
#339
        
+---[
0.002466ms
]
 org
.
apache
.
catalina
.
connector
.
Request
:
setAsyncSupported
()
 
#339
        
+---[
6.01E-4ms
]
 org
.
apache
.
catalina
.
connector
.
Connector
:
getService
()
 
#342
        
+---[
0.001859ms
]
 org
.
apache
.
catalina
.
Service
:
getContainer
()
 
#342
        
+---[
9.65E-4ms
]
 org
.
apache
.
catalina
.
Engine
:
getPipeline
()
 
#342
        
+---[
0.005231ms
]
 org
.
apache
.
catalina
.
Pipeline
:
getFirst
()
 
#342
        
+---[
7.239154ms
]
 org
.
apache
.
catalina
.
Valve
:
invoke
()
 
#342
        
+---[
0.006904ms
]
 org
.
apache
.
catalina
.
connector
.
Request
:
isAsync
()
 
#345
        
+---[
0.00509ms
]
 org
.
apache
.
catalina
.
connector
.
Request
:
finishRequest
()
 
#372
        
+---[
0.051461ms
]
 org
.
apache
.
catalina
.
connector
.
Response
:
finishResponse
()
 
#373
        
+---[
0.007244ms
]
 java
.
util
.
concurrent
.
atomic
.
AtomicBoolean
:<
init
>()
 
#379
        
+---[
0.007314ms
]
 org
.
apache
.
coyote
.
Response
:
action
()
 
#380
        
+---[
0.004518ms
]
 org
.
apache
.
catalina
.
connector
.
Request
:
isAsyncCompleting
()
 
#382
        
+---[
0.001072ms
]
 org
.
apache
.
catalina
.
connector
.
Request
:
getContext
()
 
#394
        
+---[
0.007166ms
]
 java
.
lang
.
System
:
currentTimeMillis
()
 
#401
        
+---[
0.004367ms
]
 org
.
apache
.
coyote
.
Request
:
getStartTime
()
 
#401
        
+---[
0.011483ms
]
 org
.
apache
.
catalina
.
Context
:
logAccess
()
 
#401
        
+---[
0.0014ms
]
 org
.
apache
.
coyote
.
Request
:
getRequestProcessor
()
 
#406
        
+---[
min
=
8.0E-4ms
,
max
=
9.22E-4ms
,
total
=
0.001722ms
,
count
=
2
]
 java
.
lang
.
Integer
:<
init
>()
 
#406
        
+---[
0.001082ms
]
 java
.
lang
.
reflect
.
Method
:
invoke
()
 
#406
        
+---[
0.001851ms
]
 org
.
apache
.
coyote
.
RequestInfo
:
setWorkerThreadName
()
 
#406
        
+---[
0.035805ms
]
 org
.
apache
.
catalina
.
connector
.
Request
:
recycle
()
 
#410
        
`---[0.007849ms] org.apache.catalina.connector.Response:recycle() #411

一段无聊的手动深入trace之后………………

[
arthas@24851
]
$ trace org
.
apache
.
catalina
.
webresources
.
AbstractArchiveResourceSet
 getArchiveEntries
Press
 Q 
or
 
Ctrl
+
C to abort
.
Affect
(
class
-
cnt
:
4
 
,
 method
-
cnt
:
2
)
 cost 
in
 
150
 ms
.
`---ts=2019-09-14 21:36:26;thread_name=http-nio-7744-exec-3;id=12;is_daemon=true;priority=5;TCCL=org.springframework.boot.loader.LaunchedURLClassLoader@20ad9418
    `
---[
75.743681ms
]
 org
.
apache
.
catalina
.
webresources
.
JarWarResourceSet
:
getArchiveEntries
()
        
+---[
0.025731ms
]
 java
.
util
.
HashMap
:<
init
>()
 
#106
        
+---[
0.097729ms
]
 org
.
apache
.
catalina
.
webresources
.
JarWarResourceSet
:
openJarFile
()
 
#109
        
+---[
0.091037ms
]
 java
.
util
.
jar
.
JarFile
:
getJarEntry
()
 
#110
        
+---[
0.096325ms
]
 java
.
util
.
jar
.
JarFile
:
getInputStream
()
 
#111
        
+---[
0.451916ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:<
init
>()
 
#113
        
+---[
min
=
0.001175ms
,
max
=
0.001176ms
,
total
=
0.002351ms
,
count
=
2
]
 java
.
lang
.
Integer
:<
init
>()
 
#114
        
+---[
0.00104ms
]
 java
.
lang
.
reflect
.
Method
:
invoke
()
 
#114
        
+---[
0.045105ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
getNextJarEntry
()
 
#114
        
+---[
min
=
5.02E-4ms
,
max
=
0.008531ms
,
total
=
0.028864ms
,
count
=
31
]
 java
.
util
.
jar
.
JarEntry
:
getName
()
 
#116
        
+---[
min
=
5.39E-4ms
,
max
=
0.022805ms
,
total
=
0.054647ms
,
count
=
31
]
 java
.
util
.
HashMap
:
put
()
 
#116
        
+---[
min
=
0.004452ms
,
max
=
34.479307ms
,
total
=
74.206249ms
,
count
=
31
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
getNextJarEntry
()
 
#117
        
+---[
0.018358ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
getManifest
()
 
#119
        
+---[
0.006429ms
]
 org
.
apache
.
catalina
.
webresources
.
JarWarResourceSet
:
setManifest
()
 
#120
        
+---[
0.010904ms
]
 org
.
apache
.
tomcat
.
util
.
compat
.
JreCompat
:
isJre9Available
()
 
#121
        
+---[
0.003307ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
getMetaInfEntry
()
 
#133
        
+---[
5.5E-4ms
]
 java
.
util
.
jar
.
JarEntry
:
getName
()
 
#135
        
+---[
6.42E-4ms
]
 java
.
util
.
HashMap
:
put
()
 
#135
        
+---[
0.001981ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
getManifestEntry
()
 
#137
        
+---[
0.064484ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
close
()
 
#141
        
+---[
0.007961ms
]
 org
.
apache
.
catalina
.
webresources
.
JarWarResourceSet
:
closeJarFile
()
 
#151
        
`---[0.004643ms] java.io.InputStream:close() #155

发现了一个值得暂停思考的点:


+---[min=0.004452ms,max=34.479307ms,total=74.206249ms,count=31] org.apache.catalina.webresources.TomcatJarInputStream:getNextJarEntry() #117

这行代码加载了31次,一共耗时74ms;从名字上看,应该是tomcat加载jar包时的耗时,那么是加载了31个jar包的耗时,还是加载了jar包内的某些资源31次耗时呢?

TomcatJarInputStream这个类源码的注释写到:

The purpose of this sub-class is to obtain references to the JarEntry objects for META-INF/ and META-INF/MANIFEST.MF that are otherwise swallowed by the JarInputStream implementation. 大概意思也就是,获取jar包内META-INF/,META-INF/MANIFEST的资源,这是一个子类,更多的功能在父类JarInputStream里。

其实看到这里大概也能猜到问题了,tomcat加载jar包内META-INF/,META-INF/MANIFEST的资源导致的耗时,至于为什么连续请求不会耗时,应该是tomcat的缓存机制(下面介绍源码分析)

不着急定位问题,试着通过Arthas最终定位问题细节,继续手动深入trace

[
arthas@24851
]
$ trace org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
 
*
Press
 Q 
or
 
Ctrl
+
C to abort
.
Affect
(
class
-
cnt
:
1
 
,
 method
-
cnt
:
4
)
 cost 
in
 
44
 ms
.
`---ts=2019-09-14 21:37:47;thread_name=http-nio-7744-exec-5;id=14;is_daemon=true;priority=5;TCCL=org.springframework.boot.loader.LaunchedURLClassLoader@20ad9418
    `
---[
0.234952ms
]
 org
.
apache
.
catalina
.
webresources
.
TomcatJarInputStream
:
createZipEntry
()
        
+---[
0.039455ms
]
 java
.
util
.
jar
.
JarInputStream
:
createZipEntry
()
 
#43
        
`---[0.007827ms] java.lang.String:equals() #44

`
---
ts
=
2019
-
09
-
14
 
21
:
37
:
47
;
thread_name
=
http
-
nio
-
7744
-
exec
-
5
;
id
=
14
;
is_daemon
=
true
;
priority
=
5
;
TCCL
=
org
.
springframework
.
boot
.
loader
.
LaunchedURLClassLoader@20ad9418
    
`---[0.050222ms] org.apache.catalina.webresources.TomcatJarInputStream:createZipEntry()
        +---[0.001889ms] java.util.jar.JarInputStream:createZipEntry() #43
        `
---[
0.001643ms
]
 java
.
lang
.
String
:
equals
()
 
#46
#这里一共31个trace日志,删减了剩下的

从方法名上看,还是加载资源之类的意思。都已经到jdk源码了,这时候来看一下TomcatJarInputStream这个类的源码:

/**
 * Creates a new <code>JarEntry</code> (<code>ZipEntry</code>) for the
 * specified JAR file entry name. The manifest attributes of
 * the specified JAR file entry name will be copied to the new
 * <CODE>JarEntry</CODE>.
 *
 * @param name the name of the JAR/ZIP file entry
 * @return the <code>JarEntry</code> object just created
 */
protected
 
ZipEntry
 createZipEntry
(
String
 name
)
 
{
    
JarEntry
 e 
=
 
new
 
JarEntry
(
name
);
    
if
 
(
man 
!=
 
null
)
 
{
        e
.
attr 
=
 man
.
getAttributes
(
name
);
    
}
    
return
 e
;
}

这个createZipEntry有个name参数,从注释上看,是jar/zip文件名,如果能得到文件名这种关键信息,就可以直接定位问题了;还是通过Arthas,使用watch命令,动态监测方法调用数据

watch方法执行数据观测

让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。

watch 该方法的入参

  1. [arthas@24851]$ watch org.apache.catalina.webresources.TomcatJarInputStream createZipEntry "{params[0]}"

  2. Press Q or Ctrl+C to abort.

  3. Affect(class-cnt:1 , method-cnt:1) cost in 27 ms.

  4. ts=2019-09-14 21:51:14; [cost=0.14547ms] result=@ArrayList[

  5. @String[META-INF/],

  6. ]

  7. ts=2019-09-14 21:51:14; [cost=0.048028ms] result=@ArrayList[

  8. @String[META-INF/MANIFEST.MF],

  9. ]

  10. ts=2019-09-14 21:51:14; [cost=0.046071ms] result=@ArrayList[

  11. @String[META-INF/resources/],

  12. ]

  13. ts=2019-09-14 21:51:14; [cost=0.033855ms] result=@ArrayList[

  14. @String[META-INF/resources/swagger-ui.html],

  15. ]

  16. ts=2019-09-14 21:51:14; [cost=0.039138ms] result=@ArrayList[

  17. @String[META-INF/resources/webjars/],

  18. ]

  19. ts=2019-09-14 21:51:14; [cost=0.033701ms] result=@ArrayList[

  20. @String[META-INF/resources/webjars/springfox-swagger-ui/],

  21. ]

  22. ts=2019-09-14 21:51:14; [cost=0.033644ms] result=@ArrayList[

  23. @String[META-INF/resources/webjars/springfox-swagger-ui/favicon-16x16.png],

  24. ]

  25. ts=2019-09-14 21:51:14; [cost=0.033976ms] result=@ArrayList[

  26. @String[META-INF/resources/webjars/springfox-swagger-ui/springfox.css],

  27. ]

  28. ts=2019-09-14 21:51:14; [cost=0.032818ms] result=@ArrayList[

  29. @String[META-INF/resources/webjars/springfox-swagger-ui/swagger-ui-standalone-preset.js.map],

  30. ]

  31. ts=2019-09-14 21:51:14; [cost=0.04651ms] result=@ArrayList[

  32. @String[META-INF/resources/webjars/springfox-swagger-ui/swagger-ui.css],

  33. ]

  34. ts=2019-09-14 21:51:14; [cost=0.034793ms] result=@ArrayList[

  35. @String[META-INF/resources/webjars/springfox-swagger-ui/swagger-ui.js.map],

这下直接看到了具体加载的资源名,这么熟悉的名字:swagger-ui,一个国外的rest接口文档工具,又有国内开发者基于swagger-ui做了一套spring mvc的集成工具,通过注解就可以自动生成swagger-ui需要的接口定义json文件,用起来还比较方便,就是侵入性较强。

删除swagger的jar包后问题,诡异的70+ms就消失了

  1. <!--pom 里删除这两个引用,这两个包时国内开发者封装的,swagger-ui并没有提供java spring-mvc的支持包,swagger只是一个浏览器端的ui+editor

  2. <dependency>

  3. <groupId>io.springfox</groupId>

  4. <artifactId>springfox-swagger2</artifactId>

  5. <version>2.9.2</version>

  6. </dependency>

  7. <dependency>

  8. <groupId>io.springfox</groupId>

  9. <artifactId>springfox-swagger-ui</artifactId>

  10. <version>2.9.2</version>

  11. </dependency>

那么为什么swagger会导致请求耗时呢,为什么每次请求偶读会加载swagger内部的静态资源呢?

其实这是tomcat-embed的一个bug吧,下面详细介绍一下该Bug

Tomcat embed Bug分析&解决 源码分析过程实在太漫长,而且也不是本文的重点,所以就不介绍了, 下面直接介绍下分析结果

顺便贴一张tomcat处理请求的核心类图

 

 

为什么每次请求会加载Jar包内的静态资源 关键在于org.apache.catalina.mapper.Mapper#internalMapWrapper这个方法,该版本下处理请求的方式有问题,导致每次都校验静态资源。

为什么连续请求不会出现问题

因为Tomcat对于这种静态资源的解析是有缓存的,优先从缓存查找,缓存过期后再重新解析。具体参考org.apache.catalina.webresources.Cache,默认过期时间ttl是5000ms。

为什么本地不会复现

其实确切的说,是通过spring-boot打包插件后不能复现。由于启动方式的不同,tomcat使用了不同的类去处理静态资源,所以没问题

如何解决

升级tomcat-embed版本即可

当前出现Bug的版本为:

spring-boot:2.0.2.RELEASE,内置的tomcat embed版本为8.5.31

升级tomcat embed版本至8.5.40+即可解决此问题,新版本已经修复了

通过替换springboot pom properties方式 如果项目是maven是继承的springboot,即parent配置为springboot的,或者dependencyManagement中import spring boot包的

  1. <parent>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-parent</artifactId>

  4. <version>2.0.2.RELEASE</version>

  5. <relativePath/> <!-- lookup parent from repository -->

  6. </parent>

pom中直接覆盖properties即可:

  1. <properties>

  2. <tomcat.version>8.5.40</tomcat.version>

  3. </properties>

升级spring boot版本

springboot 2.1.0.RELEASE中的tomcat embed版本已经大于8.5.31了,所以直接将springboot升级至该版本及以上版本就可以解决此问题。

posted @ 2020-03-07 16:00  这个名字想了很久~  阅读(492)  评论(0编辑  收藏  举报