乐趣区

关于openresty:优化超大-Nginx-配置导致的内存碎片

咱们最近应用 OpenResty XRay 帮忙一个销售 CDN 和流量网关服务的企业客户优化了他们的 OpenResty/Nginx 服务器的内存应用。这个客户在他们的 OpenResty/Nginx 配置文件中定义了许多虚构服务器和 URI location。OpenResty XRay 在客户的生产环境中主动进行了大部分剖析,基于剖析后果给出的计划让 nginx 过程的内存占用缩小了大概 30%

和咱们的 OpenResty Edge 的 nginx worker 过程相比显示, 进一步的优化将会持续缩小约 90%

OpenResty XRay 是一个动静追踪产品,它能够主动剖析正在运行中的应用程序,以排除性能问题、行为问题和安全漏洞,并提供可行的倡议。在底层实现上,OpenResty XRay 由咱们的 Y 语言驱动,能够在不同环境下反对多种不同的运行时,如 Stap+, eBPF+, GDB 和 ODB。

挑战

这个 CDN 供应商应用一个超大的“nginx.conf”配置文件来为他们的 OpenResty 服务器中的近万个虚拟主机服务。每个 nginx 主过程在启动后占用了好几个 G 的内存,在一次或屡次 HUP reload 后,内存简直翻倍。从上面 OpenResty XRay 生成的图中能够看出,最大内存占用约为 4.60GB。

咱们能够从 OpenResty XRay 的利用层面内存应用明细表中看到,Glibc 分配器占用了大部分常驻内存,有 4.55GB。

而 OpenResty XRay 发现 Nginx cycle pool 占用了大量的内存:

当 Nginx 加载配置文件时,咱们都晓得它为这个 cycle pool 内的配置数据调配了数据结构。尽管宏大到有 1.62GB,但远远小于下面提到的 4.60GB。

RAM 仍然是低廉和稀缺的硬件资源,特地是在 AWS 和 GCP 这样的私有云上。客户心愿通过降级到内存较小的机器来节约老本。

剖析

OpenResty XRay 对客户的在线过程进行了深入分析。它不须要客户的应用程序进行任何合作。

  • 没有额定的插件、模块或库。
  • 没有代码注入或补丁。
  • 没有非凡的编译或启动选项。
  • 甚至不须要重新启动应用程序过程。

剖析齐全是以“预先”的形式进行的。多亏了 Openresty XRay 采纳的动静跟踪技术。

太多的闲暇区块

OpenResty XRay 用 Glibc 内存分配器的分析器主动对在线 nginx 过程进行采样。分析器生成了以下柱状图,显示了分配器治理的闲暇块的大小是如何散布的。

Glibc 分配器通常不会立刻开释闲暇块给操作系统(OS)。它可能会保留一些闲暇块,以放慢后续的调配速度。但无意保留的通常很小,不可能到上 G 字节。这里咱们看到闲暇块的大小累计曾经达到 2.3GB。因而,更常见的起因是内存碎片。

查看一般堆中的内存碎片问题

大多数小的内存调配通过 brk Linux 零碎调用,产生在“一般堆”中。这个堆就像一个线性的 “ 堆“,只能通过挪动其”顶部 ” 指针来减少或缩小。在堆两头的所有闲暇块不能被开释给操作系统。直到它们下面的所有块也变成闲暇,它们才会被开释。

OpenResty XRay 的内存分析器能够帮忙咱们查看这种堆的状态。请看上面的堆图,它是在 nginx 主过程响应 HUP 信号加载新配置后的采样图。

咱们能够看到,堆是向上增长的,也就是说,向高位内存地址增长。留神 brk top 指针,这是惟一能够挪动的货色。绿色框属于 Nginx 的新“cycle pool“,而粉色框属于旧”cycle pool”。一个乏味的景象是,Nginx 会保留旧的 cycle pool 或旧的配置数据,直到新的 cycle pool 被胜利加载。这种行为是因为 Nginx 的爱护机制,当新的配置加载失败时,会优雅地退回到旧的配置。可怜的是,正如咱们在下面看到的,旧的配置数据的盒子(绿色)在新的数据(粉色)上面,因而只有当新的配置数据也被开释后,它们能力开释到操作系统。

事实上,在 Nginx 开释了旧的配置数据和旧的 cycle pool 后,它们原来的地位变成了闲暇块,被卡在新的 cycle pool 的块上面。

这是一个教科书式的内存碎片化的例子。一般堆只能在顶部开释内存;因而,它比其余内存分配机制,如 mmap 零碎调用,更容易受到内存碎片的影响。然而,mmap 会在这里援救咱们吗?不肯定。

mmap 的世界

Glibc 分配器也能够通过 mmap 零碎调用来分配内存。这些零碎调用调配离散的内存块或内存段,这些内存块或内存段可能位于过程地址空间的简直任何地址,并逾越任何数量的内存页。

这听起来是一个缓解上述内存碎片问题的好办法。然而当咱们无意阻断一般堆的增长形式时,依据 OpenResty XRay 的分析器所产生的图表,相似水平的内存碎片依然产生。

当应用程序(这里是 Nginx)申请调配较小内存块的时候,Glibc 偏向于调配绝对较大的内存段,这里是 1MB。因而,内存碎片依然会产生在这些 1MB 的 mmap 段内。如果一个小内存块仍在应用,那么整个内存段就不会被开释给操作系统。

在上图中,咱们能够看到旧的 cycle pool 块(粉红色)和新的 cycle pool 块(绿色)依然在许多 mmap 段中交织。

解决方案

咱们为客户提出了几个解决方案。

简略形式

最简略的形式是间接解决内存碎片的问题。依据下面咱们应用 OpenResty XRay 做的剖析,咱们应该做以下一个或多个变动。

  1. 防止在“一般堆”中调配 cycle pool 内存(即勾销这种调配的 brk 零碎调用)。
  2. 要求 Glibc 应用适当的 mmap 段内存大小(不要太大!)来满足 cycle pool 的内存调配申请。
  3. 将不同 cycle pool 的内存块洁净地拆散到不同的 mmap 段中。

咱们为 OpenResty XRay 的付费客户提供具体的优化阐明。因而基本就不须要编码。

更好的形式

是的,还有一个更好的形式。开源的 OpenResty 软件提供了 Lua APIs 和 Nginx 配置指令,以动静加载(和卸载)Lua 层面的新的配置数据,而不须要通过 Nginx 配置文件机制。这使得应用一个小的恒定大小的内存来解决更多的虚构 server 和 location 的配置数据成为可能。同时,Nginx 服务器的启动和从新加载工夫也大大缩短(从很多秒到简直为零)。事实上,有了动静配置加载,HUP reload 操作自身变得十分常见。这种形式的一个毛病是,这须要在咱们用户侧进行一些额定的 Lua 编码。

咱们的 OpenResty Edge 软件产品以 OpenResty 作者所构想的最佳形式实现了这种动静配置加载和卸载。它不须要用户进行任何编码。所以这也是一个容易的选项。

后果

这位客户决定先尝试简略的形式,后果在几次 HUP reload 后,总的内存占用缩小了 30%

依然有一些残余的片段值得进一步关注。但咱们的客户曾经很称心了。此外,下面提到的更好的形式能够节俭超过 90% 的总内存占用(就像在咱们的 OpenResty Edge 产品中一样):

对于作者

章亦春是开源 OpenResty® 我的项目创始人兼 OpenResty Inc. 公司 CEO 和创始人。

章亦春(Github ID: agentzh),生于中国江苏,现定居美国湾区。他是中国晚期开源技术和文化的倡导者和领军人物,曾供职于多家国内出名的高科技企业,如 Cloudflare、雅虎、阿里巴巴, 是“边缘计算“、”动静追踪“和“机器编程“的先驱,领有超过 22 年的编程及 16 年的开源教训。作为领有超过 4000 万寰球域名用户的开源我的项目的领导者。他基于其 OpenResty® 开源我的项目打造的高科技企业 OpenResty Inc. 位于美国硅谷核心。其主打的两个产品 OpenResty XRay(利用动静追踪技术的非侵入式的故障分析和排除工具)和 OpenResty Edge(最适宜微服务和分布式流量的全能型网关软件),广受寰球泛滥上市及大型企业青眼。在 OpenResty 以外,章亦春为多个开源我的项目奉献了累计超过百万行代码,其中包含,Linux 内核、Nginx、LuaJIT、GDB、SystemTap、LLVM、Perl 等,并编写过 60 多个开源软件库。

关注咱们

如果您喜爱本文,欢送关注咱们 OpenResty Inc.
公司的博客网站。

咱们也在 B 站上也有 OpenResty 官网的视频分享空间,欢送订阅。

同时欢送扫码关注咱们的微信公众号:

退出移动版