森林舞会游戏 APP下载

记一次异步处理惩罚导致Jetty Request工具走漏

发布日期:2022-08-07 06:29    点击次数:173

比来排查一个bug,缔造白一系列有意思的货物,对「自定义线程池」、「Jetty线程模型」都有了一些新的熟习。

本文预计浏览时光10分钟,蕴含:

成就表现 罕见启事筛查 根因与源码阐发 最好实际 一些小TIPS 一、成就表现

预发情形偶发要求失利很是,服务端表现舛误信息为:

Required String parameter 'seriesbaid' is not present 

对应controller的api为

乍一看,是一个极度俭朴的很是,要求参数内里没有带seriesbaid,导致失利。

然则,颠末确认,前端要求参数已经携带了seriesbaid,而且为“偶发失利”,着实不是罕见的参数通报成就。

二、罕见启事筛查 2.1 网关参数遗失?

因为前端要求抵达后端服务中会颠末网关,所以一同头思疑是否网关遗失了通报参数。

颠末 调用链阐发,在偶发的失利的要求中,也确认已经通报了querystring。所以网关没有遗失参数通报。

2.2 不凡字符导致参数转换失利 ?

既然已经通报了querystring到后端服务,那末一种罕见的启事,因为queryString中带有不凡字符而导致剖析成queryParam失利了。

会是这个成就吗?

我们经由过程在服务中继承一个spring-web的OncePerRequestFilter,对要求参数举行打印。

在偶发的失利的要求中,找到了下列日志

2021-12-29 15:36:05,536 INFO [com.xxx.interceptor.RequestLoggingFilter] - shouldLog - swanparameter:traceId:fb2266d3687911ecb5f3cf045ea19ac3; query:seriesbaid=3FO4K4SLX2IW&x_plugin=custom&x_bz=&locale=zh_CN&x_resourceId=&x_resourceVersion=; parameterMap:{}; 

相比遗憾,我们确认了要求中确凿有querystring而没有告成剖析为queryParam,然则这个querystring中,并无期冀的不凡字符,讲情理是可以或许剖析告成的。

既然罕见启事没法说明,只能去源码捞一把了。

2.3 去源码捞一把

我们的网络容器运用了jetty,所以HttpServletRequest的实现是jetty的Request类。

Request类中,对queryString的剖析是在 getParameters() 的时光。

我们缔造,当很是要求出去的时光,这里的鉴定

_queryParameter竟然不是null,而是一个空工具。

而畸形要求,这里鉴定_queryParameter为null,尔后举行剖析。

所以,照旧要从源码去阐发了。

三、根因与源码阐发 3.1 _queryParamter为何不是null了?

我们经由过程在Request类中配置多个断点,找到了启事。摒挡进程以下图所示。

1)同步要求A倏地实现前去。

当要求A出去,在一次Http要求终止后(controller编制前去客户端),会举行响应的recycle()操作,这里蕴含Requst工具执行recycle()编制,清理相干参数,蕴含_queryParameters。

2)异步使命耽误照顾,在recycle()后从头配置了_queryParameter属性。

在要求A执进步程中,运用「自定义线程池」异步执行了一个编制B(编制较慢)。编制B中,从RequestContextHolder中获患了HttpServletRequest,尔后经由过程request.getParameter()取得要求头。

因为此时_queryParameters为null,因而extractQueryParameters()编制就剖析了一个空的工具放出来。

3)新要求C进入,前去很是。

当新的要求C进入后端服务,拿到了同一个Request工具,因为此时_queryParameters不为null,因而跳过了extractQueryParameters(),导致该当剖析的queryString没法被剖析,产品中心controller抛出很是。

总结:一旦主线程执行终了,实现recycle进程,而异步线程执行较慢,异步线程中的任何request.getParameter()动作会破坏Request工具的recycle,导致_queryParameters属性为空工具而不是null,从而导致新的要求失利。

3.2 异步线程中,RequestContextHolder还能拿到Request工具?(基本启事)

我们晓得RequestContextHolder是基于ThreadLocal实现的。因而,在异步线程中,是没法间接经由过程

RequestContextHolder.getRequestAttributes()取得主线程的HttpServletRequest。

成就出在了「自定义线程池」

ThreadPoolExecutorWithMonitor中。

内里自定义实现了一个外部类DecorateRunnableTask来处理惩罚使命。

外部类DecorateRunnableTask继承了外部类DecorateTask,生活生涯了主线程的RequestAttributes工具。

尔后在异步线程执行前,经由过程before()编制配置到了今后列程的RequestContextHolder中。

总结:给异步线程通报RequestAttributes工具,是形成Request工具走漏的基本启事!

3.3 两个要求,为何会同享一个Request工具?

原本上面的阐发根抵已经找到了Bug的启事,然则我仔细想了下,又感应有点稀罕。

两个要求,为何会同享一个Request工具?

假定是运用了相干池化技能,那怎么能在两个要求找到同一个工具,尔后奔忙动复现呢?因而,又延续去研究了下jetty的相干内容。

jetty 9.x总体架构图:

SelectorManager + ManagedSelector +QueuedThreadPool 形成为了「Reactor线程模型」。关于一个http要求,SelectorManager分派给某一个ManagedSelector创立HttpConnection工具,尔后在QueuedThreadPool中执行响应的IO操作。

HttpConnection工具持有HttpChannel工具,HttpChannel中持有了Request工具(就是HttpServletRequest)。

网关到后端服务之间运用的是Http要求,默以为长跟尾,因而,在短时光内的新的要求(长跟尾终止前),会复用同一个HttpConnection工具。

四、最好实际

不要给异步线程通报RequestAttributes工具并举行生活生涯。

假定需求相干要求参数,可以或许新建凹凸文工具存储参数后举行通报。或许运用TransmittableThreadLocal。

五、一些小TIPS 5.1 jetty源码不成家

在对jetty的Request类举行debug时,一同头这里遇到一个小坑,idea一贯源码成家不上。从github上把 jetty源码拉上去,根据引入的jetty版本举行外埠mvn install,照旧不一致。

痛处pom的寄托阐发,可以或许看到引入的jetty版本为9.4.12。

其后倏忽想起来,这个名目诚然是springboot名目,然则着实不是打成jar包经由过程内置jetty容器启动的。而是打成为了war包,外埠经由过程jetty-maven-plugin的jetty:run启动的。这里运用的jetty版本为9.4.9。

所以,我们需求根据jetty-maven-plugin的版原本抉择jetty的源码。

5.2 「偶提成就」难以复现

推敲到篇幅启事与浏览休会,本文在排查进程中,没有开展分化一个极度费力之处————外埠怎么奔忙动复现「偶提成就」很是要求。

着实排查进程中,外埠奔忙动复现淹灭了大量时光。假定不是外埠可以或许奔忙动复现,后面的debug也无从谈起。

后面首要痛处代码的近期厘革情形,缔造白一个异步要求的引入,将异步改成同步后,缔作育不会再出现这个成就了。

所以才从异步要求停航,屡次查验测验后,举行了奔忙动复现。

所以本次排查的一个首要功劳,就是关于一些体系毛病的排查,可以或许推敲从近期的「种种厘革」中去寻找线索。