Dav*_*vid 19 java mmap linux-kernel amazon-ecs docker
给定一个通过mmap'd文件创建大型linux内核页面缓存的进程,在具有内存限制的docker容器(cgroup)中运行会导致内核slab分配错误:
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252395] SLUB: Unable to allocate memory on node -1 (gfp=0x2080020)
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252402] cache: kmalloc-2048(2412:6c2c4ef2026a77599d279450517cb061545fa963ff9faab731daab2a1f672915), object size: 2048, buffer size: 2048, default order: 3, min order: 0
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252407] node 0: slabs: 135, objs: 1950, free: 64
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252409] node 1: slabs: 130, objs: 1716, free: 0
Run Code Online (Sandbox Code Playgroud)
看着slabtop我可以看到在以内存限制开始的容器中,buffer_head,radix_tree_node和kmalloc *对象的数量受到严格限制。这似乎会对应用程序中的IO吞吐量产生病理影响,并且可以通过观察到iostat。即使页面缓存消耗了在容器外部运行的主机OS上的所有可用内存或没有内存限制的容器,也不会发生这种情况。
这似乎是内核内存记帐中的一个问题,其中不将内核页面高速缓存计入容器内存,而是将其支持的SLAB对象计入容器内存。该行为似乎是异常的,因为在预先分配了一个大的平板对象池时运行,内存受限的容器运行良好,可以自由地重用现有的平板空间。只有在容器中分配的平板才计入容器。内存和内核内存的容器选项的组合似乎无法解决此问题(除非根本不设置内存限制,或者设置的限制太大以至于不能限制平板,但这会限制可寻址空间)。我试图通过cgroup.memory=nokmem引导完全禁用kmem记帐,但没有成功。
系统信息:
要重现该问题,可以使用我的PageCache Java代码。这是嵌入式数据库库的基本实例,在很大程度上利用了内存映射文件来将其部署在非常快速的文件系统上。该应用程序通过ECS部署在AWS i3.baremetal实例上。我正在从主机到存储内存映射文件的docker容器映射大量存储空间。AWS ECS代理要求为所有容器设置非零内存限制。内存限制会导致病理性平板行为,并且由此产生的应用程序IO吞吐量完全不可接受。
drop_caches使用之间的运行之间有帮助echo 3 > /proc/sys/vm/drop_caches。这将清除页面缓存和关联的平板对象池。
欢迎提供有关如何修复,解决此问题或什至在哪里报告此问题的建议。
更新 看来,用4.15内核更新到Ubuntu 18.04确实可以解决观察到的kmalloc分配错误。Java的版本似乎无关紧要。这似乎是因为每个v1 CGroup只能分配不超过内存限制的页面缓存(使用多个cgroup则更加复杂,因为只有一个cgroup通过共享页面记帐 “收费”进行分配)方案)。我相信现在这与预期的行为一致。在4.4内核中,我们发现观察到的kmalloc错误是在内存限制和非常大的页面缓存的v1 Cgroup中使用软件raid0的交叉。我相信4.4内核中的cgroup可以映射无限数量的页面(我们发现该错误非常有用),直到内核为关联的slab对象耗尽内存为止,但是我仍然没有吸烟的原因。
使用4.15内核时,需要我们的Docker容器设置内存限制(通过AWS ECS),因此我们实现了一项任务,一旦在中创建了容器,便要取消设置内存限制/sys/fs/cgroup/memory/docker/{contarainer_id}/memory.limit_in_bytes。尽管这不是一个好的做法,但似乎可以奏效。这允许我们想要的行为-在主机上无限共享页面缓存资源。由于我们正在运行具有固定堆的JVM应用程序,因此降低了下行风险。
对于我们的用例,完全可以为cgroup减少页面缓存(mmap的磁盘空间)和关联的slab对象的选择,而为docker进程保留堆和堆栈的限制,这将是很棒的选择。当前的“共享页面计费”方案很难推论,我们宁愿允许LRU页面缓存(和相关的SLAB资源)使用主机内存的全部范围,就像根本没有设置内存限制的情况一样。
我已经开始关注LWN上的一些对话,但是我有点茫然。也许这是一个可怕的主意?我不知道。欢迎提供有关如何进行下一步操作的建议。
\n\n\njava 10.0.1 2018-04-17
\n
您应该尝试使用更新版本的 java 10(或 11 或...)
\n\n我在去年 5 月(2019 年)的“Java 8\xe2\x80\x8a\xe2\x80\x94\xe2\x80\x8afinally! ”中提到,Java 10 的新演变在 Java 8 中向后移植,意味着 Docker 将更准确地报告所使用的内存。
\n\n2018 年 5 月的这篇文章 报道:
\n\n\n\n\n成功了!在不提供任何标志的情况下,Java 10(10u46 - Nightly)可以正确检测到 Docker 内存限制。
\n
\n\ndocker - jvm 集成是 Java 10 中的一项重大改进。
\n\n
\n 这实际上与设置合理的 XMS 和处理器数量有关。这些现在尊重 docker 容器限制,而不是选取主机实例值(您可以根据-XX:-UseContainerSupport您的用例关闭此功能)。但我还没有发现它对处理页面缓存有帮助。
\n
\n 我发现的最佳解决方案是在创建容器后(如果需要)禁用 docker 内存限制。
\n 这绝对是一个黑客行为 - 用户要小心。
| 归档时间: |
|
| 查看次数: |
564 次 |
| 最近记录: |