当一个 LRU 缓存的容量设置得过大、几乎从不淘汰缓存项时,它就会引发内存泄漏——每一个缓存对象实际上都被永久保留。在这个真实案例中,一个 OpenResty 应用把解析后的 SSL 证书缓存进了 lua-resty-lrucache,底层的 OpenSSL 结构体不断堆积,直到 worker 进程 RSS 无限增长。下面看 OpenResty XRay 如何在不改代码、不重启的情况下把它追踪到根因。

为什么一个低流量的 OpenResty 应用会持续泄漏内存

某客户的 OpenResty 应用虽然业务流量很小,但其 worker 进程的内存占用却随着时间的推移而持续增长,远超预期。这种典型的内存泄漏现象不仅造成了严重的资源浪费,更给线上业务的稳定性带来了巨大隐患。由于缺乏有效的分析工具,客户对泄漏的根源毫无头绪。OpenResty XRay 团队随即介入,利用其强大的动态追踪和分析能力,对线上运行的 OpenResty 进程进行了非侵入式的诊断。

一步步追踪 LRU 缓存内存泄漏的完整过程

从 RSS 增长趋势确认泄漏

我们首先通过 OpenResty XRay 的进程内存趋势图,直观地验证了客户的报告。

OpenResty XRay 进程内存趋势图:worker 进程 RSS 呈线性增长,是内存泄漏的典型特征

监控数据显示,OpenResty 进程的 RSS(Resident Set Size)内存呈现出明显的线性增长,这是内存泄漏的典型特征。

将泄漏范围缩小到 Glibc Arena,而非 Nginx 内存池

为了弄清是哪部分内存出了问题,我们使用了 OpenResty XRay 的内存分析功能。自动分析报告显示:

  • Glibc 内存分配 占据了总内存的约 93%,是主要的内存消耗来源。
  • LuaJIT 内存分配 仅占约 2.4%。

OpenResty XRay 内存分析:Glibc 分配约占总内存 93%,LuaJIT 仅约 2.4%

通过 resty-memory 的内存分解图深入分析 Glibc 的内存使用,我们发现其增长完全来自于 Glibc Arena

resty-memory 内存分解图:内存增长完全来自 Glibc Arena

通常,Nginx 自身的内存池 Nginx memory pool 会通过 Arena 区域进行分配,但我们观察到 Nginx 内存池的占用量非常小且稳定。

OpenResty XRay 内存分解图中 Nginx 内存池占用量小且稳定

可以看到 Glibc 分配的内存随时间线性增长,而 Nginx memory pool 的内存使用量非常的小。

Glibc 分配的内存随时间线性增长,而 Nginx 内存池占用可忽略不计

这表明,泄漏并非源于常规的请求处理,而是由其他模块通过 Glibc 直接申请的内存。

用火焰图定位到 lua-resty-lrucache 对象

究竟是哪些代码在不断申请 Arena 内存并且从不释放?OpenResty XRay内存泄漏火焰图给出了答案。

OpenResty XRay 内存泄漏火焰图指向 SSL/TLS 证书和私钥解析环节

内存泄露火焰图显示,内存泄漏发生在解析 SSL/TLS 证书和私钥的环节。

但为什么会泄漏?我们转而使用 Lua GC 对象火焰图 来分析 Lua 层面上的对象引用关系。这与我们用命令行工具定位泄漏的 Lua table 是同一套对象级视图。

Lua GC 对象火焰图显示 cert_cache 这个 lua-resty-lrucache 对象占用了绝大部分内存

根据火焰图显示,一个名为 _LOADED.dynamic_cert.cert_cache 的 Lua table 占用了绝大部分内存。

由于其内部结构包含 .free_queu, .hashht,.key2node,.node2key 等字段,我们判断这是一个 lua-resty-lrucache 对象。

结合 cert_cache 这个表名称以及内存泄露火焰图,我们可以推测,用户把通过 ssl.parse_pem_certssl.parse_pem_priv_key 解析的结果缓存到 lru cache 中。

至此,整个问题链路完全清晰:

  1. 客户为了优化性能,将动态加载的 SSL 证书和私钥解析结果缓存到了一个 LRU 缓存 cert_cache 中。
  2. 问题在于,这个 LRU 缓存在创建时设置的容量过大,导致它几乎永远不会淘汰任何缓存项。
  3. 因此,每一个新域名证书被解析后,其结果对象都被“永久”地存放在了 LRU 缓存中。这些对象被缓存引用,导致 Lua 的 GC 回收器无法回收它们,其底层的 OpenSSL 证书结构体所占用的内存也随之泄漏。
  4. 随着时间推移,解析的证书越来越多,内存占用也就随之线性增长。

最终结果:从无限增长到内存平稳

很多企业都遇到过类似的情况:系统内存使用持续飙升,却迟迟找不到根本原因,明明问题很小,但一不小心就可能导致线上服务宕机——就像我们不重启服务就排查掉的另一个 OpenResty 隐形内存泄漏

OpenResty XRay 只用了一次分析,就帮客户解决了这个长期未解的内存泄漏难题。

  1. 精准定位,一击即中OpenResty XRay 通过内存趋势图、内存分解分析和独特的内存泄漏火焰图,层层递进地揭示了问题本质。从现象到根因,整个分析过程清晰可视,避免了传统方法中的盲目猜测和反复尝试。

  2. 非侵入式诊断,生产环境安全:全程无需修改代码、无需重启服务,也不会增加系统负载。即使在高并发的生产环境,也能做到安全、稳定、实时分析。

  3. 给客户带来的业务价值

    • 内存使用率大幅降低,从持续增长到稳定可控
    • 消除了因内存耗尽导致的潜在宕机风险
    • 提升了系统整体性能和响应速度
    • 运维压力和资源浪费大幅减少,为企业节约了真金白银

这个案例告诉我们,即使是看似简单的内存泄漏问题,如果没有专业工具的辅助,也可能成为技术团队的“拦路虎”。而 OpenResty XRay 凭借其强大的可观测性和分析能力,能够快速切入问题核心,为客户提供清晰的解决思路,最终实现技术价值到业务价值的完美转化。

常见问题

为什么 LRU 缓存会引发内存泄漏?

LRU 缓存只有在淘汰缓存项时才会释放内存。如果它的容量设置得远大于实际工作集,就几乎不会淘汰任何东西,于是每一个插入的对象都会在进程的整个生命周期内被引用。本案例中,cert_cache 这个 LRU 缓存持有着从不被淘汰的已解析证书,因此随着新域名不断被解析,内存呈线性增长。

缓存 SSL 证书会导致内存泄漏吗?

会。缓存 ssl.parse_pem_certssl.parse_pem_priv_key 的解析结果,会让底层的 OpenSSL 证书和私钥结构体一直存活。当这些解析结果被放进一个容量过大、从不淘汰的 LRU 缓存时,OpenSSL 结构体就会在 Glibc Arena 中不断堆积,RSS 持续攀升。

为什么 Lua 的垃圾回收器无法回收这些缓存对象?

Lua 的垃圾回收器只回收不再可达的对象。由于这些已解析的证书对象仍被 cert_cache 这个 LRU table 引用,它们始终可达,GC 无法回收。这不是 GC 的 bug,而是缓存的无限制保留。

如何找出是哪个 Lua 对象在泄漏内存?

OpenResty XRay 的 Lua GC 对象火焰图会展示 Lua 层面的对象引用关系,让你直接看到是哪个 table 持有着内存。本案例中它直接指向 _LOADED.dynamic_cert.cert_cache,其内部字段(.free_queu.hashht.key2node.node2key)表明它是一个 lua-resty-lrucache 对象——全程无需改代码、无需重启进程。

关于 OpenResty XRay

OpenResty XRay 是一款动态追踪产品,它可以自动分析运行中的应用,以解决性能问题、行为问题和安全漏洞,并提供可行的建议。在底层实现上,OpenResty XRay 由我们的 Y 语言驱动,可以在不同环境下支持多种不同的运行时,如 Stap+、eBPF+、GDB 和 ODB。

关于作者

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

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

关注我们

如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:

我们的微信公众号

翻译

我们提供了英文版原文和中译版(本文)。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!