ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

标准大页与透明大页到底怎么选?Linux内存性能优化实战解析

标准大页与透明大页到底怎么选?Linux内存性能优化实战解析 搞Linux性能排查的人十个里有八个都绕不过“大页”这个坎。服务一卡、数据库一抖、CPU莫名其妙飙到100%查到最后往往都指向同一个关键词Huge Pages。但真正上手配置的时候很多人又懵了——标准大页Huge Pages和透明大页Transparent Huge PagesTHP到底差在哪我该用哪个为什么网上有人说要开大页又有人说必须关掉THP这篇就把这两个东西掰开揉碎讲清楚。这不仅是给运维和DBA看的后端开发、容器平台管理员、数据库调优工程师都建议认真读一遍。理解了标准大页和透明大页的原理区别你就能解释不少棘手的线上问题为什么Java服务偶尔出现毫秒级卡顿为什么Oracle跑在Linux上建议关THP为什么预留了大页却没生效1. 为什么需要大页从TLB命中率说起1.1 页表膨胀与TLB容量危机要聊大页先得弄清楚操作系统管理内存的基本方式。Linux把物理内存切成固定大小的块默认4KB一页。虚拟地址要映射到物理地址内核靠页表Page Table记录这个映射关系。听起来很简单但问题是页表是有成本的。一个进程如果占用了2GB内存按照4KB一页来算就是50万个页表项。每个页表项大约几十字节光页表数据就要占掉不少内存。更麻烦的是CPU为了加速地址翻译内置了TLBTranslation Lookaside Buffer缓存最近的页表项。TLB的容量非常有限通常只有几十到几百个条目。一旦程序访问的内存范围超过TLB能缓存的范围就会不断发生TLB MissCPU必须去内存里翻页表一次地址翻译要多出几次内存访问。拿生活里的事打比方TLB就是你的“通讯录快捷拨号”只能存几个最常打的号码。如果每天要联系几百人每次都得翻电话本效率自然上不来。4KB小页就是这种情况程序内存越大TLB就越不够用。大页方案直接把页表粒度放大到2MB甚至1GB。2MB大页是4KB小页的512倍同样的2GB内存页表项从50万个降到1000个。TLB能覆盖的内存范围瞬间扩大了几百倍命中率大幅提升。1.2 大页为什么能提升性能TLB命中率提升意味着什么意味着CPU少了很多次“停顿”。数据库这类应用内存访问随机性很强动不动就是大量数据散落在各个内存页里。如果TLB频繁Miss性能损耗会非常明显。我见过一个很直观的测试同一套MySQL实例开启大页后TPS涨了10%到20%QPS几乎翻倍。这不算夸张因为数据库场景里CPU经常在内存地址翻译上干等大页把这块瓶颈直接疏通了。还有一层隐藏收益大页在分配时物理内存通常是连续的能减少内存访问的跨bank开销。虽然现代多通道内存架构下这个收益没那么明显但在NUMA架构下大页对内存本地性的改善还是实打实的。1.3 缺页异常和Swap负担小页还有一个问题换页机制。Linux默认会给进程分配虚拟内存真正用到物理内存时才触发缺页异常Page Fault加载。要是内存紧张系统可能把部分页换到swap里再次访问时又得换回来。标准大页和透明大页尤其是标准大页通常是不可换出的。物理内存一旦预留给大页就被牢牢锁住不会被swap。这一点对数据库的稳定性至关重要。数据库最怕的就是某内存页被换出下次访问时卡个几十毫秒甚至几百毫秒这种延迟尖刺在交易系统里就是事故。理解了这些底层逻辑再看标准大页和透明大页的区别思路就清晰了——它们解决的是同一类问题但实现路径和管理哲学完全不同。2. 标准大页手动管理的老牌方案2.1 什么是标准大页标准大页就是传统意义上的Huge Pages是最早的Linux大页实现方案。运维人员通过内核参数指定预留多少内存作为大页池系统启动或运行时会优先从池里分配。应用进程要使用这些大页要么通过共享内存接口System V共享内存、mmap要么通过hugetlbfs文件系统显式映射。核心特征是“手动”页面大小、预留数量、使用方式全部由管理员和应用协调完成。默认的大页尺寸通常是2MB也支持1GB的更大页面这取决于硬件架构和内核配置。标准大页本质上是“池化”的思路。系统启动时内核按指定数量分配大页放进一个固定池子。应用需要大页时就从这个池子里取用完归还。池子里的内存被标记为不可换出专页专用不会参与常规的LRU回收。2.2 怎么配置标准大页配置标准大页通常分三步改内核参数、预留内存、调整应用。先看内核参数。最核心的是vm.nr_hugepages表示预留多少个大页。比如要预留4GB内存、按2MB一个页算就是2048个页。# 查看当前大页状态 grep HugePages /proc/meminfo # 预留2048个2MB大页约4GB sysctl -w vm.nr_hugepages2048 # 写入配置持久化 echo vm.nr_hugepages2048 /etc/sysctl.conf预留之后要确认状态。HugePages_Free能用的页数HugePages_Rsvd是应用已申请但尚未分配的保留数。实际预留是否成功看这行就行[rootdb-server ~]# grep -E HugePages_Total|HugePages_Free /proc/meminfo HugePages_Total: 2048 HugePages_Free: 2048然后要调应用。以Oracle数据库为例它默认就能用大页。前提是启动用户得有足够的memlock限制。修改/etc/security/limits.conf给Oracle用户放开锁定内存的上限oracle soft memlock unlimited oracle hard memlock unlimited接着设数据库参数SQL ALTER SYSTEM SET use_large_pagesonly SCOPESPFILE;use_large_pagesonly表示数据库只使用大页预留给不了就直接启动失败。这虽然严格但能确保数据库不会在没大页时偷偷用普通内存页掩盖配置问题。Java的话可以用JVM参数-XX:UseLargePages开启大页支持但还要配合-XX:LargePageSizeInBytes指定页大小。实际情况中Java的大页配置比较麻烦后面实操章节细讲。2.3 标准大页的优点和隐患标准大页最大的优点是“稳定可控”。内存预留多少是固定的启动后就锁定了。数据库分配内存的延迟极低因为大页池已经提前准备好不存在内存分配失败或触发内存回收的问题。而且池里的页面不可换出性能波动小。另外标准大页在NUMA架构下支持更精细的控制。可以为不同NUMA节点预留不同数量的大页让数据库SGA绑定在本地内存上减少跨节点访问。隐患也相当明显预留的内存不可动态调整。预留多了普通内存就少了可能出现“内存明明还有几十GB系统却在OOM”的情况预留少了应用想用大页却没货只能回退到普通页或启动失败。大页池内存换不出去释放不及时的话浪费是实打实的。配置复杂依赖应用主动支持。不少应用压根不认识大页你预留了它也吃不上。在内存碎片严重的系统里预留大页可能失败需要重启或触发内存规整才能完成预留。3. 透明大页让内核替你“偷懒”的方案3.1 透明大页的工作原理透明大页是内核2.6.38开始引入的机制目标是让普通进程也能享受大页的好处又不用改代码。所谓“透明”就是应用无感知内核自动尝试把连续的小页合并成大页或者直接在分配时给大页。THP的核心组件是个后台内核线程叫khugepaged内核大页合并守护进程。它会周期性地扫描进程的页表发现虚拟内存区域里有连续的小页就把它们合并成2MB的大页。这个合并动作对应用是隐藏的应用照常读写只是底层页表的粒度变了。另外THP在缺页分配时也会直接给大页。如果进程申请分配一块大内存内核会优先尝试分配2MB的大页并一次性建立起映射。这和标准大页“先预留池子再申请”的思路完全不同——THP的大页是即时分配的用完了就释放。THP有三种运行模式写在/sys/kernel/mm/transparent_hugepage/enabled里always只要有机会就尽量使用大页不管是后台合并还是分配时优先给大页。madvise只有应用通过madvise系统调用明确指定某个内存区域可以用大页时才在该区域使用THP。never完全关闭THP不合并也不优先分配大页。内核还分了两条路径来实现THP缺页分配路径fault handler和后台合并路径khugepaged。缺少分配路径速度快但只对匿名内存进程堆、栈等生效。khugepaged则是把已有的小页合并成大页是“亡羊补牢”的路径CPU开销集中在扫描和页表操作上。3.2 为什么说THP是双刃剑THP的宣传口号是“零配置享受大页性能”听起来很美好。但实际用起来踩坑的人不在少数尤其是数据库和延迟敏感的服务。问题出在三个地方第一分配不稳定。THP的大页是即时分配的系统内存碎片化严重时分配2MB连续物理内存可能失败。内存碎片严重时会降低正常的分配效率。第二khugepaged扫描和合并过程要锁页表、修改页表项会带来短暂的停顿。在某些高并发场景下这个停顿虽然只有几十微秒到几毫秒但对交易系统来说依然是不可接受的延迟尖刺。我见过一个真实案例一套跑在云服务器上的PostgreSQL平时延迟300微秒左右开启THP后时不时跳到一个2毫秒的尖峰排查了很久才确认是khugepaged合并导致的。第三THP分配大页失败后会触发内存压缩compaction。memory compaction是内核为了凑出连续内存而做的一种“搬运移动”操作代价相当高经常引起CPU毛刺。这就形成了一个恶性循环内存越碎压缩越频繁CPU开销越大应用越卡。所以业界的共识逐渐变成数据库、JVM、实时性要求高的服务建议直接关掉THP改用标准大页或者干脆不用大页。普通Web服务、离线任务THP带来的收益和风险基本持平开了问题不大关了也没啥损失。4. 标准大页与透明大页核心对比4.1 一张表说清差异不少云厂商默认开启THP导致很多开发者的MySQL和Redis实例在THP下跑了好几年也没发现。等流量一上来性能问题集中爆发排查起来非常痛苦。所以我建议用数据库或者对延迟敏感的应用不管是什么环境第一步先确认THP状态。标准大页和透明大页的差异我用一张表列出来对照看最直观。对比维度标准大页Huge Pages透明大页THP引入内核版本2.6以前就有快速演进2.6.38正式引入管理员介入程度手动预留、手动配置内核自动管理应用改造要求需要应用显式支持或使用共享内存接口应用完全无感页表分配方式启动/运行期预分配池子按需即时分配内存是否可换出不可换出物理锁定与匿名页一样可参与回收页表合并/分配路径无缺页分配 khugepaged合并内存碎片敏感度预留时需要连续内存但可运维控制分配时对碎片敏感会触发压缩性能稳定性稳定可预期有尖刺风险配置复杂度高低典型场景Oracle/MySQL/PostgreSQL、虚拟化通用负载、JVM堆、Web服务4.2 选型思路不是二选一而是按场景取舍我在实际项目里的选型逻辑通常是这样数据库实例Oracle、MySQL、PostgreSQL、Redis关闭THP按需配置标准大页。数据库对延迟极度敏感不能容忍khugepaged带来的随机尖刺。Java应用如果JVM堆很大建议关闭THP并配置UseLargePages。JVM Interned String和对象分配非常频繁THP的即时分配和合并行为容易造成堆内碎片和停顿。但Java的UseLargePages和Linux标准大页配合起来有一些坑配置不当可能直接起不了JVM后面实操部分会说。容器和Kubernetes节点如果容器内跑的是常规Web服务THP开或关影响不大但为了稳定性建议关闭THP。容器本身的内存隔离机制已经够复杂不要让THP再添乱。裸金属高并发服务可以尝试开启THP但要通过madvise模式精确控制只对指定的内存区域启用大页避免全盘自动合并带来的不确定性。一句话总结我的经验越是对性能波动敏感的服务越倾向于标准大页或者直接关大页越是通用型负载THP的便利性才有优势。永远不要默认相信THP的“自动”是对的。5. 实操配置与排查全过程5.1 快速查看当前大页状态动手配置之前先摸清系统现状。有两个关键入口一个是/proc/meminfo看标准大页池的状态[roothost ~]# grep -i hugepages /proc/meminfo AnonHugePages: 2048 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0AnonHugePages是透明大页已分配的匿名大页内存量。HugePages_Total是标准大页池的总页数为0说明没有预留标准大页。另一个是/sys/kernel/mm/transparent_hugepage/enabled查看THP当前模式[roothost ~]# cat /sys/kernel/mm/transparent_hugepage/enabled always madvise [never]方括号里的就是当前模式。这里是[never]说明THP已关闭。如果是[always]说明全自动模式开启数据库等高敏感应用就有风险。再确认khugepaged进程状态[roothost ~]# ps -ef | grep khugepaged [roothost ~]# systemctl status khugepagedkhugepaged线程只在THP非never模式下运行。如果THP关闭了这个线程不应该存在或者没有任何活动。5.2 预留标准大页并让应用吃上以最常见的Oracle数据库场景为例一步步操作。第一步算预算。假设数据库SGA目标是16GB按2MB页计算16GB / 2MB 8192个大页预留时建议多留一点点余量防止其他组件抢大页。写到sysctl配置里echo vm.nr_hugepages8192 /etc/sysctl.conf sysctl -p第二步验证预留是否成功[rootdb-server ~]# grep HugePages /proc/meminfo HugePages_Total: 8192 HugePages_Free: 8192 HugePages_Rsvd: 0注意如果系统内存碎片严重sysctl -p之后HugePages_Total可能低于预期值。这种情况多半是物理内存碎片导致无法分配连续的2MB页。可以试试触发内存规整echo 1 /proc/sys/vm/compact_memory再重新设置nr_hugepages。还是不行的话只能考虑重启或调整预留时机到开机阶段通过内核启动参数hugepagesN。第三步修改Oracle用户的内存限制。编辑/etc/security/limits.conforacle soft memlock unlimited oracle hard memlock unlimited注意如果使用systemd管理数据库实例还需要在systemd service文件里加LimitMEMLOCKinfinity否则limits.conf不生效。第四步配置数据库参数。登录SQL*PlusSQL ALTER SYSTEM SET use_large_pagesonly SCOPESPFILE; SQL SHUTDOWN IMMEDIATE; SQL STARTUP;启动后检查数据库实际使用的大页情况[rootdb-server ~]# grep HugePages /proc/meminfo HugePages_Total: 8192 HugePages_Free: 17 HugePages_Rsvd: 5HugePages_Free从8192掉到17说明8192-17-58170个页被数据库吃掉了配置成功。5.3 关闭透明大页的三种方式关闭THP是数据库和JVM类的标配动作。方式有三种不同环境下选择不同的方式。第一种是GRUB启动参数方式最彻底。修改/etc/default/grub在GRUB_CMDLINE_LINUX行追加GRUB_CMDLINE_LINUX... transparent_hugepagenever重新生成grub配置并重启grub2-mkconfig -o /boot/grub2/grub.cfg reboot这种方法在系统启动时就禁用了THP应用完全无感。缺点是必须重启适合维护窗口内操作。第二种是sysfs运行时切换立即生效但重启后失效echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag适合临时验证不适合持久化。第三种是用systemd服务在开机阶段禁用兼顾了重启生效和不用改GRUB的优点。创建/etc/systemd/system/disable-thp.service[Unit] DescriptionDisable Transparent Huge Pages Aftermulti-user.target [Service] Typeoneshot ExecStart/bin/sh -c echo never /sys/kernel/mm/transparent_hugepage/enabled; echo never /sys/kernel/mm/transparent_hugepage/defrag [Install] WantedBymulti-user.target启用服务systemctl daemon-reload systemctl enable disable-thp.service systemctl start disable-thp.service再确认cat /sys/kernel/mm/transparent_hugepage/enabled输出应该是always madvise [never]。顺带提一句除了enabled还有人会去调/sys/kernel/mm/transparent_hugepage/defrag和khugepaged的扫描参数。defrag是控制THP分配失败时是否执行内存规整的开关。即使THP模式是never如果defrag没有关某些场景内核仍然可能做压缩同样引起CPU毛刺。所以强烈建议同时把defrag也改成never。5.4 JVM的UseLargePages为什么容易踩坑JVM开启大页支持的命令是java -XX:UseLargePages -XX:LargePageSizeInBytes2m -Xmx8g -jar app.jar我自己踩过的坑有这几个如果不预留标准大页JVM里开了UseLargePages但系统里没有大页可用JVM会直接启动失败报错信息类似“Could not reserve enough space for object heap”。Java 8/11在容器里开UseLargePages需要容器有CAP_IPC_LOCK权限否则大页锁定内存失败。和Oracle不同JVM不会自动使用共享内存大页必须通过mmap或者System V共享内存方式映射大页这就依赖Linux的hugepages配置正确。实际生产里Java应用如果堆内存在4GB以内直接用普通页就行没必要开大页。堆特别大8GB以上才值得折腾。真要用建议先预留好大页池再只对JVM开启UseLargePages其他服务不要受干扰。6. 常见问题与排查实录6.1 预留了HugePages但应用没用上症状/proc/meminfo里HugePages_Free一直等于HugePages_Total说明预留的大页一个都没被用。排查顺序# 检查应用进程是否真的用了大页 cat /proc/PID/smaps | grep -i huge # 检查memlock限制 ulimit -l # 检查系统共享内存上限 sysctl kernel.shmmax最常见的原因有三个Oracle用户的memlock没放开进程无法锁定大页内存自动回退到普通页。数据库参数use_large_pages还是默认的false或auto没有改成only或者没有重启用SPFILE。对非Oracle的应用比如自己写的C程序如果使用malloc申请内存是吃不到标准大页的。必须通过mmap加MAP_HUGETLB标志或者使用hugetlbfs挂载目录。6.2 THP引起的数据库延迟尖峰这是一个真实的案例。某在线支付系统的PostgreSQL日常平均查询延迟300多微秒但每天总有几个时段出现1到5毫秒的随机尖峰。一开始怀疑是磁盘IO但排查发现CPU和IO都很平稳。后来用perf抓内核热点发现khugepaged和compaction占了相当比例的时间。处理方式是先关闭THP并设置never同时关掉defrag。关闭后观察了一周延迟尖峰完全消失。整个过程配置不超过十分钟但影响极其明显。6.3 khugepaged占用CPU过高khugepaged是单线程的CPU占用通常会很低。如果某个时刻khugepaged的CPU占用飙高到单核100%通常说明当前系统内存碎片非常严重内核在频繁尝试合并大页。处理办法echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag然后把标准大页预留好让应用直接使用标准大页绕开THP的合并机制。6.4 大页预留过多导致OOM标准大页池里的内存不参与内核回收。预留16GB大页系统可用内存就少了16GB。如果应用确实没用这16GB而普通内存又紧张系统可能提前OOM。解决思路尽量精确预留Oracle场景下SGA大小加一个余量就行不要拍脑袋多留。调整vm.nr_overcommit_hugepages允许系统通过sysctl动态扩容大页池。这个参数可以让池子在小页不够时自动多分配但只能在运行期临时增加不能减少。预留前评估应用实际需求不要预留后放着不管。HugePages_Free长期接近HugePages_Total就说明预留浪费了。6.5 大页常用排查命令速查表命令用途grep HugePages /proc/meminfo查看标准大页池状态cat /sys/kernel/mm/transparent_hugepage/enabled查看THP模式cat /sys/kernel/mm/transparent_hugepage/defrag查看内存规整策略cat /proc/PID/smaps查看进程是否使用大页cat /proc/PID/status查看进程内存统计含HugePages字段hugeadm --list查看系统支持的大页大小numastat -m查看NUMA节点内存分配情况compact_memory触发内存规整改善大页分配成功率7. 配置不当引发的边缘问题7.1 systemd服务里的LimitMEMLOCK不生效很多人在limits.conf配了memlock unlimited但systemd启动的Oracle或者应用依然报锁内存失败。原因是systemd并不会直接加载PAM模块。需要在service文件里显式加LimitMEMLOCKinfinity。7.2 容器里UseLargePages权限不足容器内配置大页使用报Operation not permitted多半是缺少CAP_IPC_LOCK。在docker和kubernetes里要给容器加这个权限或者以privileged模式运行。还要保证节点上已经预留了足够的大页。7.3 透明大页无法完全关闭有些云内核的THP是编译进内核的修改sysfs可能确实生效了但某些应用比如Redis启动时会主动又把THP打开。Redis 3.2之前会建议通过启动脚本自动写never新版虽然不再自动开启但部署环境复杂最好发布脚本里带上关闭THP的步骤。7.4 大页预留不均衡导致NUMA节点性能差异同一台两路服务器只在一个NUMA节点上预留了大量大页应用如果绑定到了另一个节点访问大页内存就要走跨节点通道性能反而不如不用大页。配置时通过/sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages按节点预留保持均衡。echo 4096 /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages echo 4096 /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages这里没有用绝对路径展示是因为不同内核版本的sysfs入口略有差异。8. 再分享一个重要细节madvise模式什么时候用前面说过THP有三种模式always、madvise、never。我前面建议高敏感应用直接关闭但有一种情况我会坚持开madvise。就是那些既想获得大页收益又不想让khugepaged全盘自动操作的服务。这种模式下只有进程自己通过madvise调用对某个地址范围明确声明MADV_HUGEPAGE内核才会尝试给那段区域分配大页。其他内存区域保持普通页。举个例子某个视频转码服务读操作集中在几个大的缓冲区内几百MB到几GB。其他线程分配的内存很零散。用madvise模式后把读缓冲区标记为MADV_HUGEPAGE其他局部变量分配照旧用小页char *buffer malloc(1GB); // 只对这个区域启用THP madvise(buffer, 1GB, MADV_HUGEPAGE);madvise适合编程语言能控制内存分配的应用。Java里可以调Native Memory Tracking的某些场景或者Netty的PooledByteBufAllocator用io.netty.noPreDirectNoUnsafe等参数配合。但说实话C/C场景用起来最顺畅其他语言支持比较有限。9. 从性能指标反推配置是否合理配置完大页以后怎么评估效果是不是达到预期我习惯看三个指标。第一个是CPU的user time和sys time的占比。如果sys time显著下降了说明内核态在页表操作上的开销减少了大页收益初步体现。第二个是TLB Miss的相关性能计数器perf可以直接采perf stat -e dTLB-load-misses,dTLB-store-misses -p PID开启大页后dTLB-load-misses的数值应明显下降。如果两个数值降幅不大说明应用的性能瓶颈可能不在TLB上大页优化空间有限。第三个是应用的响应时间分布。数据库场景里重点看P99和平均延迟的差距如果P99和平均值的差距缩小了说明延迟尖峰在减少。这一点在排查上面那个PostgreSQL案例时非常有效。10. 最后的实操体会把标准大页和透明大页放在一起对比本质上就是“确定性”和“自动化”之间的取舍。标准大页像一个老派的库管提前把货备好、分门别类放好取用速度快但要求有流程、有规划。透明大页像一个智能货架商品自己整理、自己上架省事但偶尔翻车翻一次就可能让核心数据库卡顿几秒。我在实际项目中的底线很明确生产环境的数据库和JVM服务一律关THP按需配标准大页普通Web应用和离线任务THP开或关都行但为了减少排障维度也会选择关闭。这个决策不是为了性能最大化而是为了降低不确定性。在真实的生产环境里确定性比“理论上更快”重要得多。对一个刚开始接触大页的人我建议按这个顺序操作先跑一条命令 cat /sys/kernel/mm/transparent_hugepage/enabled 看看当前THP状态再决定是否需要进一步调整。如果你管着数据库这一条命令可能就是今晚避免一次卡顿的开始。
返回列表