【发布时间】:2020-10-28 15:38:12
【问题描述】:
我遇到了一个不知道如何解决的问题。
我正在尝试并行化我们迄今为止按顺序完成的部分代码。为此,我将任务划分为几个较小的正交任务。
我已经创建了一个 executorService 并且正在运行:
executorService.invokeAll(callableList, timeBudget, TimeUnit.NANOSECONDS);
每个可调用对象中都有多个 IO 任务(例如访问数据库和外部服务),总时间预算为 200 毫秒 +-。使用invokeAll 的原因是因为我对所有请求都有一个总体时间预算。因此,我需要一种方法来通过单一预算限制所有期货。
为了测试我自己,我添加了不同的指标,这些指标可以报告给我们拥有的一些日志记录可视化工具。我注意到了:
- 该部分代码的中位(和第 75 个百分位)延迟更快。
- 第 95 个以上的百分位数实际上变得更糟了。
经过彻底调查(我对代码的不同部分进行了基准测试)后,我注意到invokeAll 第 99 个百分位的运行时间实际上是 500 毫秒,有时甚至更多。这件事真的搞砸了优化。关于可能导致这种情况的任何想法?还有其他建议吗? invokeAll 有替代品吗?
【问题讨论】:
-
你的线程池有多大,有多少任务?在顺序模式下,每个任务需要多长时间?您是否添加了日志记录以确认您的 IO 操作正在阻塞线程,以便在这些外部调用中获得所需的并发性?你可能会饿死那些超过 95% 的线程。
-
当您并行运行所有程序时,您可能还会遇到诸如耗尽的数据库连接池和 http 客户端池之类的限制。总体而言,您需要衡量并找到瓶颈。
-
@JoeW 我们可以将代码分为 3 个任务 - 数据库、请求和将所有线程合并为一个数据结构的代码。没有并行性的数据库任务大约需要 40 毫秒,而有并行性大约需要 20 毫秒。第三方请求耗时 60ms,并行耗时 45-50ms,合并任务几乎不需要。
-
@ewramner 的注释很好。另外,您可以发布执行程序服务的创建和配置吗?如果您使用较小的固定大小的线程池会发生什么?甚至可以使用 1 的线程池运行,以确保更新后的重构按预期运行,并且得到与历史顺序代码相同的结果,并且不会在某处挂起资源。
-
您的任务可能已排队。您可以针对每个任务检测任务的创建时间、开始运行时间和结束时间。然后,您将获得开始减去创建的等待时间和结束减去创建的总时间。如果等待时间很长,则应归咎于固定线程池。编辑: System.currentTimeMillis() 应该足够好了。
标签: java concurrency parallel-processing executorservice