上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:2025-09-06 11:52 阅读量:808


那天我在香港荃湾机房的夜班,交易日快收盘时,监控屏上突然冒出一串红:某核心库的 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,上面的清单与步骤基本能帮你把碎片抖动压下去。剩下的,就交给你的基准测试和交易量来验证吧。祝顺

目录结构
全文