2、常见故障类型:网络延迟、数据库死锁、内存溢出、进程挂死
做券商运维这些年,我见过最多的故障,其实就这四类。说白了,它们就像四个老对手,隔三差五就来敲你的门。今天咱们一个一个聊透。
2.1 网络延迟——最隐蔽的杀手
网络延迟这玩意儿,平时不声不响,一出事就是大事。我个人习惯把网络延迟分成两类:偶发抖动和持续高延迟。
- 偶发抖动:比如交易所行情推送时,某个交换机突然丢了一个包。客户端等了几百毫秒没收到,直接超时重连。嗯,这种最坑人——你查日志啥都看不出来。
- 持续高延迟:比如跨机房的光纤被挖断了,或者某个网卡跑满了。这时候交易响应时间从2ms飙到200ms,客户早就炸了。
我的经验:有一次排查一个“报单慢”的问题,查了三天数据库、应用代码,最后发现是运维同事在同一个交换机上跑了个大文件备份。网络延迟从0.5ms变成了50ms。你想想看,这种问题多冤。
排查网络延迟,我一般三步走:
- ping + mtr:先看延迟在哪一跳飙升。
- tcpdump抓包:确认是不是有重传或乱序。
- 检查网卡和交换机:看看有没有丢包计数器在涨。
# 快速检查网络延迟
ping -c 10 192.168.1.100
mtr -r 192.168.1.100
netstat -s | grep -i "retransmit"
2.2 数据库死锁——两个事务互相掐架
数据库死锁,说白了就是两个事务都在等对方释放资源。谁也不让谁,最后数据库自己掐死自己。
我在项目中遇到过最典型的场景:资金账户更新和持仓更新同时操作同一个客户的不同表,但锁的顺序反了。A事务先锁资金表再锁持仓表,B事务先锁持仓表再锁资金表。好嘛,死锁了。
核心判断标准:应用日志里出现“Deadlock found when trying to get lock”或者“事务(进程 ID 56)与另一个进程被死锁在资源上”。
怎么处理?我建议按这个顺序来:
- 第一步:用
SHOW ENGINE INNODB STATUS\G查看最近一次死锁的详细信息。 - 第二步:找到涉及的两个SQL,分析它们的锁顺序。
- 第三步:修改代码,让所有事务按相同顺序访问表或行。
-- 查看死锁信息
SHOW ENGINE INNODB STATUS\G
-- 重点关注这部分:
-- LATEST DETECTED DEADLOCK
-- 它会告诉你哪个事务持有哪个锁,在等哪个锁
避坑指南:我曾经遇到一个死锁,查了半天发现是ORM框架自动生成的SQL,锁的粒度比预期大。所以别太相信框架,关键SQL一定要自己看执行计划。
2.3 内存溢出——Java进程的“癌症”
内存溢出,OOM,做Java的没人不知道。但券商系统里,OOM的后果特别严重——交易进程一挂,客户单子全堵在路上。
常见的OOM场景:
- 堆内存溢出:比如行情缓存没控制好,一天下来堆涨了2个G。
- 元空间溢出:动态生成类太多,比如某些框架的热加载。
- 直接内存溢出:NIO操作没释放ByteBuffer,这个最隐蔽。
我个人习惯的排查流程:
- 先看监控——堆内存使用曲线是不是一直往上走,不回落。
- 加
-XX:+HeapDumpOnOutOfMemoryError参数,等下次OOM自动生成dump文件。 - 用MAT或JProfiler分析dump,找那个占用内存最大的对象。
# JVM参数示例
-Xms4g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dumps/
一个小技巧:如果线上不能随便重启加参数,可以用
jmap -dump:live,format=b,file=heap.hprof <pid> 手动触发dump。但注意,这会触发一次Full GC,生产环境慎用。
2.4 进程挂死——最让人头疼的“假死”
进程挂死,不是进程退出了,而是它还在,但啥也不干。CPU 0%,内存正常,端口还开着,但就是不处理请求。
为什么会这样?我遇到过几种情况:
- 线程死锁:Java层面的synchronized嵌套,或者数据库连接池全部被占满。
- 阻塞I/O:比如某个文件系统挂掉了,线程卡在FileInputStream.read()上。
- GC停顿:CMS或G1的Full GC时间太长,导致应用线程长时间暂停。
排查方法其实很固定:
- 用
jstack <pid>打印线程栈,看哪些线程处于BLOCKED或WAITING状态。 - 重点关注那些长时间卡在同一个代码位置的线程。
- 如果是GC问题,用
jstat -gcutil <pid> 1000看GC频率和停顿时间。
# 打印线程栈
jstack 12345 > thread_dump.txt
# 查看GC情况
jstat -gcutil 12345 1000 10
我曾经踩过的坑:有一次进程挂死,jstack一看所有线程都在等一个数据库连接。但数据库明明活着。最后发现是连接池的
maxWait 设置成了-1(无限等待),而连接数被某个慢查询占满了。嗯,从那以后我每个连接池参数都亲自过一遍。
知识体系总览
下面这张图,是我自己总结的故障排查框架。你把它记在脑子里,遇到问题就不慌了。
这四种故障,你只要把排查路径练熟了,80%的线上问题都能在10分钟内定位。剩下的20%,嗯,那就得靠经验和一点点运气了。
最后说一句:别等到出事了才去学。我建议你每周找个测试环境,故意制造一次网络延迟或死锁,亲手走一遍排查流程。真到生产上,你才不会慌。