您现在的位置是:亿华云 > 人工智能
记一次异步处理导致Jetty Request对象泄漏
亿华云2025-10-08 23:20:48【人工智能】4人已围观
简介最近排查一个bug,发现了一系列有意思的东西,对「自定义线程池」、「Jetty线程模型」都有了一些新的认识。本文预计阅读时间10分钟,包括:问题表现 常见原因筛查 根因与源
最近排查一个bug,记次发现了一系列有意思的异步东西,对「自定义线程池」、处理「Jetty线程模型」都有了一些新的导致对象认识。
本文预计阅读时间10分钟,泄漏包括:
问题表现 常见原因筛查 根因与源码分析 最佳实践 一些小TIPS1、记次问题表现
预发环境偶发请求失败异常,异步服务端显示错误信息为:
Required String parameter seriesbaid is not present对应controller的处理api为
乍一看,是导致对象一个非常简单的异常,请求参数里面没有带seriesbaid,泄漏导致失败。记次
但是异步,经过确认,处理前端请求参数已经携带了seriesbaid,导致对象而且为“偶发失败”,泄漏并不是常见的参数传递问题。
2、常见原因筛查
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、根因与源码分析
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对象。
4、最佳实践
不要给异步线程传递RequestAttributes对象并进行保存。
如果需要相关请求参数,可以新建上下文对象存储参数后进行传递。或者使用TransmittableThreadLocal。
5、一些小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也无从谈起。
后面主要根据代码的近期变更情况,发现了一个异步请求的引入,将异步改为同步后,发现就不会再出现这个问题了。
所以才从异步请求出发,多次尝试后,进行了稳定复现。
所以本次排查的一个重要收获,就是对于一些故障的排查,可以考虑从近期的「各种变更」中去寻找线索。
很赞哦!(95)
相关文章
- 5、使用企业名称的英文名称作为域名也是国内许多企业选择域名的一种方式,特别适合一些与计算机、网络和通信相关的行业。
- 3、不明先知,根据相关征兆预测可能发生的事件,以便提前做好准备,赶紧注册相关域名。;不差钱域名;buchaqian抢先注册,就是这种敏感类型。预言是最敏感的状态。其次,你应该有眼力。所谓眼力,就是善于从社会上时不时出现的各种热点事件中获取与事件相关的域名资源。眼力的前提是对域名领域的熟悉和丰富的知识。
- 为什么现在中文域名觉得好?使用中文域名有什么好处?
- 5、企业注册国内域名需要证件,其它情况一律不需要证件。
- 付款完成后,您只需耐心等待,如果您注册成功,系统会提示您。这里需要注意的是,域名是一个即时产品,只有在最终付款成功时才能预订,注册成功后不能更改。
- 4、参加域名拍卖会
- 公司在注册域名时还需要确保邮箱的安全性。如果邮箱不安全,它只会受到攻击。攻击者可以直接在邮箱中重置密码并攻击用户。因此,有必要注意邮箱的安全性。
- 2、定期提交和投标域名注册。例如,益华网络点击“立即预订”后,平台会抢先为客户注册域名。当然,一个域名可能会被多个客户预订,所以出价最高的人中标。
- Status、Creation Date、Expiration Date
- 4.选择顶级的域名注册服务商
站长推荐
域名不仅仅是一个简单的网站。对于有长远眼光的公司来说,在运营网站之前确定一个优秀的域名对有长远眼光的公司来说是非常重要的。这对今后的市场营销、产品营销和企业品牌建设都具有十分重要的意义。优秀的域名是企业在市场竞争中获得持久优势的利器。
tk域名是什么域名?新手对tk域名有什么看法?
公司在注册域名时还需要确保邮箱的安全性。如果邮箱不安全,它只会受到攻击。攻击者可以直接在邮箱中重置密码并攻击用户。因此,有必要注意邮箱的安全性。
解析之后一般在十分钟内生效,如果没有生效可以联系域名服务商进行沟通。
.com域名是国际最广泛流行的通用域名,目前全球注册量第一的域名,公司企业注册域名的首选。国际化公司通常会注册该类域名。
尽量不要在域名中出现特殊字符,这样的域名很容易导致访问者输入错误,同时给人留下不专业的印象,降低网站的可信度,并流失大量潜在客户。
互联网其实拼的也是人脉,域名投资也是一个时效性很强的东西,一个不起眼的消息就会引起整个域名投资市场的动荡,因此拓宽自己的人脉圈,完善自己的信息获取渠道,让自己能够掌握更为多样化的信息,这样才更有助于自己的域名投资。
以上的就是为大家介绍的关于域名的详解