弹性架构
几周前,我获得了一个难得的机会,可以在基础设施领域中沾沾自喜。 在JVM内部的深入了解下,我每天的工作经历发生了有趣的变化,我想与您分享动机和成果。 希望它可以启发类似的问题类别。
背景
我将从解释需要解决方案的上下文开始。 如果您知道Plumbr性能监控的全部内容,则可以跳过此部分。 对于其他所有人,我们Plumbr都在构建性能监控解决方案。 我们的方法是独特的,因为我们旨在使所有性能问题都具有源代码的根本原因。
这种问题的较复杂类别之一是其根源隐藏在Java内存分配和管理中。 此类别中的问题包括:
- 内存不足;
- 面临太频繁/太长时间的GC暂停;
- 尝试减少应用程序的内存占用。
我们对此类问题的解决方案是建立在对对象图进行快照并从那里公开最需要内存的数据结构的基础上的。 结果,您将获得运行时透明性,以了解JVM堆中实际发生的情况:
上面是我们监视自己的服务时发现的示例。 我们可以看到,在重大GC暂停后的某个时刻,我们占据了70%以上的旧一代。 老一代的高占用率通常会导致长时间的GC暂停,因此Plumbr捕获了快照以显示其中的实际内容。
在这种特殊情况下,我们发现包含ProbeDataProcessingTasks的处理队列的大小已增长到近千兆字节。 了解应归咎于哪些数据结构使解决该问题变得微不足道。 结果,GC暂停的频率和持续时间保持不变。
但是,拍摄这些快照有些昂贵。 捕获快照所需的时间取决于堆中对象的数量以及它们之间的引用。 我们的代理商会仔细安排快照的时间,以避免自己成为性能瓶颈。
综上所述:在我们的基础架构中,此特殊功能导致不可预测的内存快照流入。 更糟糕的是,快照的大小也是不可预测的。 有时我们每小时可能只收到一个微小的快照,然后突然间,我们在很短的时间内被许多10 + G快照轰炸:
我们最初的解决方案存在问题
我们构建的第一个解决方案是专用的微服务,用于处理快照的传入流。 我们立即开始面临问题。 首先,我们还无法估算这些快照的大小。 最初配置的4G内存还远远不足以处理流向我们的较大快照。 要分析快照,我们需要将对象图加载到内存中,因此快照越大,分析所需的RAM越多。
因此,我们需要从亚马逊购买更大的计算机。 突然之间,微服务不再是微服务了。 正如我们很快发现的那样,在您的每月账单中实际上可以看到保持m4.10xlarge实例嗡嗡作响的24×7。 除了非常昂贵外,机器有99%的时间几乎处于空闲状态–发生的巨大堆快照很少见,因此经常会超额配置10倍来处理偶尔出现的峰值。
此外,分析持续时间很快就成为瓶颈。 快照需要10秒钟到数十分钟的时间来分析每个快照,因此当在短时间内到达多个大型快照时,队列等待时间成为一个问题:
解决方案要求
了解了问题之后,下一步就是将问题简化为解决方案的要求:
- 分析任务不应在队列中等待数小时。 我们应该能够并行处理它们。 每当一个巨大的快照到达并且需要很长时间进行分析时,其他快照就不应等待它完成。
- 对于每个快照,我们可以估计执行分析所需的堆空间。 我们希望使用尽可能多的资源,而不会过度配置基础架构。
对于以前建立过弹性环境的人来说,解决方案的要求可能会很明显。 对于那些还没有的人,我将在下一部分中介绍解决方案体系结构和实现的关键案例。
建立解决方案
这些要求有效地指示我们,我们应该维护一个弹性的基础架构,而不是一个单独的专用实例。 实例应按需生成,实例类型应与接收到的快照的大小相对应。
因此,我们继续将快照分析代码包装到docker容器中,并利用AWS ECS将此类容器用作集群中的任务。 这样做之后,我们偶然发现了第一个问题:向外扩展并不像预期的那么琐碎。
仅仅为每个分析生成一个适当大小的新实例并随后立即终止的天真的方法被证明是一个坏主意。 启动实例最多可能需要五分钟,具体取决于实例类型。 此外,AWS每小时执行一次计费,因此,让一个实例运行60分钟比运行十个实例每6分钟便宜十倍。
在这种情况下,典型的方法是使用AWS 自动扩展组。 显然,这不适合我们,因为AWS无法根据ECS任务所需的内存量自动生成实例。 您无法将任务提交给ECS集群,除非该集群已经有足够的资源来容纳它。
我们的解决方案是根据分析任务所需的内存量将其划分为多个存储桶,并为每个存储桶分配一个单独的群集。 收到新快照后,我们检查目标群集是否有足够的可用资源来运行任务。 如果没有,我们将在其自动扩展组中增加所需的实例数。 然后,AWS自动启动一个适当大小的新实例。 因此,从本质上讲,我们最终得到了六个存储桶,每个存储桶包含适当大小的实例,可以根据需求进行扩展:
第二个问题是通过扩展来解决自身问题。用于扩展的标准CloudWatch警报基于集群利用不足的情况。 如果集群闲置了足够长的时间,我们会减少所需实例的数量。 “空闲”是根据群集中消耗的内存计算的,如果在45分钟内内存使用率一直低于指定的阈值,则立即扩展并终止额外的实例。
这里也有一个警告:在自动扩展组中进行扩展时,AWS选择一种特殊的方式来终止实例。 例如,如果一个群集有两个实例,其中一个实例处于空闲状态,而另一个实例正在运行分析,则完全有可能该活动实例将被杀死而不是空转一个实例。
放大问题的解决方案是,在分析期间,我们为执行该扩展的特定实例设置了放大保护 。 开始分析时,我们设置标志,并在完成后将其删除。 自动缩放不会终止受保护而无法放大的实例。 最后一点就足够了,从那以后我们就开始平稳运行。
找到了两个问题的解决方案,我们得到了预期的结果。 更改后,队列中等待的时间现在如下所示:
带走
这是少数情况下的一种,您可以提高应用程序的性能,并减少容量需求以降低成本。 在大多数情况下,您必须为提高性能付出很大的代价,因此人们可以欣赏这些时刻。 现在,按需计算比以往任何时候都容易,因此也许您可以以类似的方式优化应用程序。
而且,如果除了弹性基础架构的有趣案例之外,该帖子还引发了人们对如何获得应用程序内存使用透明性的兴趣,那就继续免费试用Plumbr试用一下。
翻译自: https://www.javacodegeeks.com/2016/05/elastic-infrastructure-practice.html
弹性架构