我的目标是更好地了解Java EE环境中的并发性以及如何更好地使用它。
我们以典型的servlet容器(tomcat)为例。对于每个请求,它使用1个线程来处理它。线程池配置为,它可以在池中有最多80个线程。我们还采用简单的webapp - 它在每个请求期间进行一些处理和数据库通信。
在高峰时间,我可以看到80个并行运行的线程(+其他几个基础架构线程)。我们也假设我在'm1.large'EC2实例中运行它。
我不认为所有这些线程都可以在这个硬件上真正并行运行。所以现在调度程序应该决定如何更好地分割它们之间的CPU时间。所以问题是 - 在这种情况下调度程序开销有多大?如何在线程数量和处理速度之间找到正确的平衡?
在4核CPU上拥有80多个线程对我来说听起来并不健康。特别是如果它们中的大多数在某种IO(数据库,文件系统,套接字)上被阻止 - 它们只消耗宝贵的资源。如果我们将从线程分离请求并且只有合理数量的线程(例如8个)并且只是向它们发送处理任务,该怎么办?当然在这种情况下,IO也应该是非阻塞的,这样当我需要的某些数据可用时,我会收到事件,如果我有一些结果,我会发送事件。
据我所知,演员模型就是这个。 Actor没有绑定线程(至少在Akka和Scala中)。所以我有合理的线程池和一堆包含处理任务的邮箱的演员。
现在的问题是 - 演员模型在性能,调度程序开销和资源(RAM,CPU)消耗方面如何与传统的每个请求模式进行比较?
我有一些请求(只有几个)需要花费太多时间来处理。我优化了代码和所有算法,添加了缓存,但它仍然需要太多时间。但我知道,该算法可以并行化。它自然适合演员模型 - 我只是将我的大任务分成几个任务,然后以某种方式聚合结果(如果需要)。但是在每个请求线程模型中,我需要生成自己的线程(或者创建我的小线程池)。据我所知,不建议在Java EE环境中练习。而且,从我的角度来看,它并不适合每个请求线程模型。问题出现了:我的线程池大小应该有多大?即使我在硬件方面做得合理,我仍然有一堆由servlet容器管理的线程。线程管理变得分散并且变得疯狂。
所以我的问题 - 在每个请求线程模型中处理这些情况的最佳方法是什么?
答案 0 :(得分:3)
在4核CPU上拥有80多个线程对我来说听起来并不健康。特别是如果它们中的大多数在某种IO(数据库,文件系统,套接字)上被阻止 - 它们只会消耗宝贵的资源。
错误。正是在这种情况下,处理器可以处理比单个核心数量更多的线程,因为在任何时间点的大多数线程都会被阻塞,等待I / O.很公平,上下文切换需要时间,但与文件/网络/数据库延迟相比,这种开销通常无关紧要。
根据经验,线程数应该等于 - 或稍多 - 处理器核心数量仅适用于核心在大多数时间保持忙碌的计算密集型任务。
我有一些请求(只有几个)需要花费太多时间来处理。我优化了代码和所有算法,添加了缓存,但它仍然需要太多时间。但我知道,该算法可以并行化。它自然适合演员模型 - 我只是将我的大任务分成几个任务,然后以某种方式聚合结果(如果需要)。但是在每个请求线程模型中,我需要生成自己的线程(或者创建我的小线程池)。据我所知,不建议在Java EE环境中练习。
从未听说过(但我并不认为自己是最终的Java EE专家)。恕我直言,使用例如并行执行与单个请求相关联的任务没有任何错一个ThreadPoolExecutor。请注意,这些线程不是请求处理线程,因此它们不会直接干扰EJB容器使用的线程池。除了他们当然竞争相同的资源,因此他们可能会在粗心的设置中减慢或完全停止其他请求处理线程。
在每个请求线程模型中处理这些情况的最佳方法是什么?
最后,您无法逃避测量并发性能并微调线程池的大小以及针对您自己的特定环境的其他参数。
答案 1 :(得分:1)
Java EE的重点是将常见的架构问题(如安全性,状态和并发性)放入框架中,并让您提供一些业务逻辑或数据映射以及连接它们的连线。因此,Java EE故意在框架中隐藏令人讨厌的并发(锁定到读/写可变状态)。
这种方法可以让更广泛的开发人员成功编写正确的应用程序。然而,必要的副作用是这些抽象会产生开销并消除控制。这既好又简单,编码政策就像政策不代码那么好而且不好(如果你知道你在做什么,并且可以在框架中做出选择)。
生产箱上有80个线程本身并不坏。大多数将被阻止或等待I / O,这很好。有一个(可调)线程池进行实际计算,Java EE将为您提供外部挂钩来调整这些旋钮。
演员是一个不同的模型。它们还允许您编写(可以)避免锁定以修改状态的代码岛(actor主体)。您可以将您的actor编写为无状态(在递归函数调用参数中捕获状态)或在actor实例中完全隐藏您的状态,以便状态全部受限(对于您可能仍需要明确锁定数据访问权限的反应样式actor确保在运行您的actor的下一个线程上的可见性。
我不能说一个或另一个更好。我认为有足够的证据表明这两种模型都可用于编写安全,高吞吐量的系统。要使其表现良好,您需要仔细考虑您的问题并构建隔离状态部分和各种状态计算的应用程序。对于能够很好地理解数据并具有很高并行性的代码,我认为Java EE之外的模型很有意义。
通常,调整计算绑定线程池的经验法则是它们应该大约等于N + 2个核心。许多框架会自动调整大小。您可以使用Runtime.getRuntime()。availableProcessors()来获取N.如果您的问题以分而治之的方式分解并且数据项的数量很大,我强烈建议您检查fork / join,这可能是现在用作一个单独的库,将成为Java 7的一部分。
至于如何管理它,你不应该在Java EE中生成线程(他们想要控制它),但你可能会调查通过消息队列向数据运算线程池发送请求,通过返回消息处理该请求。这可以适合Java EE模型(当然有点笨拙)。
我在这里写了一些actor,fork / join和其他一些并发模型,你可能会感兴趣:http://tech.puredanger.com/2011/01/14/comparing-concurrent-frameworks/