2

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

由于 Ratpack 的线程模型是独一无二的,我们想确保这种情况是正常的:我们模拟了应用程序的 2500 个并发用户,我们的线程数上升到 400(甚至在某一时刻甚至 600 个),其中大部分是ratpack-blocking-x-yyy线程。

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

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

4

2 回答 2

2

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因此您的应用程序不会被阻塞并且可以处理大量请求。

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

于 2018-02-16T13:16:45.690 回答
1

只是为了建立 Szymon 的答案......

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

于 2018-02-19T00:47:20.853 回答