从凌晨2点重启到零故障!SpringBoot线上故障排查全实战:OOM/死锁/慢SQL到高可用优化
去年给天津滨海新区某汽车零部件工厂做生产执行MES系统上线后我们经历了长达1个月的线上故障噩梦系统每周3次JVM OOM宕机运维凌晨2点爬起来重启服务每月2次数据库死锁工单提交直接卡壳必须重启数据库才能恢复高峰期工单查询接口响应超时数据库CPU直接打满100%200产线终端同时在线时卡顿严重客户验收一度亮起红灯。后来我们建立了标准化的故障排查体系从OOM、死锁、慢SQL三大核心故障入手完成根因修复再做全链路高可用优化最终实现系统连续18个月零故障核心接口响应从2.1s稳定到120ms以内。本文就从真实生产场景出发完整拆解SpringBoot线上故障的排查流程、解决方案与高可用加固方案所有内容均经过生产验证可直接复用。一、线上故障排查的黄金原则与标准流程线上故障排查的第一准则永远是先止损恢复业务再定位根因分析绝对不能在业务受损的情况下做长时间调试这是新手最容易踩的坑。我们总结了一套可复制的故障排查标准流程无论什么类型的故障都能按这个流程快速推进故障告警触发紧急止损恢复业务故障分级与影响范围评估根因定位与现场保留修复方案与灰度验证全量上线与效果复盘高可用架构加固核心止损手段生产环境必备流量切换Nginx切流量到备用节点K8s快速重启故障Pod先恢复业务可用服务降级用Sentinel熔断非核心接口释放系统资源给核心业务会话清理数据库kill掉长时间执行的慢SQL、死锁会话快速解除阻塞资源扩容临时扩容CPU、内存、数据库连接池先缓解高峰期压力。二、三大核心线上故障排查实战生产可直接复用SpringBoot线上故障80%都集中在JVM OOM内存溢出、线程/数据库死锁、慢SQL与数据库性能问题这三类我们逐个拆解实战排查过程。2.1 JVM OOM内存溢出从每周宕机到零故障故障现象系统高峰期频繁宕机日志报错java.lang.OutOfMemoryError: Java heap space每周固定出现3次凌晨工单导出高峰期必现服务器CPU飙升到95%接口完全超时无响应只能重启服务恢复。紧急止损先切流量到备用节点重启主服务恢复业务必须第一时间保留现场开启OOM自动堆转储避免重启后丢失根因证据。根因排查过程JVM状态初步排查用jps -l找到Java进程PID再用jstat -gcutil PID 1000 10每秒输出一次GC状态发现老年代占用率长期98%以上Full GC每分钟8次单次GC耗时超过2s在线深度诊断用阿里开源的Arthas工具无需重启服务执行dashboard命令实时查看堆内存占用发现120万的工单对象占用了8GB堆内存定位到工单导出接口堆快照分析用heapdump /tmp/heap.hprof命令导出堆快照用VisualVM分析发现工单导出接口的List对象无法被GC回收内存持续泄漏。根因分析接口参数无校验前端传了pageSize999999后端没有做上限限制直接全表查询120万条数据全部加载到内存线程池配置错误用了Executors.newCachedThreadPool无界队列高峰期任务持续堆积对象无法被GC回收MyBatis一级缓存开启长事务下缓存了大量查询对象会话不关闭就无法释放导致内存持续上涨。解决方案强制参数校验pageSize最大限制10000导出用MyBatis流式查询分批写入Excel不全部加载到内存自定义线程池核心线程数CPU核心数1最大线程数CPU核心数*2队列容量1000拒绝策略用CallerRunsPolicy避免无界队列堆积关闭MyBatis一级缓存用短事务替代长事务避免缓存对象堆积新增JVM参数OOM时自动保留现场-Xms10g-Xmx10g-Xmn3g-XX:MetaspaceSize256m-XX:MaxMetaspaceSize512m-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/var/log/jvm/heapdump.hprof-XX:PrintGCDetails-XX:PrintGCDateStamps-Xloggc:/var/log/jvm/gc.log避坑指南绝对不要重启服务后就丢了现场必须开启OOM自动堆转储保留根因证据生产环境禁止用Executors默认线程池必须自定义线程池避免无界队列导致的OOM大数据量导出必须用流式查询分批处理禁止全量加载到内存。2.2 线程/数据库死锁最隐蔽的故障故障现象工单提交接口完全卡住不报错也不返回数据库连接池被占满其他接口也全部超时每月固定出现2次必须重启数据库和服务才能恢复。紧急止损先kill掉数据库的死锁会话切流量到备用节点重启服务恢复业务同时保留线程快照和数据库死锁日志。根因排查过程Java线程死锁排查用Arthas的thread -b命令一键定位到阻塞其他线程的根源线程发现两个业务线程互相持有对方的锁线程A持有orderLock等待stockLock线程B持有stockLock等待orderLock形成死锁数据库死锁排查执行SHOW ENGINE INNODB STATUS查看死锁日志发现两个事务更新工单表和库存表的顺序完全相反事务A先更新工单再更新库存事务B先更新库存再更新工单导致行锁互等形成数据库死锁。解决方案Java层面统一加锁顺序按ID从小到大的顺序加锁避免反向加锁用tryLock设置超时时间避免无限等待数据库层面所有事务强制统一更新顺序先更新主表工单再更新从表库存禁止反向更新事务范围最小化不要在事务中做RPC调用、非索引查询等耗时操作开启数据库死锁日志设置innodb_print_all_deadlocks ON自动记录死锁事件。避坑指南所有加锁、数据库更新操作必须统一顺序这是避免死锁的核心生产环境必须开启死锁日志提前告警不要等业务完全阻塞才发现禁止在事务中执行耗时操作缩短事务持有锁的时间降低死锁概率。2.3 慢SQL与数据库性能问题最普遍的性能杀手故障现象高峰期工单查询接口响应超时数据库CPU飙升到100%整个系统卡顿非高峰期恢复正常客户投诉频繁。紧急止损先kill掉长时间执行的慢SQL会话切读流量到只读库先恢复业务可用。根因排查过程开启慢查询日志设置long_query_time 1用pt-query-digest分析慢日志定位到工单查询接口的SQL执行时间2.1s扫描行数120万全表扫描用EXPLAIN分析执行计划发现typeALL全表扫描keyNULL索引失效同时出现Using filesort、Using temporary额外排序和临时表导致性能极差根因深分页LIMIT 100000,20大偏移量查询WHERE条件里的create_time用了DATE()函数导致索引失效没有覆盖索引回表次数过多。解决方案索引优化创建覆盖索引idx_order_status_time(status, create_time, id, order_no, product_name)避免回表消除全表扫描SQL优化用ID游标分页替代深分页SELECT * FROM mes_order WHERE status 1 AND id 100000 LIMIT 20避免大偏移量扫描禁止在WHERE条件里用函数操作索引字段缓存优化热点查询用Redis缓存有效期5分钟避免频繁查询数据库读写分离主库负责写从库负责读分摊高峰期主库压力。避坑指南上线前必须用EXPLAIN分析所有SQL杜绝全表扫描、索引失效的SQL上线生产环境必须开启慢查询日志定期巡检提前优化慢SQL禁止用SELECT *查询只查需要的字段尽量用覆盖索引避免回表。三、从故障解决到高可用优化从根源避免故障解决单次故障只是第一步真正的核心是通过高可用架构优化从根源上避免故障再次发生。我们从三个层面做了全链路加固3.1 架构层面避免单点故障与雪崩集群部署与负载均衡所有服务至少部署2个实例分布在不同服务器避免单点故障用Nginx做负载均衡故障节点自动剔除读写分离与分库分表主库写从库读单表数据超过500万行做分表避免大表查询性能下降熔断降级与限流用Sentinel实现接口熔断降级依赖服务异常时返回兜底数据避免级联故障用RedisLua做接口限流高峰期保护系统服务隔离核心业务和非核心业务线程池隔离避免非核心业务占用资源影响核心流程。3.2 代码层面规范先行杜绝隐患强制参数校验所有入参必须做合法性校验避免非法参数导致的内存溢出、慢SQL统一线程池规范禁止用Executors默认线程池必须自定义线程池配置合理的核心线程数、最大线程数、队列容量和拒绝策略数据库规范事务最小化统一更新顺序禁止大事务、长事务上线前必须做SQL审核确保索引合理资源释放规范流、连接、文件必须在finally里关闭避免资源泄漏。3.3 运维监控层面提前预警防患于未然全链路监控用PrometheusGrafana监控JVM、CPU、内存、数据库、接口响应时间SkyWalking做全链路追踪定位性能瓶颈多级告警体系配置接口超时、FGC频繁、OOM前兆、慢SQL、死锁的提前告警不用等宕机了才处理灰度发布上线先灰度10%流量观察监控指标没问题再全量上线避免全量故障全链路压测上线前模拟高峰期流量做压测提前发现内存泄漏、慢SQL、死锁等问题不要等线上出故障再解决。四、线上故障排查核心工具链新手必备故障类型核心工具核心用途紧急止损Nginx/K8s流量切换、服务重启JVM故障Arthas在线诊断无需重启定位OOM、死锁、接口慢问题JVM故障jps/jstat/jmap/jstack查看进程ID、GC状态、堆内存、线程快照数据库故障慢查询日志/pt-query-digest分析慢SQL定位性能瓶颈数据库故障EXPLAIN/SHOW ENGINE INNODB STATUS分析SQL执行计划、查看死锁日志监控告警PrometheusGrafana/SkyWalking全链路监控、性能瓶颈定位、提前告警写在最后SpringBoot线上故障不可怕可怕的是没有标准化的排查流程没有提前的监控告警没有事后的复盘优化。我们从最初的凌晨2点爬起来重启服务到后来的提前预警、提前优化连续18个月零故障核心就是建立了标准化的故障排查体系和全链路的高可用防护架构。线上80%的故障都是OOM、死锁、慢SQL这三类只要掌握了正确的工具和排查方法就能快速定位根因解决问题。更重要的是通过高可用优化从根本上避免故障再次发生这才是企业级开发的核心能力。如果你在SpringBoot线上故障排查中遇到任何问题欢迎在评论区交流讨论。