1. 问题现象与初步排查
那天凌晨3点17分,值班手机突然响起刺耳的告警声。监控大屏上,TongWeb应用服务器的CPU曲线从60%瞬间跌到0%,JVM进程完全消失,就像被突然拔掉了电源。这种毫无征兆的宕机最让人头疼——既没有OOM日志,也没有GC超时记录,连最基本的hs_err_pid崩溃文件都没生成。
我立即登录服务器检查基础指标:
bash复制# 检查系统日志
journalctl -u tongweb --since "2023-08-15 03:15:00"
# 查看coredump配置
cat /proc/sys/kernel/core_pattern
ulimit -c
# 检索崩溃痕迹
grep -r "Segmentation fault" /opt/TongWeb/logs
发现两个异常点:1) 内核日志中出现多条page allocation failure;2) 虽然配置了coredump,但崩溃时磁盘IO飙升至100%,导致core文件写入失败。这提示我们可能遇到了系统级资源问题。
2. 内存泄漏的蛛丝马迹
通过分析宕机前72小时的监控数据,发现JVM堆内存使用呈现"锯齿状"上升趋势——每次Full GC后,内存基线都会比之前高5-8MB。这种"内存泄漏的经典心电图"在业务低峰期尤为明显:
| 时间周期 | 初始堆使用 | GC后堆使用 | 内存增长 |
|---|---|---|---|
| Day1 02:00 | 1.2GB | 680MB | +0MB |
| Day1 14:00 | 1.8GB | 1.01GB | +5MB |
| Day2 02:00 | 2.3GB | 1.32GB | +12MB |
使用MAT分析堆转储文件,发现org.apache.catalina.session.StandardSession对象异常增多,占用了47%的堆空间。进一步查看会话管理器配置:
xml复制<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true"
maxActiveSessions="10000">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
问题浮出水面:持久化会话管理器未配置会话过期策略,导致历史会话文件在重启时被重新加载,却永远不会被清理。
3. 线程死锁的隐藏陷阱
在检查线程堆栈时,意外发现多个线程阻塞在java.util.concurrent.locks.ReentrantLock上:
code复制"Thread-32" #32 daemon prio=5 os_prio=0 tid=0x00007f1a6822a800 nid=0x1e3e waiting on condition [0x00007f1a4a7e7000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f5d1a2b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
深入追踪发现,这是TongWeb自带的连接池与Druid数据源混用导致的经典死锁场景:
- 业务线程A持有Druid连接池锁,等待TongWeb连接池资源
- 心跳检测线程B持有TongWeb连接池锁,需要检查Druid连接状态
- 两个线程互相等待,形成环形依赖
关键教训:中间件默认线程池与业务线程池必须做好隔离,特别是当它们存在交叉依赖时。
4. 系统层面的致命组合
最终导致宕机的,是多个因素的叠加效应:
- 内存泄漏:持续增长的会话数据占满堆内存
- SWAP风暴:当物理内存不足时,系统开始疯狂交换
- OOM Killer:在内存完全耗尽前,内核选择杀死最占内存的JVM进程
- 磁盘IO瓶颈:交换操作和日志写入导致磁盘响应超时
通过以下命令还原了崩溃前5分钟的系统状态:
bash复制# 检查内存压力
cat /proc/meminfo | grep -E 'MemFree|SwapCached'
# 查看OOM Killer日志
dmesg | grep -i "killed process"
# 分析磁盘IO历史
sar -d -p 1 5
5. 解决方案与加固措施
5.1 会话管理优化
xml复制<Manager className="org.apache.catalina.session.PersistentManager"
maxActiveSessions="5000"
maxIdleBackup="30"
minIdleSwap="60">
<Store className="org.apache.catalina.session.FileStore"
directory="../session_data"/>
</Manager>
- 设置会话超时时间为30分钟
- 当空闲会话超过5000个时启动自动清理
- 将会话存储目录移出系统盘
5.2 线程池隔离方案
java复制// 自定义线程池用于Druid心跳检测
ScheduledExecutorService druidHeartbeatExecutor = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder().setNameFormat("druid-heartbeat-%d").build());
// 修改Druid配置
druidDataSource.setCreateScheduler(druidHeartbeatExecutor);
5.3 系统层加固
bash复制# 调整内核参数
echo "vm.swappiness=10" >> /etc/sysctl.conf
echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
# 限制coredump生成路径
echo "/data/corefiles/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
6. 监控体系的升级
新增以下监控指标:
- 会话增长速率告警(每分钟新增会话数>100时触发)
- 线程锁等待时间监控(超过500ms即报警)
- 物理内存与交换空间使用率关联分析
配置Prometheus抓取规则示例:
yaml复制- name: tongweb_session
rules:
- alert: SessionLeak
expr: increase(tongweb_session_active_total[1h]) > 5000
for: 30m
labels:
severity: critical
annotations:
summary: "会话泄漏 detected on {{ $labels.instance }}"
经过三个月的运行观察,系统再未出现同类故障。这个案例给我的深刻启示是:中间件的默认配置往往不适合生产环境,特别是在资源受限的场景下,必须对内存、线程、IO等关键维度建立立体监控体系。
