资讯详情

容器与 MySQL 内存管理:从 WorkingSet 到 jemalloc 实践

📅 2026/10/1 8:09:34 | 华诺云谱 👁 阅读
容器与 MySQL 内存管理:从 WorkingSet 到 jemalloc 实践
一、容器内存WorkingSet 到底是什么在 Kubernetes 中WorkingSet是 HPA 扩缩容、调度和驱逐决策的核心指标。它的准确定义是WorkingSet total_usage - total_inactive_file其中total_usage RSS Cache 内核内存total_inactive_file是可以被优先回收的冷文件缓存把Cache拆成active_file inactive_file公式可以推导为WorkingSet (RSS active_file inactive_file) - inactive_file RSS active_file所以一个常见的说法是准确的WorkingSet 等于 RSS 加上活跃的文件缓存。它的直观含义是——保留了进程真正在用的匿名内存RSS和热文件缓存active_file排除了随时可回收的冷缓存inactive_file。RSS 是什么RSSResident Set Size常驻内存集指进程当前实际占据物理内存的大小主要包括组成部分说明匿名内存堆、栈、malloc/new 分配的内存共享库已加载部分真正被载入物理内存的动态库代码段和数据段映射的文件页mmap 映射文件中已读入物理内存的部分共享内存共享内存中属于该进程的部分需要注意多个进程共享同一块内存时RSS 会在每个进程里各算一次所以所有进程 RSS 之和会大于实际物理内存占用。RSS 也不包含未实际加载的虚拟内存以及被换出到 swap 的页。二、MySQL 的 RSS 由哪些部分组成MySQL 的 RSS 不是单一组件而是多个内存分配的总和内存组件说明是否计入 RSSInnoDB Buffer Pool缓存 InnoDB 表和索引数据通常是最大头✅每连接会话缓冲区sort_buffer、join_buffer、read_buffer 等✅按需分配时Performance Schema内存监控 instrumentation 自身消耗✅TempTable 引擎MySQL 8.0 内存临时表✅有上限线程栈每个连接线程的栈空间✅表缓存及其他内部结构表打开缓存、自适应哈希索引、redo log buffer 等✅关键误区RSS ≠ Buffer PoolBuffer Pool 是预留的innodb_buffer_pool_size是配置上限物理内存按实际访问逐步增长。配置 100G 但只访问 20G 数据时RSS 不会立刻达到 100G。连接缓冲区有乘数效应max_connections × 每连接缓冲区在高并发下可能贡献可观的 RSS 增量。要精确定位是哪部分在吃内存可以查询SELECT * FROM performance_schema.memory_summary_global_by_event_name ORDER BY SUM_NUMBER_OF_BYTES_ALLOC DESC;Performance Schema 的内存是动态的Performance Schema 的内存占用随实际负载自动扩展不是启动时一次性分配的固定值。大多数尺寸参数如performance_schema_max_thread_instances默认值为-1即自动扩展模式初始不占内存缓冲区为空随负载按需分配缓冲区可无上限增长运行期不主动释放但可被回收复用呈高水位曲线仅在服务器关闭时释放全部内存若想限制其内存占用可将相关参数从-1改为正数值 N缓冲区达到 N 后不再增长但会丢失超出部分的数据。三、MySQL 申请的内存为什么不释放MySQL 申请的内存通常不会主动归还给操作系统但这不一定是内存泄漏。原因一全局缓冲池是常驻内存InnoDB Buffer Pool 的设计目标就是尽可能多地缓存数据除非进程退出否则永远不会主动释放。这是正常的性能优化。原因二连接级内存的高水位现象并发连接激增时MySQL 为每个连接分配 sort_buffer、join_buffer 等会话内存。连接关闭后MySQL 只在内部把这些内存标记为可复用并不归还给操作系统RSS 不会下降。可以把它想象成一个蓄水池水位涨上去后即使不再进水池子也保持在高位等待复用。原因三glibc 分配器的策略MySQL 默认使用的 glibcptmalloc在free()后倾向于保留内存块以备复用而非归还系统。这是导致内存只涨不跌的常见原因。什么时候才算异常内存持续单调增长且无上限业务平稳期仍缓慢攀升直至触发 OOM明显的泄漏迹象例如已知 Bug #31116036 曾导致 TempTable 在每个连接上缓存约 1MB 内存连接关闭后也不回收大量连接场景下会浪费数 GB如何真正释放唯一能让 MySQL 把内存彻底归还操作系统的方法是重启进程。四、glibc 如何主动回收内存glibc 的回收能力天然弱于 jemalloc因为它只能释放堆顶的连续空闲内存。方式一调用 malloc_trim()#include malloc.h malloc_trim(0); // 0 表示只保留最小量空闲内存从 glibc 2.8 开始它会遍历所有 arena释放其中整页为空闲的 chunk但本质仍是尽力而为——无法释放卡在其他已分配块之间的碎片。方式二调整阈值让 free() 自动回收#include malloc.h // 堆顶空闲内存超过此值时free() 自动归还系统 mallopt(M_TRIM_THRESHOLD, 64 * 1024); // 超过此值的分配走 mmapfree 时立即归还系统 mallopt(M_MMAP_THRESHOLD, 64 * 1024);M_TRIM_THRESHOLD调低 → 回收更频繁但sbrk系统调用增多有性能代价M_MMAP_THRESHOLD调低能让更多中等分配享受立即回收mmap 内存在 free 时直接 munmap为什么 glibc 不如 jemalloc 彻底glibcptmalloc基于brk/sbrk管理主堆brk只能连续扩展/收缩无法释放堆中间的洞jemalloc大量使用mmap管理独立 arena配合后台线程和 decay 策略能细粒度识别并归还长期不用的内存页五、用 GDB 给 MySQL 主动释放内存如果 MySQL 基于 glibc可以用 GDB 附加到进程并调用malloc_trimgdb --batch --pid $(pidof mysqld) --ex call malloc_trim(0)命令拆解--batch执行完自动退出--pid $(pidof mysqld)动态获取 mysqld 进程 ID--ex call malloc_trim(0)调用函数参数 0 表示释放尽可能多的空闲内存实际效果社区案例显示 RSS 通常明显下降有从 25GB 回落到 5.2GB 的案例也有释放 5GB 的报告。注意事项仅对 glibc 有效若已切换 jemalloc此命令无意义治标不治本只清理 glibc 堆中的空闲碎片无法回收 Buffer Pool 或真实泄漏生产环境建议低峰期执行GDB 附加会短暂暂停 MySQL六、切换到 jemallocjemalloc 的设计目标之一就是减少内存碎片并更积极地把不再使用的内存页交还给操作系统。jemalloc 与 glibc 的本质区别glibcfree()后不强制归还内存倾向保留以备快速复用RSS 高水位后难下降jemalloc通过后台线程和 decay 策略基于MADV_FREE等机制主动清理长时间未使用的内存页归还系统实际表现内存不再持续缓慢增长占用变得稳定执行DROP TABLE等操作时mysqld 内存占用会立刻下降有案例下降约 1GB高并发结束后 RSS 能有效回落需要注意的例外InnoDB Buffer Pool 依然不释放无论用哪种分配器这块全局缓存都不主动归还虚拟内存VSZ可能只增不减jemalloc 默认保留虚拟地址空间opt.retain可能导致 VMA 数量激增释放不是瞬时的decay 策略意味着内存释放有一定延迟七、jemalloc 配置实战核心思路通过LD_PRELOAD让 MySQL 启动时优先加载 jemalloc替换 glibc 分配器。方法一Systemd 管理的 MySQL最常见安装 jemallocyum/apt/源码编译库文件通常在/usr/lib64/或/usr/local/lib/用systemctl status mysqld查看 service 文件路径在 service 文件的[Service]段确保有EnvironmentFile指令然后编辑对应环境变量文件如/etc/sysconfig/mysqlLD_PRELOAD/usr/lib64/libjemalloc.so.2重启并验证systemctl daemon-reload systemctl restart mysqld lsof -Pn -p $(pidof mysqld) | grep jemalloc方法二mysqld_safe 启动的 MySQL在my.cnf中添加[mysqld_safe] malloc-lib/usr/lib64/libjemalloc.so.1或直接在mysqld_safe脚本开头添加export LD_PRELOAD/lib64/libjemalloc.so.1方法三Docker 环境dockerfileFROM mysql:8.4 USER root RUN microdnf install -y jemalloc ENV LD_PRELOAD/usr/lib64/libjemalloc.so.2 USER mysql验证配置# 方法 1查看进程加载的动态库 lsof -Pn -p $(pidof mysqld) | grep jemalloc # 方法 2检查 mysqld 可执行文件依赖 ldd $(which mysqld) | grep jemalloc输出出现libjemalloc.so路径即配置成功。注意不同系统下 jemalloc 版本和路径可能不同.so.1或.so.2用find /usr -name libjemalloc.so*确认实际路径。八、VMA 耗尽 vs 物理 OOM两个完全不同的故障这是使用 jemalloc 时最容易混淆的地方。问题触发条件表现OOM物理内存 swap 耗尽内核 OOM Killer 介入kill -9掉 mysqldVMA 耗尽vm.max_map_count达到上限mmap/munmap返回ENOMEMMySQL 报错退出关键点VMA 耗尽时物理内存可能很充裕VMA 数量达到vm.max_map_count上限时物理内存可能完全够用甚至大量空闲但内核无法再为新的映射分配 VMA 结构mmap直接失败。报错信息通常长这样[ERROR] InnoDB: Error in munmap(): Cannot allocate memory或[ERROR] mysqld: Out of memory (Needed xxx bytes)注意虽然也写了 “Out of memory”但它指的是虚拟地址空间/VMA 资源耗尽不是物理内存不足。检查free -h会发现内存还很充裕。如何区分看 dmesg 有无 OOM Killer 记录dmesg | grep -i killed process有 → 物理 OOM没有 → 继续排查看错误日志内容Error in munmap(): Cannot allocate memory→ 大概率 VMA 耗尽看当前 VMA 数量wc -l /proc/$(pidof mysqld)/maps接近vm.max_map_count默认 65530即为 VMA 耗尽九、解决 jemalloc 的 VMA 问题核心解法调整 vm.max_map_countLinux 默认vm.max_map_count通常是 65530。jemalloc 频繁 mmap/munmap 导致 VMA 碎片化时数量会迅速逼近甚至超过此值。# 临时调整 sysctl -w vm.max_map_count262144 # 永久调整 echo vm.max_map_count262144 /etc/sysctl.conf sysctl -p262144 这个数值的依据与局限这个数值并非来自 Linux 内核或 MySQL 官方的硬性规定而是一个在实践中被广泛采纳的事实标准。真实来源262144 最早且最广泛地出现在Elasticsearch 的官方文档中。ES 默认使用mmapfs存储索引会消耗大量 VMA。因此 ES 官方明确要求用户将vm.max_map_count至少设为 262144否则节点无法启动或崩溃-6。这里需要补充一个重要更新Elasticsearch 官方文档在后续版本中已将推荐值从 262144 提升到了 1048576-1-5。在 Kubernetes 环境下Elastic 的文档进一步明确8.16 及以后版本设为 10485768.15 及以前版本仍为 262144-9。为什么合理Linux 内核默认的 65530 非常古老设计初衷是兼容 2000 年以前的 ELF 核心转储格式16 位段编号并非为现代应用的高并发内存管理设计jemalloc 确实会制造大量 VMA 碎片。jemalloc 官方邮件列表中有开发者报告通过把max_map_count从默认的 65530 提高到 131060 后munmap失败问题消失并确认 jemalloc 4.0 切换到 per-arena 独立 chunk 管理后虚拟内存使用量增加更容易触及此限制-4-8把默认值提高约 4 倍通常足以提供足够缓冲MySQL 上的实际验证天翼云的一篇排查记录详细描述了 MySQL 开启 jemalloc 后频繁 OOM 的案例。通过pmap -x对比发现开启 jemalloc 的 mysqld 有约 24773 个 anon 映射未使用 jemalloc 的只有 2602 个物理内存释放但映射区持续保留并增长最终 VMA 耗尽。将vm.max_map_count从 65530 调大到 262144 后实例不再触发 OOM 重启-7。局限性没有官方保证内核或 jemalloc 都没承诺262144 一定够用它只是经验值取决于负载连接数极高、查询极复杂大量小内存分配时VMA 增长可能超过此值。jemalloc 社区有人指出极端分配模式下无论设多高最终仍可能触顶-8新内核趋势2024 年 8 月Linux 内核邮件列表中有开发者正式提议将DEFAULT_MAX_MAP_COUNT从 65530 直接改为INT_MAX实际等于取消限制。邮件中列举了多个项目的实际使用值Elasticsearch/OpenSearch 要求至少 262144BIND 9.18 使用 jemalloc 5.2.1 观察到约 700000Fedora/Ubuntu/NixOS/Arch 等发行版已采用 1048576-17结论262144 是实践上合理、被广泛验证有效的经验值在 MySQL jemalloc 场景中有真实案例支撑-7。但应理解为一个足够大的安全余量而非精确计算出的必需值。考虑到 Elasticsearch 已提升到 1048576且内核社区在讨论取消限制如果你的环境允许直接设置 1048576 是更面向未来的选择。对于特别重的场景持续监控 VMA 数量比迷信某个具体数字更可靠wc -l /proc/$(pidof mysqld)/maps备选方案关闭 jemalloc 的虚拟内存保留MALLOC_CONFretain:false减少 VMA 数量但会增加 mmap/munmap 频率有轻微性能损耗。升级 jemalloc 到 5.0该版本对 VMA 管理和碎片化做了显著改进。额外提醒禁用透明大页THPjemalloc 与 THP 结合时可能加剧内存无法释放的问题。强烈建议禁用。先确认状态cat /sys/kernel/mm/transparent_hugepage/enabled看到[always]说明全局启用。临时禁用echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag永久禁用方法一GRUB 内核参数最彻底编辑/etc/default/grub在GRUB_CMDLINE_LINUX引号内添加transparent_hugepagenever更新 GRUBRHEL/CentOS/Rockysudo grub2-mkconfig -o /boot/grub2/grub.cfgUbuntu/Debiansudo update-grub重启服务器永久禁用方法二systemd 服务创建/etc/systemd/system/disable-thp.service[Unit] DescriptionDisable Transparent Huge Pages (THP) DefaultDependenciesno Aftersysinit.target local-fs.target Beforemysqld.service [Service] Typeoneshot ExecStart/bin/sh -c echo never | tee /sys/kernel/mm/transparent_hugepage/enabled /dev/null echo never | tee /sys/kernel/mm/transparent_hugepage/defrag /dev/null RemainAfterExityes [Install] WantedBybasic.target启用sudo systemctl daemon-reload sudo systemctl enable --now disable-thp.service验证cat /sys/kernel/mm/transparent_hugepage/enabled # 期望输出always madvise [never]还可检查 MySQL 进程的AnonHugePages字段长期为0说明 THP 已有效禁用。十、总结主题核心结论WorkingSetRSS active_file计算方式为total_usage - total_inactive_fileMySQL RSSBuffer Pool最大头 连接缓冲区 PFS TempTable 其他内存不释放glibc 策略 Buffer Pool 常驻 连接内存高水位多数情况正常glibc 回收malloc_trim(0) 调低M_TRIM_THRESHOLD/M_MMAP_THRESHOLDGDB 释放gdb --batch --pid $(pidof mysqld) --ex call malloc_trim(0)仅对 glibc 有效jemalloc主动归还物理内存但 Buffer Pool 仍不释放VMA vs OOMVMA 耗尽是虚拟地址空间资源不够物理内存可能充裕VMA 解法调大vm.max_map_count或MALLOC_CONFretain:false262144 的真相源自 Elasticsearch 官方要求被广泛沿用ES 已提升至 1048576内核社区在讨论取消限制THP使用 jemalloc 时务必禁用透明大页理解这些概念的关键在于区分物理内存与虚拟地址空间、可回收缓存与常驻内存、分配器策略与真实泄漏、经验值与硬性规定。多数内存不释放现象都是正常的设计权衡而非 Bug。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑