线程池工作人员不堪重负的Runnables崩溃了JVM

时间:2011-11-08 21:35:21

标签: java multithreading memory-management garbage-collection

我正在运行一个多线程tcp服务器,它使用一个带有无限制Runnable队列的固定线程池。客户端将runnables分配到池中。

在我的压力测试场景中,600个客户端尝试登录服务器并立即向其他客户端同时重复播放消息睡觉(现在客户端只丢弃传入的消息)。使用为堆内存保留1GB的四核 - 以及年轻和老一代的并行GC - 服务器在20分钟后崩溃并出现OOM异常。监视垃圾收集器显示终生代正在缓慢增加,而完整的GC只能释放一小部分内存。完整堆的快照显示旧一代几乎完全被Runnables(以及它们的传出引用)占用。

似乎工作线程无法比客户端能够将它们排队执行更快地完成Runnables的执行(对于服务器的每个传入“事件”,服务器将创建599个runnables,因为有600 - 1客户 - 假设他们当时都已登录。)

问题

有人可以帮我设想如何处理不堪重负的线程池工作者的策略吗?

同时

  • 如果我绑定队列,我应该采用什么策略来处理被拒绝的执行?
  • 如果我增加堆的大小,那不会只延长OOM异常吗?
  • 可以进行计算以测量Runnables聚合中完成的工作量。也许这个衡量标准可以作为协调客户调度工作的锁定机制的基础?
  • 当服务器被工作淹没时,客户应该遇到什么反应?

4 个答案:

答案 0 :(得分:2)

1)不要使用无界队列。我无法告诉你应该是什么样的;你的负载测试应该给你一个问题的答案。无论如何,使绑定可配置:至少可动态配置,更好地适应某些负载测量。

2)您没有告诉我们客户端如何提交请求,但如果涉及HTTP,则已经存在重载案例的状态代码:503 Service Unavailable

答案 1 :(得分:2)

我建议你限制队列的容量,并“推回”发布者以阻止它优雅地发布或删除请求。你可以做前者b完全填充队列。

您应该能够根据网络带宽和邮件大小计算最大吞吐量。如果您的收益低于此值,我会考虑更改服务器分发数据的方式。

另一种方法是使您的邮件处理更有效。您可以让每个客户端的每个读取线程直接写入侦听客户端。这避免了对显式队列的需要(您可能会将Socket中的缓冲区视为字节队列)并将速度限制为服务器可以处理的任何内容。它也不会在负载下使用更多内存(比空闲时更多)

使用此方法可以实现网络带宽可以处理的高消息速率。 (即使使用10 Gig-E网络)这会将瓶颈移到其他地方,这意味着您仍有问题但服务器不会出现故障。

BTW:如果你使用直接ByteBuffers,你可以在不创建垃圾和最少堆的情况下实现这一点。例如每个客户端大约1 KB的堆。

答案 2 :(得分:1)

您必须以某种方式限制传入的请求,执行此操作的机制应取决于您尝试的工作。任何其他东西只会导致OOM承受足够的负载,从而打开你的DOS攻击(甚至是无意的攻击)。

从根本上说,您有4种选择:

  1. 让客户等到您准备好接受他们的请求
  2. 在您准备接受新请求之前,主动拒绝客户请求
  3. 允许客户端在未准备好接收请求时尝试访问服务器时超时
  4. 上述两种或三种策略的混合。
  5. 正确的策略取决于您的真实客户在各种情况下的反应 - 他们是否更好地等待,可能(有效)无限期,或者他们更快地知道他们的工作将无法完成他们以后再试一次?

    无论您采用哪种方式,您都需要能够计算当前排队的任务数量,并根据队列中的项目数添加延迟,完全阻止或返回错误条件。

    可以使用BlockingQueue实现实现简单的阻止策略。但是,这并没有给出特别精细的控制。

    或者您可以使用信号量来控制将任务添加到队列的许可,如果您想应用温和限制,则可以提供tryAcquire(长超时,TimeUnit单位)方法。

    无论哪种方式,都不允许服务客户端的线程无限制地增长,否则你只会因为不同的原因而最终得到OOM!

答案 3 :(得分:1)

听起来好像在进行负载测试。我会确定你认为“可接受的重载”。单个客户端可以产生的最大流量是多少?然后翻倍。或三倍。或者按照类似的方式缩放。使用此阈值来限制或拒绝使用这么多带宽的客户端。

这有很多好处。首先,它为您提供了确定服务器负载(每台服务器的用户)所需的分析。其次,它为您提供了防御DDOS攻击的第一道防线。