共计 3331 个字符,预计需要花费 9 分钟才能阅读完成。
咱们最近应用 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 做的剖析,咱们应该做以下一个或多个变动。
- 防止在“一般堆”中调配 cycle pool 内存(即勾销这种调配的
brk
零碎调用)。 - 要求 Glibc 应用适当的 mmap 段内存大小(不要太大!)来满足 cycle pool 的内存调配申请。
- 将不同 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 官网的视频分享空间,欢送订阅。
同时欢送扫码关注咱们的微信公众号: