OpenResty 및 Nginx 서버는 일반적으로 모든 작업자 프로세스 간에 공유되는 데이터를 저장하기 위해 공유 메모리 영역으로 구성됩니다. 예를 들어 Nginx 표준 모듈 ngx_http_limit_req 및 ngx_http_limit_conn은 공유 메모리 영역을 사용하여 상태 데이터를 저장하여 모든 작업자 프로세스에서 사용자 요청 속도와 사용자 요청 동시성을 제한합니다. OpenResty의 ngx_lua 모듈은 lua_shared_dict를 통해 사용자 Lua 코드에 공유 메모리 기반 데이터 사전 저장소를 제공합니다.
이 기사에서는 몇 가지 간단하고 독립적인 예를 통해 이러한 공유 메모리 영역이 물리적 메모리 리소스(또는 RAM)를 어떻게 사용하는지 살펴봅니다. 또한 ps
/와 같은 시스템 도구의 결과에서 VSZ
및 RSS 및 기타 지표. <code>ps
等系统工具的结果中的 VSZ
和 RSS
等指标。
与本博客网站 中的几乎所有技术类文章类似,我们使用 OpenResty XRay 这款动态追踪产品对未经修改的 OpenResty 或 Nginx 服务器和应用的内部进行深度分析和可视化呈现。因为 OpenResty XRay 是一个非侵入性的分析平台,所以我们不需要对 OpenResty 或 Nginx 的目标进程做任何修改 -- 不需要代码注入,也不需要在目标进程中加载特殊插件或模块。这样可以保证我们通过 OpenResty XRay 分析工具所看到的目标进程内部状态,与没有观察者时的状态是完全一致的。
我们将在多数示例中使用 ngx_lua 模块的 lua_shared_dict,因为该模块可以使用自定义的 Lua 代码进行编程。我们在这些示例中展示的行为和问题,也同样适用于所有标准 Nginx 模块和第三方模块中的其他共享内存区。
Nginx 及其模块通常使用 Nginx 核心里的 slab 分配器 来管理共享内存区内的空间。这个slab 分配器专门用于在固定大小的内存区内分配和释放较小的内存块。
在 slab 的基础之上,共享内存区会引入更高层面的数据结构,例如红黑树和链表等等。
slab 可能小至几个字节,也可能大至跨越多个内存页。
操作系统以内存页为单位来管理进程的共享内存(或其他种类的内存)。
在 x86_64
Linux 系统中,默认的内存页大小通常是 4 KB,但具体大小取决于体系结构和 Linux 内核的配置。例如,某些 Aarch64
에 거의 모든 기술 기사가 있습니다. 우리는 OpenResty XRay
를 사용합니다. 동적 추적🎜이 제품은 수정되지 않은 OpenResty 또는 Nginx 서버 및 애플리케이션의 내부 깊숙이 침투합니다. 분석 및 시각적 프레젠테이션 . OpenResty XRay🎜는 비침습적 분석 플랫폼이므로 OpenResty 또는 Nginx의 대상 프로세스는 수정되지 않습니다. 코드 삽입이 필요하지 않으며 대상 프로세스에 특별한 플러그인이나 모듈을 로드할 필요도 없습니다. 이를 통해 OpenResty XRay🎜를 통해 대상 프로세스의 내부 상태를 확인할 수 있습니다. 관찰자가 없는 상태와 완전히 일치하는 분석 도구입니다. 🎜🎜이 모듈은 사용자 정의 Lua 코드로 프로그래밍할 수 있으므로 대부분의 예제에서 🎜ngx_lua🎜 모듈의 🎜lua_shared_dict🎜을 사용합니다. 이 예제에서 설명하는 동작과 문제는 모든 표준 Nginx 모듈 및 타사 모듈의 다른 공유 메모리 영역에도 적용됩니다. 🎜x86_64
Linux 시스템에서 기본 메모리 페이지 크기는 일반적으로 4KB이지만 정확한 크기는 아키텍처 및 Linux 커널 구성에 따라 다릅니다. 예를 들어, 일부 Aarch64
Linux 시스템의 메모리 페이지 크기는 최대 64KB입니다. 🎜🎜OpenResty 및 Nginx 프로세스의 공유 메모리 영역에 대한 자세한 내용을 메모리 페이지 수준과 슬랩 수준에서 각각 살펴보겠습니다. 🎜하드 드라이브와 같은 리소스와 달리 물리적 메모리(또는 RAM)는 항상 매우 귀중한 리소스입니다.
대부분의 최신 운영 체제는 사용자 애플리케이션이 RAM 리소스에 대한 부담을 줄이는 데 사용되는 요구 페이징(요구 페이징)이라는 최적화 기술을 구현했습니다. 특히, 큰 메모리 블록을 할당하면 운영 체제 커널은 메모리 페이지의 데이터가 실제로 사용될 때까지 RAM 리소스(또는 물리적 메모리 페이지)의 실제 할당을 연기합니다. 예를 들어, 사용자 프로세스에 10개의 메모리 페이지가 할당되었지만 그 중 3개만 사용하는 경우 운영 체제는 이 3개의 메모리 페이지만 RAM 장치에 매핑했을 수 있습니다. 이 동작은 Nginx 또는 OpenResty 애플리케이션에 할당된 공유 메모리 영역에도 적용됩니다. 사용자는 nginx.conf
파일에서 거대한 공유 메모리 영역을 구성할 수 있지만 서버가 시작된 후에는 추가 메모리가 거의 차지하지 않는다는 것을 알 수 있습니다. 결국 일반적으로 추가 메모리가 거의 없습니다. 처음 시작될 때의 메모리 공유 메모리 페이지가 실제로 사용됩니다. nginx.conf
文件中配置庞大的共享内存区,但他可能会注意到在服务器启动之后,几乎没有额外占用多少内存,毕竟通常在刚启动的时候,几乎没有共享内存页被实际使用到。
我们以下面这个 nginx.conf
文件为例。该文件分配了一个空的共享内存区,并且从没有使用过它:
master_process on; worker_processes 2; events { worker_connections 1024; } http { lua_shared_dict dogs 100m; server { listen 8080; location = /t { return 200 "hello world\n"; } } }
我们通过 lua_shared_dict 指令配置了一个 100 MB 的共享内存区,名为 dogs
。并且我们为这个服务器配置了 2 个工作进程。请注意,我们在配置里从没有触及这个 dogs
区,所以这个区是空的。
可以通过下列命令启动这个服务器:
mkdir ~/work/ cd ~/work/ mkdir logs/ conf/ vim conf/nginx.conf # paste the nginx.conf sample above here /usr/local/openresty/nginx/sbin/nginx -p $PWD/
然后用下列命令查看 nginx 进程是否已在运行:
$ ps aux|head -n1; ps aux|grep nginx USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND agentzh 9359 0.0 0.0 137508 1576 ? Ss 09:10 0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx -p /home/agentzh/work/ agentzh 9360 0.0 0.0 137968 1924 ? S 09:10 0:00 nginx: worker process agentzh 9361 0.0 0.0 137968 1920 ? S 09:10 0:00 nginx: worker process
这两个工作进程占用的内存大小很接近。下面我们重点研究 PID 为 9360 的这个工作进程。在 OpenResty XRay 控制台的 Web 图形界面中,我们可以看到这个进程一共占用了 134.73 MB 的虚拟内存(virtual memory)和 1.88 MB 的常驻内存(resident memory),这与上文中的 ps
命令输出的结果完全相同:
正如我们的另一篇文章 《OpenResty 和 Nginx 如何分配和管理内存》中所介绍的,我们最关心的就是常驻内存的使用量。常驻内存将硬件资源实际映射到相应的内存页(如 RAM 1)。所以我们从图中看到,实际映射到硬件资源的内存量很少,总计只有 1.88MB。上文配置的 100 MB 的共享内存区在这个常驻内存当中只占很小的一部分(详情请见后续的讨论)。
当然,共享内存区的这 100 MB 还是全部贡献到了该进程的虚拟内存总量中去了。操作系统会为这个共享内存区预留出虚拟内存的地址空间,不过,这只是一种簿记记录,此时并不占用任何的 RAM 资源或其他硬件资源。
空 不是 空无一物
我们可以通过该进程的“应用层面的内存使用量的分类明细”图,来检查空的共享内存区是否占用了常驻(或物理)内存。
有趣的是,我们在这个图中看到了一个非零的 Nginx Shm Loaded
(已加载的 Nginx 共享内存)组分。这部分很小,只有 612 KB,但还是出现了。所以空的共享内存区也并非空无一物。这是因为 Nginx 已经在新初始化的共享内存区域中放置了一些元数据,用于簿记目的。这些元数据为 Nginx 的 slab 分配器所使用。
已加载和未加载内存页
我们可以通过 OpenResty XRay 自动生成的下列图表,查看共享内存区内被实际使用(或加载)的内存页数量。
我们发现在dogs
区域中已经加载(或实际使用)的内存大小为 608 KB,同时有一个特殊的 ngx_accept_mutex_ptr
被 Nginx 核心自动分配用于 accept_mutex 功能。
这两部分内存的大小相加为 612 KB,正是上文的饼状图中显示的 Nginx Shm Loaded
nginx.conf
파일을 예로 들어 보겠습니다. 파일은 빈 공유 메모리 영역을 할당하고 절대 사용하지 않습니다: 🎜init_by_lua_block { for i = 1, 300000 do ngx.shared.dogs:set("key" .. i, i) end }🎜우리는 lua_shared_dict🎜 지시어는
dogs
라는 100MB 공유 메모리 영역을 구성합니다. 그리고 이 서버에 대해 2개의 작업자 프로세스를 구성했습니다. 구성에서 dogs
섹션은 절대 건드리지 않으므로 비어 있습니다. 🎜🎜다음 명령으로 이 서버를 시작할 수 있습니다: 🎜kill -QUIT `cat logs/nginx.pid` /usr/local/openresty/nginx/sbin/nginx -p $PWD/🎜 그런 다음 다음 명령을 사용하여 nginx 프로세스가 실행 중인지 확인합니다. 🎜
$ ps aux|head -n1; ps aux|grep nginx USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND agentzh 29733 0.0 0.0 137508 1420 ? Ss 13:50 0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx -p /home/agentzh/work/ agentzh 29734 32.0 0.5 138544 41168 ? S 13:50 0:00 nginx: worker process agentzh 29735 32.0 0.5 138544 41044 ? S 13:50 0:00 nginx: worker process🎜이 두 작업자 프로세스가 차지하는 메모리 크기는 매우 가깝습니다. 아래에서는 PID 9360을 사용한 작업 프로세스에 중점을 둡니다. OpenResty XRay🎜 콘솔의 웹 그래픽 인터페이스에서 이 프로세스를 볼 수 있습니다. 134.73MB의 가상 메모리와 1.88MB의 상주 메모리가 사용되며 이는 위의
ps
명령에 의해 출력된 결과와 정확히 동일합니다: 🎜🎜🎜🎜다른 기사 "OpenResty와 Nginx가 메모리를 할당하고 관리하는 방법"🎜에서 소개했듯이 우리가 가장 우려하는 것은 상주 메모리의 사용입니다. 상주 메모리는 실제로 하드웨어 리소스를 해당 메모리 페이지(예: RAM 1🎜 )에 매핑합니다. 따라서 실제로 하드웨어 리소스에 매핑된 메모리 양은 총 1.88MB에 불과하여 매우 작다는 것을 그림에서 볼 수 있습니다. 위에서 구성된 100MB 공유 메모리 영역은 이 상주 메모리의 작은 부분만을 차지합니다(자세한 내용은 후속 설명 참조). 🎜🎜물론 공유 메모리 영역의 100MB는 모두 프로세스의 전체 가상 메모리에 기여합니다. 운영 체제는 이 공유 메모리 영역을 위해 가상 메모리 주소 공간을 예약합니다. 그러나 이는 단지 기록일 뿐이며 현재로서는 RAM 리소스나 기타 하드웨어 리소스를 차지하지 않습니다. 🎜🎜🎜Empty는 아닙니다 아무것도 아닙니다 🎜🎜🎜"프로세스 분류 세부 정보의 애플리케이션 수준 메모리 사용량"을 전달할 수 있습니다. 빈 공유 메모리 영역이 상주(또는 물리적) 메모리를 점유하는지 확인하는 다이어그램입니다. 🎜🎜🎜🎜흥미롭네요 흥미로운 점은 이 그림에서 0이 아닌 Nginx Shm Loaded
(로드된 Nginx 공유 메모리) 구성 요소를 볼 수 있다는 것입니다. 해당 부분은 612KB에 불과한 작은 크기이지만 그럼에도 불구하고 표시됩니다. 따라서 비어 있는 공유 메모리 영역은 비어 있지 않습니다. 이는 Nginx가 장부 관리 목적으로 새로 초기화된 공유 메모리 영역에 일부 메타데이터를 배치했기 때문입니다. 이 메타데이터는 Nginx의 슬랩 할당자에서 사용됩니다. 🎜🎜🎜로드 및 언로드된 메모리 페이지🎜🎜🎜 OpenResty XRay🎜는 공유 메모리 영역에서 실제로 사용된(또는 로드된) 메모리 페이지 수를 확인하기 위해 다음 차트를 자동으로 생성합니다. 🎜🎜🎜🎜us dogs
영역에 로드된(또는 실제로 사용된) 메모리 크기는 608KB이며, 자동으로 할당되는 특수한 ngx_accept_mutex_ptr
이 있는 것으로 확인되었습니다. accept_mutex 함수를 위한 Nginx 코어입니다. 🎜🎜이 두 메모리 부분을 합친 크기는 612KB이며 이는 위 원형 차트에 표시된 Nginx Shm Loaded
의 크기와 정확히 같습니다. 🎜如前文所述,dogs
区使用的 608 KB 内存实际上是 slab 分配器 使用的元数据。
未加载的内存页只是被保留的虚拟内存地址空间,并没有被使用过。
关于进程的页表
我们没有提及的一种复杂性是,每一个 nginx 工作进程其实都有各自的页表。CPU 硬件或操作系统内核正是通过查询这些页表来查找虚拟内存页所对应的存储。因此每个进程在不同共享内存区内可能有不同的已加载页集合,因为每个进程在运行过程中可能访问过不同的内存页集合。为了简化这里的分析,OpenResty XRay 会显示所有的为任意一个工作进程加载过的内存页,即使当前的目标工作进程从未碰触过这些内存页。也正因为这个原因,已加载内存页的总大小可能(略微)高于目标进程的常驻内存的大小。
空闲的和已使用的 slab
如上文所述,Nginx 通常使用 slabs 而不是内存页来管理共享内存区内的空间。我们可以通过 OpenResty XRay 直接查看某一个共享内存区内已使用的和空闲的(或未使用的)slabs 的统计信息:
如我们所预期的,我们这个例子里的大部分 slabs 是空闲的或未被使用的。注意,这里的内存大小的数字远小于上一节中所示的内存页层面的统计数字。这是因为 slabs 层面的抽象层次更高,并不包含 slab 分配器针对内存页的大小补齐和地址对齐的内存消耗。
我们可以通过OpenResty XRay进一步观察在这个 dogs
区域中各个 slab 的大小分布情况:
我们可以看到这个空的共享内存区里,仍然有 3 个已使用的 slab 和 157 个空闲的 slab。这些 slab 的总个数为:3 + 157 = 160个。请记住这个数字,我们会在下文中跟写入了一些用户数据的 dogs
区里的情况进行对比。
下面我们会修改之前的配置示例,在 Nginx 服务器启动时主动写入一些数据。具体做法是,我们在 nginx.conf
文件的 http {}
配置分程序块中增加下面这条 init_by_lua_block 配置指令:
init_by_lua_block { for i = 1, 300000 do ngx.shared.dogs:set("key" .. i, i) end }
这里在服务器启动的时候,主动对 dogs
共享内存区进行了初始化,写入了 300,000 个键值对。
然后运行下列的 shell 命令以重新启动服务器进程:
kill -QUIT `cat logs/nginx.pid` /usr/local/openresty/nginx/sbin/nginx -p $PWD/
新启动的 Nginx 进程如下所示:
$ ps aux|head -n1; ps aux|grep nginx USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND agentzh 29733 0.0 0.0 137508 1420 ? Ss 13:50 0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx -p /home/agentzh/work/ agentzh 29734 32.0 0.5 138544 41168 ? S 13:50 0:00 nginx: worker process agentzh 29735 32.0 0.5 138544 41044 ? S 13:50 0:00 nginx: worker process
虚拟内存与常驻内存
针对 Nginx 工作进程 29735,OpenResty XRay 生成了下面这张饼图:
显然,常驻内存的大小远高于之前那个空的共享区的例子,而且在总的虚拟内存大小中所占的比例也更大(29.6%)。
虚拟内存的使用量也略有增加(从 134.73 MB 增加到了 135.30 MB)。因为共享内存区本身的大小没有变化,所以共享内存区对于虚拟内存使用量的增加其实并没有影响。这里略微增大的原因是我们通过 init_by_lua_block 指令新引入了一些 Lua 代码(这部分微小的内存也同时贡献到了常驻内存中去了)。
应用层面的内存使用量明细显示,Nginx 共享内存区域的已加载内存占用了最多常驻内存:
已加载和未加载内存页
이제 이 dogs
공유 메모리 영역에는 로드된 메모리 페이지가 더 많고 로드되지 않은 메모리 페이지 수도 크게 줄었습니다. dogs
共享内存区里,已加载的内存页多了很多,而未加载的内存页也有了相应的显著减少:
空的和已使用的 slab
现在 dogs
共享内存区增加了 300,000 个已使用的 slab(除了空的共享内存区中那 3 个总是会预分配的 slab 以外):
显然,lua_shared_dict 区中的每一个键值对,其实都直接对应一个 slab。
空闲 slab 的数量与先前在空的共享内存区中的数量是完全相同的,即 157 个 slab:
正如我们上面所演示的,共享内存区在应用实际访问其内部的内存页之前,都不会实际耗费物理内存资源。因为这个原因,用户可能会观察到 Nginx 工作进程的常驻内存大小似乎会持续地增长,特别是在进程刚启动之后。这会让用户误以为存在内存泄漏。下面这张图展示了这样的一个例子:
通过查看 OpenResty XRay 生成的应用级别的内存使用明细图,我们可以清楚地看到 Nginx 的共享内存区域其实占用了绝大部分的常驻内存空间:
这种内存增长是暂时的,会在共享内存区被填满时停止。但是当用户把共享内存区配置得特别大,大到超出当前系统中可用的物理内存的时候,仍然是有潜在风险的。正因为如此,我们应该注意观察如下所示的内存页级别的内存使用量的柱状图:
图中蓝色的部分可能最终会被进程用尽(即变为红色),而对当前系统产生冲击。
Nginx 支持通过 HUP 信号来重新加载服务器的配置而不用退出它的 master 进程(worker 进程仍然会优雅退出并重启)。通常 Nginx 共享内存区会在 HUP 重新加载(HUP reload)之后自动继承原有的数据。所以原先为已访问过的共享内存页分配的那些物理内存页也会保留下来。于是想通过 HUP 重新加载来释放共享内存区内的常驻内存空间的尝试是会失败的。用户应改用 Nginx 的重启或二进制升级操作。
值得提醒的是,某一个 Nginx 模块还是有权决定是否在 HUP 重新加载后保持原有的数据。所以可能会有例外。
我们在上文中已经解释了 Nginx 的共享内存区所占用的物理内存资源,可能远少于 분명히 lua_shared_dict 각 키-값 쌍 이 영역에서는 실제로 슬래브에 직접적으로 해당합니다. nginx.conf
거짓 메모리 누수
🎜 위에서 설명한 것처럼, 공유 메모리 영역은 애플리케이션이 실제로 내부 메모리 페이지에 액세스할 때까지 실제 메모리 리소스를 소비하지 않습니다. 이러한 이유로 사용자는 Nginx 작업자 프로세스의 상주 메모리 크기가 특히 프로세스가 시작된 직후 지속적으로 증가하는 것으로 나타날 수 있습니다. 이로 인해 사용자는 메모리 누수가 있다고 잘못 믿게 될 수 있습니다. 아래 그림은 이에 대한 예를 보여줍니다: 🎜🎜🎜🎜OpenResty XRay에서 생성된 애플리케이션 수준 메모리 사용량 세부 정보 그래프를 보면 Nginx의 공유 메모리 영역이 실제로 상주 메모리 공간의 대부분을 차지하고 있음을 분명히 알 수 있습니다. 🎜🎜🎜🎜이런 종류의 메모리 증가는 일시적이며 앞으로도 계속될 것입니다. 공유 메모리 영역이 가득 차면 중지합니다. 그러나 사용자가 공유 메모리 영역을 매우 크게 구성하여 현재 시스템에서 사용 가능한 물리적 메모리를 초과하는 경우 여전히 잠재적인 위험이 있습니다. 이 때문에 아래와 같이 메모리 페이지 수준의 메모리 사용량 히스토그램에 주의해야 합니다. 🎜🎜🎜🎜그림의 파란색 부분은 결국 프로세스에 의해 사용(즉, 빨간색으로 변)될 수 있으며, 이는 현재 시스템. 🎜HUP 다시 로드
🎜Nginx는 마스터 프로세스를 종료하지 않고 서버 구성 🎜을 다시 로드합니다(작업자 프로세스는 계속해서 정상적으로 종료되고 다시 시작됩니다). 일반적으로 Nginx 공유 메모리 영역은 HUP 다시 로드 후 자동으로 원본 데이터를 상속합니다. 따라서 액세스된 공유 메모리 페이지에 원래 할당된 실제 메모리 페이지도 유지됩니다. 따라서 HUP 재로드를 통해 공유 메모리 영역의 상주 메모리 공간을 해제하려는 시도는 실패합니다. 대신 사용자는 Nginx 다시 시작 또는 바이너리 업그레이드🎜 작업을 사용해야 합니다. 🎜🎜특정 Nginx 모듈에는 HUP가 다시 로드된 후에도 원본 데이터를 유지할지 여부를 결정할 권리가 있다는 점을 상기할 가치가 있습니다. 따라서 예외가 있을 수 있습니다. 🎜결론
🎜 위에서 설명했듯이 Nginx의 공유 메모리 영역이 차지하는 물리적 메모리 리소스는 nginx.conf
보다 훨씬 작을 수 있습니다. 구성된 크기 파일에서. 이는 최신 운영 체제의 🎜Demand Paging🎜 기능 덕분입니다. 우리는 일부 메모리 페이지와 슬랩이 슬랩 할당자 자체에 필요한 메타데이터를 저장하기 위해 빈 공유 메모리 영역에서 계속 사용된다는 것을 보여주었습니다. 🎜OpenResty XRay🎜의 고급 분석기를 통해 실행 중인 nginx 작업자 프로세스를 실시간으로 확인하고 두 가지 다른 수준의 메모리 페이지와 슬랩을 포함하여 공유 메모리 영역에서 사용되거나 로드된 실제 메모리를 볼 수 있습니다. 🎜🎜반면에 온디맨드 페이징 최적화로 인해 일정 기간 동안 메모리가 계속 증가하게 됩니다. 이는 실제로 메모리 누수는 아니지만 여전히 특정 위험을 초래합니다. 또한 Nginx의 HUP 다시 로드 작업은 일반적으로 공유 메모리 영역의 기존 데이터를 지우지 않는다는 점을 설명했습니다🎜🎜권장 튜토리얼: 🎜nginx 튜토리얼🎜🎜
위 내용은 OpenResty 및 Nginx의 공유 메모리 영역에서 물리적 메모리 리소스(또는 RAM)의 사용을 살펴보시겠습니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!