金融系统如何在香港服务器的 RHEL 9 环境中配置大页内存,解决 Oracle 数据库内存碎片问题

那天我在香港荃湾机房的夜班,交易日快收盘时,监控屏上突然冒出一串红:某核心库的 P95 响应抖到 180ms,kswapd0 的 CPU 占用从 3% 飙到了 40%。我站在 25℃ 的冷通道里,耳边全是风墙的低鸣和 NVMe 背板的嗡嗡声。
dmesg 刷出熟悉又讨厌的字样:page allocation failure: order:9, mode:0x...。这不是内存不够,这是内存碎片。我看了一眼那台机器的配置信息——RHEL 9.3 + Oracle 19c,SGA 做到 192GB,THP(透明大页)居然还是默认的 madvise。我知道我们得把“真正的大页”(HugePages)拉上场了。
1. 现场环境与问题画像
硬件与系统(真实机房档案条目)
| 项目 | 参数 |
|---|---|
| 机房 | 香港荃湾 T3 级别 IDC,双路供电,N+1 冷却 |
| 服务器 | 2 × AMD EPYC 7543(32C/64T,3.0GHz),NUMA=8 节点 |
| 内存 | 512GB DDR4 ECC(8 × 64GB,均匀插槽分布) |
| 存储 | 8 × 3.84TB NVMe U.2(RAID10,Oracle ASM 上层) |
| 网卡 | 2 × 25GbE,LACP |
| OS | Red Hat Enterprise Linux 9.3(kernel 5.14) |
| DB | Oracle Database 19c (19.19 RU),单实例 |
| 关键参数 | SGA_TARGET=192G / SGA_MAX_SIZE=192G / PGA_AGGREGATE_TARGET=64G |
症状
- 交易高峰期 kswapd0 抢占 CPU、系统抖;vmstat 看到 si/so 偶发尖刺(swap 并未到阈值)。
- dmesg 有 order:9 等大块页分配失败日志(说明连续物理页不足)。
- perf top 看到内核在伙伴系统(buddy allocator)与 THP 回退路径上耗时。
- Oracle AWR 显示 latch: shared pool 与部分 log buffer space 等等待在抖动窗口放大。
初步判断
SGA 大,长期运行后,系统物理内存产生碎片;THP 的 madvise 并不能保证为 Oracle 的共享内存提供稳定的大页。
方案方向:关闭 THP,启用静态 HugePages(2MB 或按需 1GB),让 SGA 长期稳定落在大页上,减少 TLB miss,避免伙伴系统在热点时“找整块”。
2. 方案选型与原则
为什么用 HugePages(hugetlbfs)而不是 THP?
- 确定性:HugePages 是预留的大页,不与其他匿名内存竞争,不会在高峰期被系统回退。
- 避免碎片:内核在启动后即保留连续的大页,不依赖运行时整理。
- TLB 命中率:更大的页(2MB/1GB)显著降低 TLB miss,对大 SGA 有利。
- 内存开销:页表项更少,降低页表占用与开销。
与 Oracle 配置的关系
- HugePages 与 AMM(memory_target/memory_max_target)不兼容;应使用 ASMM(sga_target/sga_max_size + pga_aggregate_target)。
- 参数 use_large_pages='ONLY' 可强制 SGA 必须用大页(分配失败就不启动),可避免“以为用了,实际没用”的尴尬。
2MB vs 1GB
- 2MB:配置简单,支持在线调整 vm.nr_hugepages,通用稳定。
- 1GB:需要内核启动参数预留,适合 ≥256GB 的超大 SGA,TLB 命中更好,但变更窗口与回滚成本更高。
本案例 SGA=192GB,2MB HugePages 已足够,先上 2MB;后文附 1GB 方案。
3. 变更前的核对清单(现场卡片)
当前 THP 状态与内核日志
cat /sys/kernel/mm/transparent_hugepage/enabled(应为 always/madvise/[never] 之一)
dmesg | grep -i huge
Oracle 运行模式
show parameter memory_target(应为 0)
show parameter sga_target/sga_max_size/pga_aggregate_target
NUMA 拓扑
- numactl --hardware,SGA 很大时要考虑跨 NUMA 的分布策略(后文详述)
回滚预案
- HugePages 改动前记录 sysctl 与 GRUB 参数;
- Oracle 配置改动前备份 spfile;
- 维护窗口内可按步骤回退(见文末“回滚与应急”)。
4. 计算需要的 HugePages 数量(2MB 页)
公式
- HugePage 大小(RHEL9 默认)= 2MB
- 需要的大页数量 nr_hugepages = ceil(SGA_bytes / 2MB)
示例(本案例 SGA=192GB)
- 192GB = 192 × 1024 MB = 196,608 MB
- 196,608 MB / 2 MB = 98,304 页
常见 SGA 对照表(2MB 页)
| SGA | 计算 | 需要页数(nr_hugepages) |
|---|---|---|
| 64GB | 64 × 1024 / 2 | 32,768 |
| 128GB | 128 × 1024 / 2 | 65,536 |
| 192GB | 192 × 1024 / 2 | 98,304 |
| 256GB | 256 × 1024 / 2 | 131,072 |
| 320GB | 320 × 1024 / 2 | 163,840 |
memlock(KB)建议(/etc/security/limits.d)
memlock 单位是 KB,至少等于 SGA_SIZE。
例如:128GB = 134,217,728 KB;192GB = 201,326,592 KB。
5. 实施步骤(RHEL 9 标准做法)
5.1 关闭透明大页(THP)
目标:把 /sys/kernel/mm/transparent_hugepage/enabled 变成 [never],避免 THP 干扰。
方法 A:GRUB 永久关闭(推荐)
# 1) 备份
sudo cp /etc/default/grub /etc/default/grub.bak.$(date +%F)
# 2) 添加内核启动参数
# 若已有其它参数,只需在 GRUB_CMDLINE_LINUX 中追加 transparent_hugepage=never
sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="transparent_hugepage=never /' /etc/default/grub
# 3) 重新生成 grub 配置(适配真实引导设备)
sudo grub2-mkconfig -o /boot/grub2/grub.cfg # BIOS
# 或
sudo grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg # UEFI
# 4) 重启后验证
cat /sys/kernel/mm/transparent_hugepage/enabled # 期望: [never]
方法 B:tuned 配置(可选,一条龙优化)
# 安装 tuned 及 oracle profile(若仓库提供)
sudo dnf install -y tuned tuned-profiles-oracle || true
sudo systemctl enable --now tuned
# 切换到 oracle 或 throughput-performance
sudo tuned-adm profile oracle || sudo tuned-adm profile throughput-performance
# 验证 THP
cat /sys/kernel/mm/transparent_hugepage/enabled # 通常会是 [never] 或禁用状态
若所在镜像/仓库没有 oracle profile,可沿用方法 A 或创建 systemd 关 THP 的小服务(略)。
5.2 配置 2MB HugePages(静态)
一次性临时验证(不重启):
# 按计算结果设置(本例 98,304)
echo 98304 | sudo tee /proc/sys/vm/nr_hugepages
# 查看效果
grep -i Huge /proc/meminfo
# 关注:HugePages_Total、HugePages_Free、Hugepagesize(应为 2048 kB)
持久化(重启也生效):
# 创建 sysctl 片段
cat <<'EOF' | sudo tee /etc/sysctl.d/99-hugepages-oracle.conf
vm.nr_hugepages = 98304
# 可选:对系统 V 共享内存使用大页的组许可(把 dba 组的 GID 写上)
#vm.hugetlb_shm_group = 54321
# 可选:关闭自动 NUMA 平衡(按需)
#kernel.numa_balancing = 0
EOF
sudo sysctl --system
注:vm.hugetlb_shm_group 仅在你需要限制哪些进程可用 HugePages 的场景使用;大多数 Oracle 单实例主机可不设(或将 dba 组 GID 配上)。
5.3 给 oracle 用户放开 memlock
# 假设 dba 组为 dba,SGA=192GB => 201,326,592 KB
# /etc/security/limits.d/oracle.conf
sudo tee /etc/security/limits.d/oracle.conf >/dev/null <<'EOF'
oracle soft memlock 201326592
oracle hard memlock 201326592
EOF
修改后需重新登录会话或重启服务进程才会生效。可以 ulimit -l 验证(显示 KB)。
5.4 调整 Oracle 参数(ASMM & 强制大页)
在 SQL*Plus:
-- 关闭 AMM
ALTER SYSTEM SET memory_target=0 SCOPE=SPFILE;
ALTER SYSTEM SET memory_max_target=0 SCOPE=SPFILE;
-- 使用 ASMM
ALTER SYSTEM SET sga_max_size=192G SCOPE=SPFILE;
ALTER SYSTEM SET sga_target=192G SCOPE=BOTH;
ALTER SYSTEM SET pga_aggregate_target=64G SCOPE=BOTH;
-- 强制使用大页(不能分配就失败,避免“假启”)
ALTER SYSTEM SET use_large_pages='ONLY' SCOPE=SPFILE;
变更 SPFILE 后需要重启实例。首次重启建议在维护窗口进行。
5.5(可选)NUMA 策略与亲和性
机器为多 NUMA node(EPYC 多 chiplet)。SGA 192GB 会跨 NUMA。
可选策略:让 Oracle 进程跨节点交错分配,减少单节点热度:
用 numactl --interleave=all 启动监听/实例(视你们的启动脚本与 Grid/ASM 管理方式而定)。
或关 kernel.numa_balancing=0(在 sysctl 里配置,如上注释),避免内核自动迁移页造成抖动。
强烈建议:上线前做你们自家的 TPS/RT 基准对比,选择更稳的策略。
5.6 重启与验证
1)系统侧验证
# HugePages 是否就绪
grep -i Huge /proc/meminfo
# 期望:HugePages_Total 接近配置值(98304);HugePages_Free 在 Oracle 启动后显著下降(被占用)
# THP 是否关闭
cat /sys/kernel/mm/transparent_hugepage/enabled # 期望:[never]
# dmesg 有无大页分配失败
dmesg | egrep -i 'huge|thp|allocation failure' | tail
2)Oracle 侧验证
-- v$SGA 动态视图看 SGA 段是否稳定
SELECT * FROM v$sga;
-- 用 X$KSMMEM、v$memory_dynamic_components 等进一步确认
SELECT component, current_size, granule_size FROM v$memory_dynamic_components;
-- alert 日志中会有 “Large Pages” 相关信息(19c 通常会写明)
3)进程映射验证(粗暴但好用)
# 取出 PMON 进程号
PMON=$(pgrep -f ora_pmon)
# 看 /proc/$PMON/smaps 汇总是否有 Anonymous HugePages
grep -i 'AnonHugePages\|KernelPageSize\|MMUPageSize' /proc/$PMON/smaps | head -n 20
# 也可以直接看 HugePages_Free 的变化前后
watch -n1 "grep -i HugePages_ /proc/meminfo"
6. 可选:1GB HugePages(特大 SGA 的进阶打法)
如果你的 SGA ≥256GB,考虑用 1GB 大页以进一步降低 TLB miss 和页表开销。注意:必须在内核启动阶段预留。
GRUB 配置示例(SGA=256GB,预留 256 个 1GB 大页):
# 备份
sudo cp /etc/default/grub /etc/default/grub.bak.$(date +%F)
# 追加以下参数:
# default_hugepagesz=1G hugepagesz=1G hugepages=256 transparent_hugepage=never
sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="default_hugepagesz=1G hugepagesz=1G hugepages=256 transparent_hugepage=never /' /etc/default/grub
# 重建 grub 并重启(同上)
sudo grub2-mkconfig -o /boot/grub2/grub.cfg # 或 EFI 路径
验证:
grep -i Huge /proc/meminfo
# Hugepagesize: 1048576 kB (显示为 1GB)
Oracle 侧配置与 2MB 相同(AMM 仍需关闭,use_large_pages='ONLY')。
7. 上线后的对比数据(截取某次交易日 30 分钟窗口)
| 指标 | 变更前(THP=madvise,无 HugePages) | 变更后(THP=never,HugePages=2MB×98,304) |
|---|---|---|
| kswapd0 CPU 峰值 | 40% | < 3% |
page allocation failure |
多次 | 0 |
| AWR P95 SQL 响应 | 180ms | 110ms |
| 系统上下文切换(ctx/s) | ~150k | ~90k |
| LLC/TLB Miss(perf 采样) | 高 | 显著下降 |
| 稳定性 | 高峰偶发抖动 | 平滑 |
以上为我司该库在实机的抽样数据,曲线肉眼可见变稳,告警静了。
8. 实战中踩过的坑与解决过程
以为用了大页,实际没用
原因:未设 use_large_pages='ONLY',Oracle 分配失败后回退到普通页;或 memory_target 没清零。
处理:强制 ONLY,AMM 置 0;检查 HugePages_Free 的实时下降与 alert 日志。
memlock 不足导致分配失败
现象:alert 报错或实例启动失败;dmesg 有权限/锁定失败。
处理:把 /etc/security/limits.d/oracle.conf 的值调到 ≥ SGA(KB),重新登录用户或重启服务。
THP 没彻底关
现象:/sys/.../enabled 仍是 always 或 madvise;perf 看到 THP 路径热。
处理:走 GRUB 加载 transparent_hugepage=never;部分环境(安全启动/内核参数未生效)需核对实际 grub 文件与引导目标。
NUMA 不均导致单节点热
现象:numastat 某节点内存压力远高于其他节点,延迟抖。
处理:尝试 numactl --interleave=all 启动 DB;或关闭 kernel.numa_balancing,以你们的基准结果为准。
1GB HugePages 预留失败
现象:HugePages_Total 小于期望;dmesg 提示不可用。
处理:BIOS 里关内存镜像/在线热插等干扰;保证在非常早的引导阶段预留,必要时减少数量、分阶段推进。
容器/CGroup 限制
现象:在容器化环境,memlock/hugetlb cgroup 未放开,导致分配失败。
处理:在 cgroup/容器编排层增加 hugetlb 资源与 memlock 限制(Kubernetes 有单独的 hugetlb 配置项)。
9. 速查清单(复制即用)
计算页数(2MB)
页数 = (SGA_GB × 1024) / 2
例:192GB → 98304
关闭 THP(GRUB)
transparent_hugepage=never
持久化 HugePages(2MB)
/etc/sysctl.d/99-hugepages-oracle.conf
vm.nr_hugepages = <计算值>
memlock(KB)
/etc/security/limits.d/oracle.conf
oracle soft/hard memlock = SGA(GB) × 1024 × 1024
Oracle 参数
memory_target=0
memory_max_target=0
sga_max_size=<SGA>
sga_target=<SGA>
pga_aggregate_target=<PGA>
use_large_pages='ONLY'
验证
grep -i Huge /proc/meminfo
cat /sys/kernel/mm/transparent_hugepage/enabled
watch -n1 "grep -i HugePages_ /proc/meminfo"
10. 回滚与应急
数据库无法启动(大页不足):
- 临时改 use_large_pages=FALSE 启动,或减小 sga_target,或减少 vm.nr_hugepages 与 SGA 的差距;
- 若是 1GB 大页,先用 2MB 大页临时救火。
系统抖动更严重(少见,多为 NUMA 策略不当):
- 暂时恢复 kernel.numa_balancing=1 或撤回 numactl 策略,观察对比。
彻底回退:
- 恢复原 grub/sysctl 备份,memory_target 恢复原值,重启后验证。
变更后的第一个交易日,我特意把 AWR 报告的时间轴和机房墙上的时钟对上,盯着每一个抖点。那天收盘很安静,告警面板像一片平地。
我在走出冷通道前回头看了一眼那台机器的标签——“HK-COREDB-03”。贴纸已经被我换了新的:下面用马克笔写着“THP=never / HugePages=OK”。
做运维久了你会知道,很多“性能问题”其实是“确定性问题”。把 THP 关了,把 HugePages 立起来,让内核少点“临场发挥”,让数据库多点“笃定”。这口气,机房和盘面都能听见。
附:我常用的核查脚本片段(放到你的工具箱)
#!/usr/bin/env bash
# check_hugepages.sh - RHEL9 + Oracle 快速体检
echo "== THP =="
cat /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null || echo "N/A"
echo "== HugePages in /proc/meminfo =="
grep -i Huge /proc/meminfo
echo "== NUMA =="
numactl --hardware | sed -n '1,20p'
echo "== Oracle PMON & smaps (摘要) =="
PMON=$(pgrep -f ora_pmon | head -n1)
if [ -n "$PMON" ]; then
echo "PMON PID: $PMON"
grep -i 'AnonHugePages\|KernelPageSize\|MMUPageSize' /proc/$PMON/smaps | head -n 30
else
echo "PMON not found"
fi
如果你也在香港、也在 RHEL 9、也在跑大 SGA 的 Oracle,上面的清单与步骤基本能帮你把碎片抖动压下去。剩下的,就交给你的基准测试和交易量来验证吧。祝顺