【问题标题】:Graceful degradation in Java to avoid out of memory errorsJava中的优雅降级以避免内存不足错误
【发布时间】:2011-09-20 00:27:25
【问题描述】:

有哪些工具或最佳实践可用于在内存密集型请求突发期间优雅地降级 Java 服务中的服务?有问题的应用程序是多线程的。处理每个请求所需的工作量可能会有很大差异,并且不容易拆分和并行化。

我对编写涉及堆使用和 GC 的应用程序级代码持谨慎态度,但我们发现应用程序可能会因处理多个密集请求而陷入困境,这意味着内存不足错误或完全 GC。通常完整的 GC 无法找到任何要释放的内存。

长话短说:我正在考虑添加一些限制或排队功能来预先解决此类问题。

任何想法或建议表示赞赏。

【问题讨论】:

  • 我假设您已经分析了您的应用程序以确保您不会减少使用的内存量。您还检查过您无法获得更大的服务器,一台 16 GB 的 PC 可能要花费您 1000 美元。
  • Peter - 是的,我们已经对应用程序进行了分析,看看我们可以在哪里减少内存使用。更多的物理内存,更大的服务器——这些选项可能不会导致 GC 时间更长、更痛苦吗?
  • GC的成本与使用量成正比,与free量成反比。如果您使用相同数量的内存,则 GC 暂停将相同,但频率要低得多。如果你使用更多内存,它会更长,但这比彻底失败要好。

标签: java performance memory


【解决方案1】:

正如 joeslice 所说,通过一个简单的资源池实现节流。在最基本的层面上,这是一个信号量——您的工作线程在处理请求之前需要获得许可。既然您说您有异构任务,您可能希望许可证更复杂一些,例如获得与工作规模成正比的一定数量的许可证。

过去,我发现这并不总是有效。假设您的启发式方法已关闭,并且您的应用程序无论如何都会引发 OOM。防止进程处于不良状态很重要,因此请立即终止并重新启动该进程。有几种方法可以注意到 OOM 何时发生,例如见java out of memory then exit

【讨论】:

  • 进程不必挂在坏状态。 OOM 将展开有问题的线程的堆栈,释放一些内存。在线程池中,OOM 可以被捕获,并且可以稍微调整启发式方法以考虑更改的请求模式。整个应用程序可以生存并继续提供服务。
  • OOM 可能有多种原因,其中只有一种是由信号量/启发式控制的,所以我认为您所描述的并不可靠。换句话说,捕捉 OOM 有时可能会奏效,但我发现安全总比抱歉好得多。保留 GC 和应用程序日志,以便您以后可以在生产环境之外重现问题!
【解决方案2】:

这是 Netty (link) 的作者提供的一个实现示例。他们基本上跟踪内存使用情况并根据该统计数据直接限制。

另一种更粗略的方法是通过使用固定线程池和有界队列来限制并发执行。一种常见的方法是让queue.put() 的调用者在此队列满后自行执行任务。这样,负载将(应该)一直传播回客户端,直到新请求的创建变慢。因此应用程序的行为。变得更加“优雅”。

在实践中,我几乎只使用上述“粗略”的方式。它工作得很好。基本上是固定线程池和有界队列+调用者运行拒绝策略的组合。我保持参数(队列大小,线程池大小)可配置,然后在设计完成后,我将调整这些参数。有时很明显,线程池可以在服务等之间共享,因此在这种情况下,使用 ThreadPoolExecutor 类将固定线程池/有界队列/调用者运行策略全部封装在一个中非常方便。

【讨论】:

    【解决方案3】:

    我想知道是否有一种方法可以预先确定您将在给定工作中使用多少内存......如果有某种方法可以确定特定输入可能会产生爆炸性的内存大小,也许您可​​以尽量避免它与另一个高使用率作业同时运行。

    如果您可以确定作业之间的相对大小(这是一个很大的假设),您可以使用计数Semaphore 允许(比如说)一次运行 100 个工作单元。一个典型的工作可能只算作一个单位(并且只获得一个许可证),而一个更大的工作可能需要在运行之前获得 10 或 20 个许可证......

    当然,如果您无法预先确定要消耗的内存大小,您可能仍然可以探索进一步细分问题的方法,以便您处理更多的小内存工作,而不是少量的大工作。

    【讨论】:

      【解决方案4】:

      在应用服务器中,通常有工作线程池的设置。该池中的最大线程数大致定义了您将消耗多少内存。这是一个简单且重要的是可行的概念。

      不过,我不会称其为“优雅退化”。这是节流。优雅降级涉及降低服务级别(例如提供给用户的详细信息量),以至少保持每个当前用户可用的基本必要功能。限制额外的用户只是不走运。

      按照该定义的优雅降级需要了解应用程序的性质,因此您必须让代码了解它。

      显而易见的方法是根据用户的需要将所有可能的操作划分为类。应始终处理第一类。 2nd (3rd, 4th, ...) 类仅在服务器低于特定负载水平时才提供服务,否则返回“暂时不可用”错误。

      【讨论】:

        【解决方案5】:

        您在使用 J2EE 吗?因为这是Application Server做负载均衡的工作,相信很多主流AppServer都支持。您的应用程序不应该担心它。

        【讨论】:

          猜你喜欢
          • 2013-10-04
          • 2012-07-08
          • 1970-01-01
          • 2012-12-23
          • 2010-11-01
          • 2017-08-12
          • 1970-01-01
          • 2016-02-25
          • 2017-11-19
          相关资源
          最近更新 更多