Closed lesterwang closed 4 years ago
问题收到,我先理解一下。
看你已经分析过源码了,方便分离一下问题,给一下 极简的(不涉及Tomcat)可运行复现问题的测试Demo代码吗?
如果能写成个UT就更好了: @lesterwang ❤️
@oldratlee 因为这个问题牵扯到
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask
这个内部类(非public的),不使用tomcat容器很难复现这个问题,使用springBoot 1.5.x版本,然后添加一个bean:
@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
return container -> {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container)
.setProtocol("org.apache.coyote.http11.Http11Nio2Protocol");
}
};
}
随意提供一个接口访问应该能复现这个问题。需要的话明天我再提供一个单独的项目方便测试。
这个问题产生的原因是: 当使用Http11Nio2Protocol协议处理请求时,会通过
ScheduledThreadPoolExecutor.reExecutePeriodic(RunnableScheduledFuture<?> task)
来添加任务,由于task既不是Runnable也不是Callable,所以不会被转换为TtlRunnable。 后续的执行都是使用ScheduledThreadPoolExecutor#DelayedWorkQueue这个内部类的方法,即使参数是Runnable也不会被TTL框架转换,最终加入到DelayedWorkQueue#queue队列里的任务是非TtlRunnable的。
同时Http11Nio2Protocol协议会通过
sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishRead(boolean var1)
方法来清理任务,该方法会调用到
java.util.concurrent.ThreadPoolExecutor.remove(Runnable task)
这时task会被转换为TtlRunnable,最终导致
ScheduledThreadPoolExecutor$DelayedWorkQueue.indexOf(Object x)
效率严重降低。
做了一个ScheduledThreadPoolExecutor
/ScheduledFutureTask
的使用Demo:
在使用TTL Agent
增强的情况下,与不使用TTL Agent
一样, @lesterwang
ScheduledFuture
的cancel
方法,清理/删除定时任务。 (功能一致)PS: 对于Agent方式通过脚本方便些来完成运行(省得设置Agent
参数等操作)
$ scripts/run-agent-for-class.sh com.alibaba.demo.scheduled_thread_pool_executor.ScheduledFutureTaskDemoKt
输出如下:
I'm a Runnable task, I'm working...
I'm a Runnable task, I'm working...
I'm a Runnable task, I'm working...
cancel
canceled: true # canceled成功之后,不再有定时任务的输出,即不再运行
Bye
对于 TTL Agent
运行方式,Debug的断住 ScheduledFuture
的cancel
方法:
可以确认
ScheduledFuture
实例 -> java.util.concurrent.Executors.RunnableAdapter
实例 -> task
(已经TTL Wrap过)因为整个 TTL
及其Java Agent
增强的实现 是
Executor
(如ScheduledThreadPoolExecutor
、ThreadPoolExecutor
)的API
/语义流程。Executor
内部具体的实现方式 所以使用(无论是一般的业务使用,还是Tomcat
的Executor
使用方式)
应该是不会有问题的。
如果Tomcat
的实现很 HACK 的,非常依赖的Executor
的内部具体的实现方式,是可能会出问题。
比如ScheduledThreadPoolExecutor.reExecutePeriodic
是内部的具体实现流程,如果Tomcat
的实现有直接调用,就很 HACK 了。
当然一般大家(含Tomcat
的同学)不会 设计/实现 成这样。 😄
# HACK 让 自己的实现脆弱了,会因为依赖不稳定而常常出问题要改。
(复现的问题 是指 与不用TTL Agent对比出的 基础功能 或 性能 不一致的地方)
我先Close这个Issue了, 如有可以运行复现问题的测试Demo/UT,请 @lesterwang 随时 Reopen 这个Issue。 🙌
在
Tomcat
容器使用Http11Nio2Protocol
协议时,请求的分发会执行到:其中
indexOf
的逻辑为:在使用
TTL Agent
增强的情况下,ThreadPoolExecutor.remove(Runnable task)
的参数task
会被转换为com.alibaba.ttl.TtlRunnable
,在执行语句时,执行结果永远为
false
,TtlRunnable
会和队列中的每一个任务进行比较,且结果为false
,在高并发的情况下,任务队列中的任务会有几千到几十万个不等,x.equals(queue[i])
成为一个严重影响性能的操作,且无法及时清理掉任务进一步影响性能。这种情况怎么处理比较合适呢?