Multithreading 评测Ratpack:ExecControllerBindingReadFactory高CPU使用率和大量线程

Multithreading 评测Ratpack:ExecControllerBindingReadFactory高CPU使用率和大量线程,multithreading,ratpack,Multithreading,Ratpack,我们有一个用Ratpack 1.5.1编写的移动应用程序API服务器即将上线,我们目前正在分析该应用程序,以捕获任何性能瓶颈。该应用程序由SQL数据库支持,我们会小心地始终使用Blocking类运行查询。代码是用Kotlin编写的,我们编写了一些coroutine胶水代码来强制在Ratpack的阻塞线程上执行阻塞操作 由于Ratpack的线程模型是唯一的,我们希望确保这种情况是正常的:我们模拟了应用程序的2500个并发用户,我们的线程数增加到400个(甚至一度达到600个),其中大多数是Ratp

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

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

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

因此,要问具体的问题:给定Ratpack的线程模型,高阻塞线程数是否正常,我们是否应该担心在上述方法中花费的高CPU时间?

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。这意味着executor服务将保留这些线程60秒,当它们在60秒后不再被重用时,它将清理它们

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


(*)无限在这种情况下意味着受可用内存的限制,或者如果您有足够的内存,则受
2147483647
线程的限制(此值用于
ExecutorService.newCachedThreadPool(factory)
)。

仅基于Szymon的答案


Ratpack不会固有地限制任何操作。这实际上取决于你。您可以使用的一个选项是对资源的访问进行约束和排队。

谢谢,我现在更好地了解了情况。对于此测试,我们在同一台服务器上运行数据库,但prod服务器在另一台计算机上使用数据库。@GregorPetrin请尝试模拟prod环境。对远程服务器执行DB查询的阻塞操作不会消耗太多CPU—线程将在大部分时间内等待响应。我运行了10万个请求,执行了几分钟,阻塞操作并没有消耗太多的CPU,我得到了大约7%的CPU使用率。如果我的阻塞操作真的会运行一些CPU消耗操作,那么结果会大不相同。我会的,但是prod仍然运行一个与测试人员版本匹配的旧版本,并且它运行的是一个旧协议,没有任何指标等等。。将在可能的情况下进行测试,感谢您分享您的经验!不,没关系,我们不需要限制任何东西,数据库应该保持自己的状态(如果没有,我们需要在那里进行优化,而不是在Ratpack方面)。我的主要收获是,许多线程都是完全正常的,只要它们不做太多实际工作,这似乎也解释了探查器总是在
execontrollerbindingthreadfactory
类中捕获工作的原因(也就是说,我更确信我们没有做错任何事情)。感谢您的输入,Luke!