分析Ratpack:ExecControllerBindingThreadFactory高CPU使用率和大量线程

时间:2018-02-16 12:51:02

标签: multithreading ratpack

我们有一个用Ratpack 1.5.1编写的移动应用API服务器即将上线,我们目前正在分析应用程序以捕获任何性能瓶颈。该应用程序由SQL数据库支持,我们要小心,始终使用Blocking类运行查询。代码是用Kotlin编写的,我们编写了一些协程胶代码,强制在Ratpack的阻塞线程上执行阻塞操作。

由于Ratpack的线程模型是独一无二的,我们希望确保这种情况是正常的:我们模拟了应用程序的2500个并发用户,我们的线程数达到400(甚至600个一点) ),其中大多数是ratpack-blocking-x-yyy个线程。

对CPU进行采样我们在ratpack.exec.internal.DefaultExecController$ExecControllerBindingThreadFactory.lambda$newThread$0方法中花费了92%的时间,但这可能是一个采样工件。

所以,问一下具体问题:给定Ratpack的线程模型,高阻塞线程计数正常,我们是否应该担心上述方法花费的高CPU时间?

2 个答案:

答案 0 :(得分:1)

Ratpack为阻塞操作创建无限(*)线程池。它是在DefaultExecController

中创建的
public DefaultExecController(int numThreads) {
    this.numThreads = numThreads;
    this.eventLoopGroup = ChannelImplDetector.eventLoopGroup(numThreads, new ExecControllerBindingThreadFactory(true, "ratpack-compute", Thread.MAX_PRIORITY));
    this.blockingExecutor = Executors.newCachedThreadPool(new ExecControllerBindingThreadFactory(false, "ratpack-blocking", Thread.NORM_PRIORITY));
}

在此池中创建的线程在阻塞操作完成后不会立即被杀死 - 它们在池中空闲并等待下一个作业执行。其背后的主要原因是保持线程处于空闲状态比在需要时生成新线程更便宜。这就是为什么当您模拟2500个并发用户调用和执行阻塞操作的端点时,您将在此池中看到2500个线程。创建的缓存线程池使用以下ThreadPoolExecutor对象:

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), threadFactory);
}

其中2147483647是最大池大小,60L是以秒为单位表示的TTL。这意味着执行程序服务将使这些线程保持60秒,并且当它们在60秒后不被重新使用时,它将清理它们。

实际上预计会出现这种情况下的高CPU。 2500个线程正在使用CPU的几个核心。它也很重要 - 你的SQL数据库在哪里运行?如果你在同一台机器上运行它,那么你的CPU工作就更难了。如果在阻塞线程池上运行的操作占用大量CPU时间,则必须优化这些阻塞操作。 Ratpack的强大功能来自异构和非阻塞体系结构 - 处理程序使用ratpack-compute线程池并将所有阻塞操作委派给ratpack-blocking,因此您的应用程序不会被阻止,并且可以处理大量请求。 / p>

(*)无限制在这种情况下意味着受可用内存的限制,或者如果你有足够的内存,它会被2147483647个线程限制(ExecutorService.newCachedThreadPool(factory)中使用此值)。

答案 1 :(得分:1)

只是建立在Szymon的答案上......

Ratpack本身并没有限制任何操作。这实际上取决于你。您可以选择使用https://ratpack.io/manual/current/api/ratpack/exec/Throttle.html来约束和排队对资源的访问。