ExecutorService任务执行间歇性延迟

时间:2014-07-16 16:19:04

标签: java multithreading executorservice

我在CentOS 6.4服务器上运行Java 7 Dropwizard应用程序,它基本上充当数据存储(Cassandra)之上的一个层,并进行一些额外的处理。它还有一个Zookeeper接口,使用Curator框架来处理其他一些东西。这一切都运行良好,大多数情况下,CPU和RAM负载从不超过50%,通常约为10%,我们的响应时间也很好。

我的问题是,最近我们发现偶尔我们会看到大约1-2秒的昙花一现,似乎所有通过线程池安排的任务都会被延迟。我们注意到了这一点,因为与Cassandra的连接超时和Zookeeper的会话超时。我们采取了哪些措施来缩小范围:

  1. 使用Wireshark和Boundary来确保我们的应用程序中的所有网络活动都停滞不前,而不仅仅是单个组件。所有网络活动都在同时停止。
  2. 写了一个快速的小Python脚本,将时间戳字符串发送到我们看到超时连接的服务器上的netcat,以确保它们不是盒子之间的整体网络问题。我们看到所有时间戳在我们的应用程序超时期间顺利通过。
  3. 在服务器上禁用超线程。
  4. 检查超时期限的垃圾收集时间日志。它们是一致的,并且在超时期限内不到1毫秒。
  5. 在超时期间检查了我们的CPU和RAM资源。再次,一致,并且承受很大的负担。
  6. 向我们的应用添加了一个额外的Dropwizard资源,用于诊断,将时间戳字符串发送到另一台服务器上的netcat,就像Python脚本一样。在这种情况下,当我们在应用程序中看到超时时,我们确实看到了时间戳的延迟。使用半秒ping时,我们通常会看到整整一秒丢失,然后在接下来的一秒内丢失四次,额外的两次是前一秒的延迟ping。
  7. 要从等式中删除网络,我们将上述内容更改为仅写入控制台和本地文件而不是网络。我们看到了同样的结果(延迟ping)。
  8. 描述并检查我们的线程池设置,看看我们是否使用了太多的OS线程。 /proc/sys/kernel/threads-max是190115,我们永远不会超过1000。
  9. #7代码(除了使用Socket和PrintWriter代替FileWriter外,#6相同):

    public void start() throws IOException {
        fileWriter = new FileWriter(this.fileName, false);
    
        executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(this, 0, this.delayMillis, TimeUnit.MILLISECONDS);
    }
    
    @Override
    public synchronized void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            Date now = new Date();
            String debugString = "ExecutorService test " + this.content + " : " + sdf.format(now) + "\n";
            fileWriter.write(debugString);
            fileWriter.flush();
        } catch (Exception e) {
            logger.error("Error running ExecutorService test: " + e.toString());
        }
    }
    

    所以似乎Executor正在安排要运行的任务,但它们在启动时被延迟(因为时间戳被延迟而且{{1}的前两行没有办法try方法中的块正在延迟任务执行)。关于什么可能导致我们可以尝试的这个或其他事情的任何想法?希望我们不会达到我们开始恢复代码的程度,直到找到导致它的变化为止......

    TL; DR:计划任务正在延迟,我们不知道原因。

    UPDATE 1:我们修改了执行程序任务,将每半秒的时间戳推送到环形缓冲区而不是直接输出到文件,然后每20秒转储一次缓冲区。这会删除I / O作为阻止任务执行的可能原因,但仍然提供相同的信息。从这一点开始,我们仍然看到了相同的时间戳模式,从中可以看出问题不是偶尔会阻塞下一次执行任务的任务,而是由于某种原因任务执行引擎本身会延迟执行。

2 个答案:

答案 0 :(得分:2)

当您使用scheduleAtFixedRate时,您表示希望您的任务尽可能接近该费率。遗嘱执行人将尽力保持,但有时却不能。

您使用Executors.newSingleThreadScheduledExecutor(),因此执行程序只有一个线程可供使用。如果任务的每次执行花费的时间都超过了您在计划中指定的时间,那么执行程序将无法跟上,因为单个线程可能没有在执行中执行计划之前执行上一次运行下一次运行。结果将表现为计划的延迟。这似乎是一个合理的解释,因为你说你的真实代码是写入套接字。这可以很容易地阻止和发送你的时间。

您可以通过在run方法的末尾添加更多日志记录(即在flush之后)来确定是否确实如此。如果IO花费的时间太长,您会在日志中看到。

作为修复,您可以考虑使用scheduleWithFixedDelay,这将在每次执行任务之间添加延迟,因此长时间运行的任务不会相互碰撞。如果失败了,那么你需要确保套接字写入按时完成,允许每个后续任务执行按计划开始。

答案 1 :(得分:0)

诊断活动问题的第一步通常是在系统停止时进行线程转储,并检查线程正在做什么。在您的情况下,执行程序线程将是特别感兴趣的。他们正在处理,还是在等待工作?

如果它们都在处理,则执行程序服务已耗尽工作线程,并且只能在当前任务完成后安排新任务。这可能是由于任务暂时需要更长时间才能完成。工作线程的堆栈跟踪可能会产生线索 需要更长的时间。

如果许多工作线程处于空闲状态,那么您在JDK中发现了一个错误。恭喜!