我们的服务在 AWS 的 m5.12xlarge 节点(48 核,192 G RAM)上运行,该节点使用 Ubuntu 16.04。我们使用 Java 8。对于我们的服务,我们分配了大约 150G 作为最大堆大小。我们在节点上没有交换。我们的服务的性质是它分配了大量的大型短期对象。除此之外,通过我们依赖的第三方库,我们创建了许多短期进程,这些进程通过管道与我们的进程通信,并在处理少量请求后被回收。
我们注意到,在进程启动后,进程的 RES(位于 top)达到约 70G 时,CPU 中断显著增加,JVM 的 GC 日志显示系统时间猛增至数十秒(有时为 70 秒)。在此状态下,这 48 个核心节点上的平均负载最初小于 1,最后几乎达到 10。
sar 输出表明,当节点处于此状态时,最小页面错误会显著增加。总体而言,大量 CPU 中断与此状态相关。
重启服务只能暂时缓解压力。平均负载缓慢但稳步上升,GC 系统时间再次飙升。
我们在大约 10 个节点的集群上运行我们的服务,每个节点的负载(几乎)均匀分布。我们发现有些节点比其他正常工作的节点更频繁、更快地进入此状态。
我们尝试了各种 GC 选项以及诸如大页面/THP 等选项,但都没有成功。
以下是 loadavg 和 meminfo 的快照
/proc/meminfo on a node with high load avg:
MemTotal: 193834132 kB
MemFree: 21391860 kB
MemAvailable: 52217676 kB
Buffers: 221760 kB
Cached: 9983452 kB
SwapCached: 0 kB
Active: 144240208 kB
Inactive: 4235732 kB
Active(anon): 138274336 kB
Inactive(anon): 24772 kB
Active(file): 5965872 kB
Inactive(file): 4210960 kB
Unevictable: 3652 kB
Mlocked: 3652 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 89140 kB
Writeback: 4 kB
AnonPages: 138292556 kB
Mapped: 185656 kB
Shmem: 25480 kB
Slab: 22590684 kB
SReclaimable: 21680388 kB
SUnreclaim: 910296 kB
KernelStack: 56832 kB
PageTables: 611304 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 96917064 kB
Committed_AS: 436086620 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
HardwareCorrupted: 0 kB
AnonHugePages: 85121024 kB
CmaTotal: 0 kB
CmaFree: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 212960 kB
DirectMap2M: 33210368 kB
DirectMap1G: 163577856 kB
/proc/meminfo on a node that is behaving ok
MemTotal: 193834132 kB
MemFree: 22509496 kB
MemAvailable: 45958676 kB
Buffers: 179576 kB
Cached: 6958204 kB
SwapCached: 0 kB
Active: 150349632 kB
Inactive: 2268852 kB
Active(anon): 145485744 kB
Inactive(anon): 8384 kB
Active(file): 4863888 kB
Inactive(file): 2260468 kB
Unevictable: 3652 kB
Mlocked: 3652 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 1519448 kB
Writeback: 0 kB
AnonPages: 145564840 kB
Mapped: 172080 kB
Shmem: 9056 kB
Slab: 17642908 kB
SReclaimable: 17356228 kB
SUnreclaim: 286680 kB
KernelStack: 52944 kB
PageTables: 302344 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 96917064 kB
Committed_AS: 148479160 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
HardwareCorrupted: 0 kB
AnonHugePages: 142260224 kB
CmaTotal: 0 kB
CmaFree: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 149472 kB
DirectMap2M: 20690944 kB
DirectMap1G: 176160768 kB
火焰图中最重要的部分是:
https://i.stack.imgur.com/yXmOM.png
偶然间,我们重启了一个节点,发现它以非常稳定的方式运行了大约 2 周,其他地方没有任何变化。从那时起,我们不得不重启处于这种状态的节点以获得一些喘息空间。后来我们在其他地方发现这些症状可能与页表被卡住有关,只有通过重启才能缓解。目前还不清楚这是否正确,也不清楚这是否是我们遇到这种情况的原因。
有没有办法可以永久解决这个问题?
答案1
透明大页面正在变得碎片化或混乱。在 Linux 上,这是考虑放弃透明并明确设置页面大小的内存大小。
差值大于 10 GB(坏值减去好值):
Committed_AS: 274.3
AnonHugePages: -54.5
DirectMap2M: 11.9
DirectMap1G: -12.0
DirectMap 从 1G 变为 2M 表明 x86 TLB 和 Linux 内部可用的连续空间更少。其中 AnonHugePages 丢失了 50 GB,这是一个很大的差异。不知何故,这导致 Committed_AS 膨胀到 MemTotal 的 225%,这是一个不好的症状;这个系统将疯狂地进行页面调出。
鉴于火焰图堆栈中的页面错误,Linux 虚拟内存系统对页面进行重新排列会产生大量开销。
提高性能包括明确配置大页面。150 GB 的堆远远超出了 30 GB 的过渡点,在该过渡点上压缩指针不再可行。(关于保持在此 30 GB 阈值以下的文章很多。)三位数 GB 也是我认为需要认真评估 Linux 大页面的大小。
在 OpenJDK 或 Oracle JDK 上:首先正确分配大页面,然后使用选项 -XX:+UseLargePages
。 Java 对大内存页面的支持 和HugePages 上的 Debian wiki
如果你还想尝试垃圾收集器,请查看OpenJDK 的 ZGC 维基页面。有限的暂停时间、处理大堆和 NUMA 意识是明确的目标。简而言之,还要添加实验选项: -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:+UseLargePages
。ZGC wiki 页面还讨论了使用 Linux 大页面池和 hugetlbfs 的麻烦,有这些事情的例子总是有帮助的。
关于 NUMA,想一想这将在哪个 CPU 上运行。可能是两个 24 核插槽左右。AWS 没有具体说明,但说它是一个白金 8175。由于您将在不同的套接字上执行,因此部分内存将不位于套接字本地。即使虚拟机管理程序不向 VM 客户机公开此拓扑,情况也是如此。
然而,现代 Xeon 上的两个插槽可以产生可控的 NUMA 效果。页面大小是一个更大的问题。