[转帖]优化超大 Nginx 配置导致的内存碎片
https://blog.openresty.com.cn/cn/ngx-cycle-pool-frag/?src=org_news
我们最近使用 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 官方的视频分享空间,欢迎订阅。
同时欢迎扫码关注我们的微信公众号:
[转帖]优化超大 Nginx 配置导致的内存碎片的更多相关文章
- Nginx使用教程(三):Nginx配置性能优化之I/O和TCP配置
配置Nginx I/O <br\> Sendfile 当应用程序传输文件时,内核首先缓冲数据,然后将数据发送到应用程序缓冲区. 应用程序反过来将数据发送到目的地. Sendfile方法是一 ...
- [转帖]nginx配置ssl加密(单/双向认证、部分https)
nginx配置ssl加密(单/双向认证.部分https) https://segmentfault.com/a/1190000002866627 nginx下配置ssl本来是很简单的,无论是去认证 ...
- Nginx服务器性能优化与安全配置实践指南
转载自:https://www.bilibili.com/read/cv16151784?spm_id_from=333.999.0.0 1.引言 1.1 目的 为了更好的指导部署与测试艺术升系统ng ...
- Nginx配置性能优化
大多数的Nginx安装指南告诉你如下基础知识--通过apt-get安装,修改这里或那里的几行配置,好了,你已经有了一个Web服务器了.而且,在大多数情况下,一个常规安装的nginx对你的网站来说已经能 ...
- nginx 配置优化的几个参数
nginx 配置优化的几个参数 2011-04-22 本文地址: http://blog.phpbean.com/a.cn/7/ --水平有限欢迎指正-- -- 最近在服务器上搞了一些nginx 研究 ...
- Nginx配置性能优化(转)
大多数的Nginx安装指南告诉你如下基础知识——通过apt-get安装,修改这里或那里的几行配置,好了,你已经有了一个Web服务器了.而且,在大多数情况下,一个常规安装的nginx对你的网站来说已经能 ...
- Nginx配置性能优化与压力测试webbench【转】
这一篇我们来说Nginx配置性能优化与压力测试webbench. 基本的 (优化过的)配置 我们将修改的唯一文件是nginx.conf,其中包含Nginx不同模块的所有设置.你应该能够在服务器的/et ...
- Nginx配置不当可能导致的安全问题
Nginx配置不当可能导致的安全问题 Auther: Spark1e目前很多网站使用了nginx或者tenginx(淘宝基于Nginx研发的web服务器)来做反向代理和静态服务器,ningx的配置文件 ...
- 关于Nginx配置性能优化
基本的 (优化过的)配置 将修改的唯一文件是nginx.conf,其中包含Nginx不同模块的所有设置.在服务器的/etc/nginx目录中找到nginx.conf. 首先,我们将谈论一些全局设置,然 ...
- Nginx配置优化参考
Nginx配置优化参考 ...
随机推荐
- 如何使用.NET在2.2秒内处理10亿行数据(1brc挑战)
译者注 在上周我就关注到了在github上有1brc这样一个挑战,当时看到了由Victor Baybekov提交了.NET下最快的实现,当时计划抽时间写一篇文章解析他的代码实现,今天突然看到作者自己写 ...
- 为什么浏览器会提示网站“不安全”?一文读懂https协议与SSL证书
[摘要] 为什么浏览器会提示网站"不安全"?从浏览器的"不安全"提示来详细了解https与SSL证书.我们打开很多http网站时候,会看到浏览器提示" ...
- 带你了解WDR-GaussDB(DWS) 的性能监测报告
摘要:通过本文,读者可知晓什么是WDR,如何创建性能数据快照以及生成WDR报告. 本文分享自华为云社区<WDR-GaussDB(DWS) 的性能监测报告>,作者:Zhang Jingyao ...
- 鸿蒙轻内核源码分析:Newlib C
摘要:本文介绍了LiteOS-M内核Newlib C的实现,特别是文件系统和内存分配释放部分,最后介绍了Newlib钩子函数. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二十 Newlib ...
- 在springboot中,如何读取配置文件中的属性
摘要:在比较大型的项目的开发中,比较经常修改的属性我们一般都是不会在代码里面写死的,而是将其定义在配置文件中,之后如果修改的话,我们可以直接去配置文件中修改,那么在springboot的项目中,我们应 ...
- JAVA CRC8
Java CRC8 /** * CRC-8 * * <table width="400px" border="1" cellpadding="0 ...
- schedule 定时运行 Python 函数
安装 pip install schedule 例子 每x分钟运行一次 import schedule import time def job(): print("I'm working.. ...
- WebSoket 的广泛应用
目前大多数网站都在使用的传统 HTTP 协议,即由 Web 服务器通过 HTTP 接收并响应来自客户端的消息,整个发起请求与响应的过程类似我们点外卖,由以下 2 部分构成: 下订单(发起请求):用户( ...
- [译]为什么你应该关注 Docker
注:该文原文为 Why You Should Care About Docker ,由 CHRIS DAWSON 编写. 当我在 Dockercon 上陶醉于那些令人激动地议题时,我想到了一个问题:我 ...
- Spring注解@Resource和@Autowired区别对比 (附 Maven 引入方法)
@Resource 导入方法: <dependency> <groupId>javax.annotation</groupId> <artifactId> ...