生产环境中经常遇到这样的场景:监控系统报警显示服务无响应,但检查发现进程仍在运行,端口正常监听,CPU使用率异常(可能是100%或接近0%)。这种现象称为"服务假死"。
服务假死的核心特征:
- 进程存活但无法处理新请求
- 端口监听正常但连接超时
- 日志输出停滞或出现大量异常
- 健康检查失败,负载均衡摘除节点
与服务宕机不同,假死状态不会触发进程自动重启,会持续占用系统资源,影响范围更大。
常见原因分析
1. 数据库连接池耗尽
问题表现: 应用线程大量阻塞在获取数据库连接的操作上,通过jstack可以看到线程堆栈停留在DataSource.getConnection()方法。
根本原因:
- 连接池容量配置过小(默认配置通常只有8-10个连接)
- 慢SQL占用连接时间过长,导致连接周转率低
- 事务未正确提交或回滚,造成连接泄漏
- 突发流量超过连接池承载能力
解决方案: 根据业务并发量合理设置连接池参数(最大连接数、最小空闲连接数、获取连接超时时间),优化慢SQL,使用连接池监控工具(如Druid)及时发现连接泄漏。
2. 线程池资源耗尽
问题表现: 应用无法处理新请求,线程池队列已满,拒绝策略被频繁触发。
根本原因:
- 核心线程数设置过小,无法应对正常业务负载
- 队列容量不足,高峰期任务堆积
- 拒绝策略不当(如使用阻塞策略导致调用线程被挂起)
- 任务执行时间过长,线程无法及时释放
解决方案: 根据业务特点设置合理的线程池参数。对于IO密集型任务,线程数可设置为CPU核心数的2-3倍;对于CPU密集型任务,设置为CPU核心数+1。不同业务场景使用独立线程池,避免相互影响。Web请求处理应使用快速失败策略,批量任务可使用调用者运行策略。
3. Redis连接异常
问题表现: 日志中出现大量"Cannot get Jedis connection"或"Connection timeout"异常,接口响应时间显著增加。
根本原因:
- Redis连接池配置不合理
- Redis服务端存在慢查询(如大key操作、复杂的Lua脚本)
- 网络波动导致连接中断
- Redis服务器资源不足(内存、CPU)
解决方案: 优化Redis连接池配置,设置合理的超时时间(建议3-5秒),对Redis操作实施降级策略。避免使用大key,定期清理过期数据,必要时使用Redis集群分散压力。
4. 外部接口调用超时
问题表现: 应用线程大量阻塞在HTTP调用或RPC调用上,Tomcat工作线程耗尽。
根本原因:
- 未设置连接超时和读取超时参数
- 第三方服务响应慢或不可用
- 网络故障导致请求挂起
- 缺少熔断降级机制
解决方案: 所有外部调用必须设置超时时间(连接超时建议3秒,读取超时根据业务设置5-10秒)。引入熔断降级机制(如Hystrix、Sentinel),当下游服务异常时快速失败,避免雪崩效应。
5. 线程死锁
问题表现: 通过jstack可以检测到死锁,相关线程永久阻塞。
根本原因:
- 多个线程以不同顺序获取多个锁
- 嵌套锁使用不当
- 锁未正确释放
解决方案: 避免嵌套锁,统一锁的获取顺序。使用Lock接口的tryLock方法设置超时时间,优先使用JUC并发工具类(ConcurrentHashMap、CopyOnWriteArrayList等)替代synchronized。
排查方法
线程堆栈分析
使用jstack导出线程堆栈是定位问题的关键手段:
jstack <pid> > thread_dump.txt
重点关注线程状态:
- BLOCKED:线程在等待获取锁,可能存在锁竞争或死锁
- WAITING:线程在等待某个条件(如等待连接池资源)
- TIMED_WAITING:带超时的等待,需确认等待时间是否合理
如果发现大量线程堆栈相似且处于等待状态,说明存在资源瓶颈。例如,大量线程阻塞在DataSource.getConnection(),说明数据库连接池耗尽。
数据库连接检查
执行MySQL命令查看当前连接情况:
SHOW PROCESSLIST;
关注TIME列较大的连接,这些可能是慢SQL。结合慢查询日志分析具体SQL语句,优化索引或改写查询逻辑。
系统资源监控
检查CPU、内存、网络IO等系统指标:
- CPU 100%:可能是死循环、大量计算或GC频繁
- CPU接近0%:可能是线程全部阻塞
- 内存持续增长:可能存在内存泄漏
- 网络IO异常:可能是外部调用问题
预防措施
超时时间设置规范
所有外部依赖调用必须设置超时参数,建议值:
- 数据库查询:30秒(复杂查询可适当延长)
- Redis操作:3-5秒
- HTTP/RPC调用:5-10秒(根据业务SLA确定)
- 消息队列消费:根据业务逻辑设置
监控告警体系
建立多层次监控:
- 应用层:接口响应时间、错误率、QPS、线程池使用率
- 中间件层:数据库连接数、Redis连接数、消息队列堆积
- 系统层:CPU、内存、磁盘IO、网络流量
设置合理的告警阈值,避免告警疲劳。关键指标应设置多级告警(预警、严重、紧急)。
压力测试
定期进行压力测试,模拟生产环境流量:
- 确定系统容量上限
- 验证限流降级策略有效性
- 发现性能瓶颈并优化
- 验证扩容方案可行性
代码规范
- 数据库操作必须在finally块中关闭连接
- 使用try-with-resources自动管理资源
- 避免在锁内执行耗时操作
- 合理使用连接池,避免创建过多连接
应急处理流程
- 保留现场:重启前导出jstack、jmap信息,保存应用日志
- 快速恢复:重启服务或切换流量到备用节点
- 根因分析:离线分析dump文件,定位问题代码
- 修复验证:修改代码或配置,在测试环境验证
- 上线观察:发布到生产环境,持续观察监控指标
- 复盘总结:记录故障原因、处理过程、改进措施
总结
服务假死问题的本质是资源耗尽或线程阻塞。通过合理的配置、完善的监控、规范的编码,可以有效预防此类问题。当问题发生时,系统化的排查方法能够快速定位根因。建议团队建立故障知识库,积累问题处理经验,提升整体应急响应能力。