【问题标题】:OutOfMemory error in a queue consumer application队列使用者应用程序中的 OutOfMemory 错误
【发布时间】:2020-02-03 15:02:44
【问题描述】:

我有一个数据管道组件,它读取 SQS 消息,在 S3 上传触发时生成,并为批处理管道组件解析和发布消息。 我最近观察到,在生产系统中,我的数据管道在重负载下不断崩溃并出现 OutOfMemory 错误,但在本地以类似负载进行测试时它从未崩溃?批处理管道似乎永远不会在生产中崩溃。

本地无法复现如何调试?

【问题讨论】:

    标签: amazon-s3 memory-management jvm out-of-memory amazon-sqs


    【解决方案1】:

    由于我在 2 周后找到了解决上述问题的方法,我想我会为他人和未来的自己记录下来。

    我无法复制该问题,因为 aws 命令-

    aws s3 cp --recursive dir s3://input-queue/dir

    不知何故,上传消息的速度不够快,可能会对我的本地数据管道造成压力。所以我关闭了数据管道,一旦队列中有 10k 条 SQS 消息,我启动它,正如预期的那样,它在处理约 3000 条消息后因内存不足错误而崩溃。事实证明,管道能够处理持续的吞吐量,但在开始加载 10k 消息时就中断了。

    我的假设是问题的发生是因为 Java 垃圾收集在执行后无法正确清理对象。因此,我开始分析生成的堆转储,经过几天的研究,我偶然发现了内存不足错误的可能根本原因。我的 MessageHandlerTask 类有大约 5000 个实例,理想情况下它们应该在处理后被 GC 处理,而不是继续堆积。

    对这一思路的进一步调查让我找到了根本原因——结果发现代码使用 Executors.newFixedThreadPool() 创建了一个 ExecutorService 来提交任务。这个实现使用了一个无限的任务队列,所以如果提交的任务太多,它们都在队列中等待,占用大量内存。 现实情况类似——轮询消息的速度快于处理它们的速度。如果有消息积压,这会导致创建大量有效的 MessageHandlerTask 实例来填充堆内存。

    修复方法是创建一个容量为 100 的 ArrayBlockingQueue 的 ThreadPoolExecutor,以便对 MessageHandlerTask 及其成员变量的实例数设置上限。

    找到解决方法后,我继续通过改变 ThreadPoolExecutor 的 maximumPoolSize 来优化管道以获得最大吞吐量。事实证明,在较高的线程数处发生了一些 SQS 连接异常。进一步调查显示,增加 SQS 连接池大小可以改善此问题。 对于给定的 Xmx 堆大小 1.5G 和 80 个 SQS 连接池大小,我最终确定了 40 个线程的计数,以便任务线程在处理时不会用完 SQS 连接。这帮助我仅使用一个数据管道实例就实现了 44 条消息/秒的吞吐量。

    我还发现了为什么批处理管道从未在生产中崩溃,尽管遭受了类似的 ExecutorService 实现 - 结果表明数据管道可能会因过多的并发 S3 上传而受到压力,但批处理管道的消息是由数据管道以渐进方式生成的。此外,批处理管道的吞吐量要高得多,我在使用 70 maximumPoolSize 时以 347 条消息/秒进行了基准测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多