You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
如果运行在容器环境中,Node.js 12.x 会通过限制默认的堆内存在可用限制范围内的方式解决一些不一致的问题。然而,对于没有默认max_old_space_size设置的情况,上述的例子也是成立的,当在调整默认值的时候需要谨慎一点。此外,由于默认值是保守的,知道默认限制可以让你更好的调整(knowing the default limits will let you tune better as the defaults are conservative)。
本文首发于 IBM Developer.
在容器中运行 Node.js 程序的时候,传统的内存限制参数可能不会如预期般生效。本文中,我们将探讨造成这个现象的原因并且提供一些你在容器化环境中运行 Node.js 程序时候可以参考的建议和最佳实践。
总结
当 Node.js 程序运行在有内存限制的容器化环境中(例如,docker 下的
--memory
参数或者其他系统下的某些参数),同时使用--max-old-space-size
参数来确保 Node 知道它能够使用的内存限制,并且这个值要小于容器的限制值。当 Node.js 程序运行在容器环境中,并且容器内存是可调的,此时可以根据程序使用的活动内存的峰值来设置容器的内存。
让我们更进一步进行探索。
Docker 内存限制
通常,容器没有内存限制并且可以使用宿主机允许使用的全部内存资源。
docker run
命令提供了命令行参数来设置容器可以使用的内存和 CPU 资源。形如:
docker run --memory <x><y> --interactive --tty <imagename> bash
解析:
更多例子:
docker run --memory 1000000b --interactive --tty <imagename> bash
设置了内存限制为 1,000,000 bytes。
可以通过以下命令来检查容器内以 bytes 为单位的内存限制:
如果以这个值为
--max-old-space-size
的值,让我们来看看容器的行为。“Old space” 是 V8 引擎管理的堆内存中的公共部分(例如,js 中的对象存放的位置),而
--max-old-space-size
则是用来控制这块空间的最大值的。更多内容,请参考About –max-old-space-size。通常,当应用程序使用了超过容器内存限制的内存的时候,程序将会被终止。
下面这个例程每隔 10 毫秒执行一次循环,快速的循环使堆内存漫无边际的增长,模拟内存泄漏的情况。
文中提到的所有例程都可以在我发布到
Docker Hub
中的镜像中找到。你可以拉下这个镜像然后执行这些程序。使用docker pull ravali1906/dockermemory
来得到这个镜像。或者,你也可以自己将这个程序打包成 Docker 镜像,然后使用如下命令以一个内存限制的方式启动容器:
以上命令中的
ravali1906/dockermemory
就是镜像的名字。接下来,以高于容器内存限制的内存大小执行程序:
解析:
--max_old_space_size
的单位是 Mbytesprocess.memoryUsage()
的单位则是 bytes程序会在内存使用达到某一个阈值的时候被终止。这个阈值是什么呢?限制又是什么?让我们一起来探讨一下。
在具有容器内存限制下的
--max-old-space-size
预期行为默认情况下,Node.js(直至 11.x 版本)的最大堆内存空间为 32 位操作系统下的 700MB 和 64 位操作系统下的 1400MB。可以通过查看文末提到的博客来了解当前的默认值。
因此,当设置
--max-old-space-size
的值超过了容器的限制的时候,程序将会被 OOM 终止掉。实际上,这个情况可能不会出现。
在具有容器内存限制下的
--max-old-space-size
的实际行为通过
--max-old-space-size
声明的内存空间不是在程序最开始就可用的。相反的,JavaScript 堆内存是随着增长的需求而增长的。
程序实际使用的内存(以对象形式存在于 JavaScript 堆内存中的)可以通过
process.memoryUsage()
接口的heapUsed
字段表示。因此,此时的预期行为是如果实际使用的堆内存大小(常驻对象的大小)超过了 OOM 的阈值(容器
--memory
的大小),程序则会被终止。实际上,这个情况也不会出现。
当我在具有内存限制下的容器环境中执行一个内存敏感的 Node.js 应用的时候,我发现了两种模式:
heapTotal
和heapUsed
的值超过了容器内存限制之后很久才会触发终止程序的行为Node.js 在容器环境中的行为:解释
容器会持续追踪内部运行的程序的“常驻居民大小”(RSS)。
它表示程序使用的虚拟内存的一部分,进一步解释就是,它表示的是程序已分配的内存中的一部分。
更进一步解释就是它表示的是程序已分配的内存中的活动的那部分。
并不是所有程序中被分配的内存都是活动的。这是因为“被分配的内存”在进程开始使用它之前不需要被分配。此外,为了应对其他进程的内存需求,操作系统会将程序中休眠的或者是非活动的那部分内存
写到交换区
(swap out),从而将内存腾出来给需要它的进程使用。此后,当这个程序需要这些信息的时候,操作系统则会将之前写到交换区的内存重新写到内存中。RSS 内存反映了程序的寻址空间中可用的并且是活动的内存的大小。
证明
例 1. 分配内存到缓冲区
以下列子,
buffer_example.js
,展示了分配内存到缓冲区的操作:通过以下命令启动容器:
执行程序,你将会看到:
即便内存已经超过了容器的限制,程序依然没有被终止。这是因为分配的内存你没有被完全使用。rss 的值很小,没有达到容器的内存限制。
例 2. 分配内存并且填充它
在下面的程序中,此时的内存被数据填充满了:
启动容器:
执行程序:
即使是这样,程序依然没有被终止!为啥?当活动内存超过了容器的限制,同时交换空间中还有空间的时候,部分旧的内存将会被写到交换空间,腾出来的空间会被程序继续使用。通常,docker 会分配与通过
--memory
设置的值同样大小的交换空间。由于这个特性,示例程序当前分配了 2GB 内存-1GB 是活动的内存,1GB 在交换空间。简而言之,由于交换空间中分但了一部分内存压力,rss
的大小依然处于容器的限制之内,程序能够继续执行。例 3. 分配内存并以数据填充,同时容器不允许使用交换空间
以下设置禁止容器使用交换空间:
Issue the docker memory limit, swap limit, and swappiness while running the image as:
启动容器,分别设置内存限制、交换空间限制和、内存交换比例限制:
执行程序:
注意到上面的
Killed
了吗?当--memory-swap
的值等于--memory
的时候,表明容器不可以使用额外的交换空间。此外,通常宿主机的内核能够将容器使用的部分匿名分页写到交换空间,因此,设置--memory-swappiness
的值为 0 来禁用这种交换。此时,容器内不会再发生内存交换,rss
达到了容器的限制,立刻终止进程。总结
当你以超过容器限制的
--max-old-space-size
的值来运行程序的时候,似乎 Node.js“无视”了容器内存限制的存在。但是如你在上面的例子中见到的,真正的原因(程序没有被终止)是程序没有完全用完--max-old-space-size
设置的值。需要铭记于心的是,当使用超过容器限制的内存时,你不能期待程序总是按照同样的方式运行。因为进程的活动内存(也就是 rss)被一系列超过了程序的控制范围的因素所影响,并且高度依赖负载和环境,例如工作负载本身、系统并发量、操作系统调度、垃圾回收率等等。
关于 Node.js 堆内存的建议 (当你可以控制堆内存,但是不能控制容器的内存)
old_space_size
来说的一个安全的值关于容器内存的建议 (当你能控制容器内存,但是不能控制 Node.js 使用的内存)
rss
的增长。我同时使用了top
命令和process.memoryUsage()
接口来观测 rss 的增长后记
如果运行在容器环境中,Node.js 12.x 会通过限制默认的堆内存在可用限制范围内的方式解决一些不一致的问题。然而,对于没有默认
max_old_space_size
设置的情况,上述的例子也是成立的,当在调整默认值的时候需要谨慎一点。此外,由于默认值是保守的,知道默认限制可以让你更好的调整(knowing the default limits will let you tune better as the defaults are conservative)。更多内容,请移步 Configuring default heap dumps.
The text was updated successfully, but these errors were encountered: