凌晨两点,告警短信轰炸:实例 1 宕了,alert log 最后一条是 'LGWR is stuck'。重启后一切正常,但过两天又宕。这种间歇性宕机最折磨人,因为重启后所有证据都清掉了,只能看历史 trace。
1 LGWR 为什么会 stuck
LGWR 的职责是把 redo log buffer 里的数据写到在线日志文件。它的写入路径是:LGWR → OS 内核 → 文件系统 → 块设备 → 存储阵列。任何一个环节卡住,LGWR 都会进入 'log file parallel write' 等待。如果等待时间超过 3 分钟(默认的 LGWR timeout),PMON 会认为 LGWR 死了,为了数据一致性,PMON 会主动把实例杀掉(instance crash)。这就是 alert log 里 'LGWR is stuck' 后面跟着 'Instance terminated by PMON' 的原因。
我们那次的根因不在 Oracle 层,在存储多路径(multipath)层。客户的存储是 FC 双链路,multipath 配置了 4 条路径。其中一条路径的光模块老化,间歇性闪断,multipath 的 path checker 每隔 5 秒检测一次,检测到故障后把 I/O 切到备用路径。但这个切换过程有 2-3 秒的窗口,如果 LGWR 正好在这 2-3 秒内写日志,就会卡住。由于光模块是间歇性故障,不是彻底坏,所以很难抓到现场。
2 模拟实验:用 SystemTap 注入 I/O 延迟
-- 查看 LGWR 的等待事件历史
SELECT event, total_waits, time_waited_micro/1000 time_ms, max_wait_micro/1000 max_ms
FROM v$system_event WHERE event LIKE '%log file%';
-- 正常:log file parallel write 的 max_ms < 50
-- 故障时:max_ms = 185000(185 秒)
-- OS 层查看 LGWR 的系统调用
strace -p $(pgrep -f lgwr) -e pwrite64,fdatasync
-- 正常:pwrite64 返回很快
-- 故障时:pwrite64 卡住 120 秒,返回 EAGAIN 或 EIO
-- SystemTap 脚本模拟存储延迟(测试环境)
probe syscall.pwrite64
{
if (execname() == "oracle" && pid() == target_pid)
{
if (gettimeofday_ms() % 10000 < 3000) -- 每 10 秒模拟 3 秒延迟
mdelay(3000);
}
}
-- 运行后观察 LGWR 是否进入 stuck 状态
我们在测试环境用 SystemTap 模拟了 3 秒 I/O 延迟,结果 LGWR 的 'log file parallel write'等待时间飙到 3000ms,但还没触发 PMON 杀实例。后来把延迟调到 180 秒,PMON 才动手。这说明 PMON 的阈值不是固定的,跟 LGWR 的写入频率和 redo 量有关。如果 LGWR 积压了大量 redo,等待容忍时间会更短。
修复方案:第一,更换了老化的光模块,这是治本;第二,把 multipath 的 polling_interval 从 5 秒降到 1 秒,故障检测更快;第三,在 Oracle 层把在线日志文件放到 ASM 的 HIGH 冗余磁盘组,ASM 的镜像机制可以在单路径故障时自动切到镜像副本,避免 LGWR 感知到 I/O 中断。最后,建议所有生产库的存储链路都要做冗余,而且别只信 multipath,ASM 的镜像才是最后一道防线。