1. 从“提取”二字说起:异常排查的底层逻辑
“常见异常及原因---提取”这个标题,乍一看像是某个技术文档里的一个章节名,甚至有点不起眼。但如果你在开发或运维一线待过几年,就会明白这六个字背后藏着多少血泪。异常排查这件事,本质上就是一个“提取”的过程——从一堆杂乱无章的日志、堆栈、监控指标里,把真正有用的信息提取出来,然后定位根因。这个能力,比会写多少行代码重要得多。
我见过太多人,遇到报错第一反应是复制错误信息去搜索引擎,然后在一堆似是而非的答案里碰运气。这种做法在简单场景下能蒙对,但一旦遇到稍微复杂一点的问题,就彻底抓瞎。真正高效的排查,靠的是一套系统化的提取方法:先确定异常的分类,再根据分类锁定可能的原因范围,最后用排除法逐步缩小,直到找到那个唯一的真相。
这篇文章要聊的,就是这套方法。我会把常见的异常类型做一个系统梳理,然后针对每一类,讲清楚它的典型表现、背后的原理、以及最可能的原因。更重要的是,我会分享一些在实际操作中总结出来的提取技巧——怎么从日志里快速捞出关键信息,怎么判断一个异常是表象还是根因,怎么避免在排查过程中被误导。这些内容,你在官方文档里基本看不到,但却是真正能帮你省下大量时间的干货。
不管你是刚入行的新手,还是已经有一定经验的开发者,只要你的工作涉及系统排错,这篇文章都值得你花时间读完。我会尽量用大白话把原理讲透,同时给出可以直接上手操作的步骤和命令。目标只有一个:让你下次遇到异常时,不再靠猜,而是有一套清晰的思路可以依赖。
2. 异常分类的底层框架:先搞清楚你在面对什么
2.1 按来源划分:代码层、运行时层、系统层、网络层
很多人排查异常时容易陷入一个误区:一上来就盯着报错信息看,却忽略了异常发生的层级。不同层级的异常,排查思路和工具完全不同。我习惯把异常按来源分成四层,这个分类框架是我自己多年实践总结出来的,非常好用。
代码层异常是最容易理解的,就是你的业务代码本身逻辑有问题。比如空指针、数组越界、类型转换错误、除零错误。这类异常的特点是:堆栈信息会精确指向你的代码行号,而且通常在特定输入下才会触发。排查这类问题,重点看堆栈最上面的几行,找到你自己的代码位置,然后检查输入数据是否符合预期。
运行时层异常指的是语言虚拟机或运行时环境层面的问题。比如内存溢出、栈溢出、垃圾回收频繁导致的长暂停、线程死锁。这类异常的特点是:堆栈信息可能不指向你的代码,或者指向的是框架代码。排查这类问题,需要借助运行时提供的监控工具,比如堆转储分析、线程转储分析、GC日志分析。
系统层异常是操作系统层面的问题。比如文件句柄耗尽、磁盘空间不足、CPU负载过高、进程被系统杀死。这类异常的特点是:应用日志里可能只看到一个模糊的错误,比如“无法创建文件”或“连接被拒绝”,但真正的原因在系统层面。排查这类问题,需要登录到服务器上,用系统命令查看资源使用情况。
网络层异常是服务之间通信的问题。比如连接超时、DNS解析失败、TLS握手失败、数据包丢失。这类异常的特点是:通常表现为间歇性故障,而且往往和特定的网络路径或时间段相关。排查这类问题,需要用到网络诊断工具,比如抓包分析、路由追踪、延迟测量。
把这四层分清楚,你就能在拿到一个异常时,先判断它属于哪一层,然后直接跳到对应的排查路径上。这个判断过程本身,就是一次高效的“提取”。
2.2 按表现划分:崩溃型、降级型、间歇型、静默型
除了按来源分类,按表现分类同样重要。因为不同类型的异常,对业务的影响程度不同,排查的紧迫性和策略也不同。
崩溃型异常是最直接的,进程直接退出或者服务完全不可用。这类异常通常有明确的错误日志和退出码,排查起来相对容易,因为现场比较清晰。但要注意的是,崩溃型异常有时候是“果”而不是“因”——比如进程因为内存溢出被系统杀死,真正的原因可能是代码里有内存泄漏。
降级型异常是指服务虽然没有完全挂掉,但性能大幅下降或者部分功能不可用。比如响应时间从100毫秒变成5秒,或者某个接口开始返回错误。这类异常最容易被忽视,因为系统还在“运行”,但用户体验已经严重受损。排查这类问题,需要对比正常时期和异常时期的监控指标,找出差异点。
间歇型异常是最让人头疼的,时好时坏,难以复现。比如每隔几小时出现一次连接超时,或者在高并发时偶尔出现数据不一致。这类异常的排查,需要借助日志聚合和趋势分析,找出异常发生的时间规律和条件规律。很多时候,间歇型异常的根因是资源竞争或者边界条件,需要耐心和细致的观察。
静默型异常是最危险的,系统看起来一切正常,但实际数据已经出错或者任务已经失败。比如定时任务没有执行但没有任何报错,或者数据写入成功但内容不对。这类异常往往要等到业务方反馈才能发现,排查时需要检查任务调度记录、数据校验日志、以及上下游系统的交互记录。
我自己的经验是,拿到一个异常后,先判断它属于哪种表现类型,然后根据类型选择对应的排查策略。崩溃型优先看现场,降级型优先看对比,间歇型优先看规律,静默型优先看校验。这个判断过程,往往能帮你省下大量盲目搜索的时间。
2.3 异常信息提取的黄金三要素:时间、堆栈、上下文
不管你面对的是哪一层、哪一类的异常,有三样东西是必须第一时间提取出来的:时间、堆栈、上下文。我把它称为“黄金三要素”。
时间不仅仅是异常发生的时间点,还包括:异常持续了多久、是第一次出现还是反复出现、发生前后有没有其他事件(比如发布、配置变更、流量突增)。时间信息能帮你建立因果关系,很多异常的原因就藏在时间线里。
堆栈是异常现场最直接的证据。但很多人看堆栈只看最上面一行,这是不够的。完整的堆栈能告诉你异常的传播路径:从哪个方法抛出,经过了哪些调用,最终在哪里被捕获。有时候最上面的异常只是表象,真正的问题在堆栈的更深处。我习惯从下往上读堆栈,先看最底层的触发点,再看中间的传播路径,最后看顶层的表现。
上下文包括:异常发生时的输入参数、系统状态、并发情况、依赖服务的健康状况。这些信息往往不在异常堆栈里,需要你主动去收集。比如一个空指针异常,堆栈只告诉你哪一行出了问题,但你需要知道那一行接收到的参数是什么,才能判断为什么是空。上下文信息通常散落在不同的日志文件和监控系统里,提取时需要有一定的联想能力。
这三要素提取到位了,大部分异常的原因其实已经呼之欲出了。剩下的工作,就是验证你的假设。
3. 高频异常场景的逐类拆解与提取要点
3.1 空指针与类型转换:最基础也最容易翻车
空指针异常和类型转换异常,可以说是所有开发者遇到最多的两类异常。它们的基础程度,以至于很多人觉得不值得专门讨论。但恰恰是这两类异常,在实际排查中经常被误判。
先说说空指针。很多人看到空指针,第一反应是“某个对象没初始化”。这个判断在简单场景下没错,但在复杂系统里,空指针的来源可能非常隐蔽。比如:从缓存里取出来的对象是空的,但缓存本身没有报错;从数据库查询返回的结果是空的,但查询语句本身是合法的;从上游服务接收到的响应里某个字段是空的,但HTTP状态码是200。这些情况下,空指针只是最终的表现,真正的问题在于数据来源。
我处理空指针异常时,有一个固定的提取流程:第一步,看堆栈定位到具体行号;第二步,检查该行涉及的所有对象,判断哪个可能为空;第三步,追溯这个对象的来源,是本地创建、外部传入、还是从某个容器里取出来的;第四步,检查来源环节有没有做空值校验。这个流程走下来,基本能定位到根因。
类型转换异常也是类似。表面上看是“把A类型转成了B类型但失败了”,但真正的原因可能是:数据格式不符合预期、序列化协议不匹配、或者版本不一致导致字段类型变了。我遇到过一次典型的情况:上游服务把一个数字字段从整数改成了字符串,但下游服务还在按整数解析,结果就抛出了类型转换异常。这种问题,光看堆栈是看不出来的,必须结合上下游的接口文档和变更记录。
注意:空指针和类型转换异常虽然简单,但千万不要只看堆栈就下结论。多问一句“这个值为什么是空的/这个类型为什么不对”,往往能发现更深层的问题。
3.2 内存溢出与栈溢出:堆和栈的边界在哪里
内存溢出和栈溢出,是运行时层异常里最典型的两种。很多人分不清它们的区别,导致排查方向完全错误。
内存溢出发生在堆上,通常是因为对象创建太多或者太大,垃圾回收来不及释放。典型的表现是:程序运行一段时间后越来越慢,最终抛出内存溢出错误。排查内存溢出,核心是找到“谁在占用内存”。我常用的方法是:先看堆转储文件,按对象数量和大小排序,找出占用最多的对象类型;然后看这些对象的引用链,找到是谁在持有它们;最后检查对应的代码逻辑,看有没有不合理的大对象创建或者集合无限增长。
栈溢出发生在栈上,通常是因为递归调用太深或者方法调用链太长。典型的表现是:程序在某个特定操作时直接崩溃,堆栈里能看到大量重复的调用帧。排查栈溢出相对简单,看堆栈里重复的模式就能定位到递归方法。但要注意的是,有时候栈溢出不是递归引起的,而是方法本身太复杂,局部变量太多,导致单个栈帧过大。这种情况需要优化方法结构,把大方法拆成小方法。
这里有一个容易被忽略的点:堆和栈的大小是可以配置的。有时候异常的发生不是因为代码有问题,而是因为默认的堆栈大小不适合当前的业务场景。比如一个批处理任务需要创建大量临时对象,默认堆大小不够,就会频繁触发垃圾回收甚至内存溢出。这种情况下,调整堆大小参数就能解决问题。但调整之前,一定要先确认代码本身没有内存泄漏,否则只是把问题推迟了。
3.3 连接超时与拒绝连接:网络问题的排查路径
连接超时和拒绝连接,是网络层异常里最常见的两种。它们看起来很像,但原因完全不同。
连接超时的意思是:客户端发起了连接请求,但在规定时间内没有收到服务端的响应。这通常意味着:服务端太忙处理不过来、网络路径上有丢包、或者防火墙静默丢弃了数据包。排查连接超时,我习惯从三个方向入手:第一,检查服务端的负载和响应时间,看是不是服务端本身处理慢;第二,用网络诊断工具检查客户端到服务端的网络质量,看有没有丢包或延迟;第三,检查中间的网络设备,看有没有做限速或丢包策略。
拒绝连接的意思是:客户端发起了连接请求,但服务端明确拒绝了。这通常意味着:服务端没有在监听对应的端口、服务端已经达到最大连接数、或者防火墙明确拒绝了该连接。排查拒绝连接,第一步是确认服务端进程是否在运行、端口是否在监听;第二步是检查服务端的连接数限制和资源使用情况;第三步是检查防火墙规则。
这两种异常在日志里的表现也不一样。连接超时通常会在等待一段时间后才报错,而拒绝连接通常是立即报错。通过报错的时间特征,可以快速区分两者。
提示:排查网络异常时,不要只盯着应用日志。操作系统层面的网络统计信息(比如连接数、重传率、丢包率)往往能提供更直接的线索。
3.4 并发竞争与死锁:最难复现的异常怎么抓
并发竞争和死锁,是所有异常类型里最难排查的。因为它们往往在特定时序下才会触发,难以稳定复现。但难排查不代表没法排查,关键是要掌握正确的提取方法。
并发竞争的典型表现是:数据不一致、结果时对时错、偶发的空指针或越界。排查并发竞争,核心是找到“共享资源”和“访问时序”。我常用的方法是:先梳理代码里所有被多个线程访问的共享变量和资源;然后检查这些资源的访问有没有加锁保护;最后分析可能的交错执行顺序,看哪种顺序会导致异常。这个过程需要一定的想象力,但一旦找到竞争点,修复通常很简单。
死锁的典型表现是:程序卡住不动,CPU占用率为零,线程转储里能看到多个线程互相等待对方持有的锁。排查死锁,最直接的方法是抓取线程转储,然后分析锁的持有和等待关系。大多数运行时环境都提供了自动检测死锁的工具,能直接告诉你哪些线程陷入了死锁。但工具只能告诉你“死锁发生了”,不能告诉你“为什么会发生”。要找到根因,还需要检查代码里的加锁顺序,看有没有不一致的地方。
我处理并发问题时有一个原则:不要试图通过“加更多锁”来解决竞争。锁越多,死锁的风险越大,性能越差。正确的做法是:尽量减少共享状态,用不可变对象、线程本地变量、消息传递等机制来替代共享内存。如果必须共享,确保所有线程以相同的顺序获取锁。
3.5 配置错误与依赖缺失:环境问题的隐蔽表现
配置错误和依赖缺失,是系统层异常里最容易被忽视的两类。因为它们往往不表现为直接的报错,而是表现为“功能不正常”或“启动失败”。
配置错误的典型表现是:应用启动时报错说某个配置项无效,或者应用启动成功但行为不符合预期。排查配置错误,第一步是确认配置文件的加载顺序和优先级——很多系统支持多级配置覆盖,最终生效的配置可能不是你修改的那个文件。第二步是检查配置项的格式和类型,比如字符串有没有加引号、数字有没有超出范围、布尔值有没有写错。第三步是检查配置项之间的依赖关系,比如A配置项的值依赖于B配置项的值。
依赖缺失的典型表现是:应用启动时报错说找不到某个类或某个库,或者运行时报错说某个方法不存在。排查依赖缺失,第一步是确认依赖有没有被正确引入——检查依赖管理文件、检查打包结果里有没有包含该依赖。第二步是检查依赖的版本是否匹配——不同版本的库可能有不同的API,版本不匹配会导致方法找不到或行为异常。第三步是检查依赖的传递性——A依赖B,B依赖C,但C没有被正确引入,就会导致运行时找不到C。
这两类异常的共同特点是:它们通常发生在部署或环境变更之后。所以,如果你刚做完发布或配置变更就遇到了异常,优先检查配置和依赖。
4. 从日志到根因:异常信息提取的实操方法
4.1 日志分级与过滤:先看什么,后看什么
日志是排查异常最重要的信息来源,但日志太多也是灾难。一个中等规模的系统,一天产生几个GB的日志是常态。如果你从头到尾翻日志,那基本等于大海捞针。所以,第一项技能就是:快速过滤出有用的日志。
我的习惯是:先看错误级别(ERROR)的日志,因为这是最直接的异常信号。但要注意,ERROR日志不一定都是根因,有些ERROR只是表象。比如一个连接超时的ERROR,根因可能是网络问题,也可能是服务端过载。所以看完ERROR之后,还要看警告级别(WARN)的日志,因为有些异常在早期只表现为警告。比如内存使用率持续上升、线程池队列开始积压、响应时间逐渐变长,这些都会先出现在WARN日志里。
接下来是信息级别(INFO)的日志,但不要全看,而是围绕异常发生的时间点,看前后几分钟的日志。重点看:异常发生前有没有特殊的操作(比如配置加载、连接建立、任务调度),异常发生后有没有连锁反应(比如重试、降级、熔断)。这些上下文信息,往往比异常本身更有价值。
调试级别(DEBUG)的日志通常只在排查特定问题时才打开,因为量太大。但如果问题难以复现,可以临时打开DEBUG日志,然后等待问题再次出现。打开DEBUG日志时要注意:只打开相关模块的DEBUG,不要全局打开,否则日志量会爆炸。
注意:不同日志框架的级别名称可能不同,但思路是一样的。关键是建立一个从高到低的过滤顺序,先看最严重的,再看次严重的,最后看上下文。
4.2 堆栈阅读技巧:从下往上还是从上往下
堆栈阅读是异常排查的基本功,但很多人读堆栈的方式是错的。最常见的错误是:只看最上面一行,然后就去搜索解决方案。这种做法在简单场景下能解决问题,但在复杂场景下会误导你。
我读堆栈的习惯是:先从下往上读,找到异常的“起源点”。堆栈的最下面通常是线程的入口方法,往上依次是调用链。异常的起源点往往在堆栈的中下部,也就是第一个抛出异常的方法。找到起源点后,再从上往下读,看异常是怎么传播的,经过了哪些中间层,最终在哪里被捕获或导致崩溃。
举个例子:一个空指针异常的堆栈,最上面可能是某个框架的拦截器,中间是你的业务代码,最下面是线程池的任务执行方法。如果你只看最上面,可能会以为是框架的问题;但如果你从下往上看,会发现起源点是你的业务代码里某个方法返回了空值,然后被框架拦截器处理时抛出了空指针。这样你就能准确定位到自己的代码。
还有一个技巧:关注堆栈里的“Caused by”部分。很多异常是包装异常,外层异常只是说明“发生了什么”,内层异常才说明“为什么发生”。比如一个“服务调用失败”的异常,Caused by可能是“连接超时”,再Caused by可能是“路由不可达”。一层层往下看,才能找到最根本的原因。
4.3 关联分析:把散落的信息拼成完整画面
异常排查最难的环节,不是看单条日志或单个堆栈,而是把散落在不同地方的信息关联起来,拼成一个完整的画面。这个能力,我称之为“关联分析”。
关联分析的第一步是确定关联维度。最常用的维度是时间:把同一时间段内的所有异常、警告、变更记录放在一起看。其次是请求ID:如果系统支持全链路追踪,可以通过请求ID把一次请求经过的所有服务、所有日志串联起来。再次是资源ID:比如同一个数据库连接、同一个线程、同一个文件句柄相关的日志。
关联分析的第二步是寻找模式。比如:异常是不是只在特定时间段出现?是不是只在特定服务器上出现?是不是只在特定版本的代码上出现?是不是只在特定用户或特定请求参数下出现?找到模式,就找到了缩小排查范围的方向。
关联分析的第三步是验证假设。根据模式提出一个假设,比如“异常是因为数据库连接池不够用”,然后去验证:检查连接池的配置、检查连接池的使用率、检查有没有连接泄漏。如果验证结果支持假设,就继续深入;如果不支持,就调整假设重新验证。
这个过程听起来简单,但实际操作中需要耐心和细致。我见过很多排查失败的案例,不是因为技术不够,而是因为过早下结论,没有做充分的关联分析。
4.4 工具链的合理搭配:别用锤子找螺丝
排查异常的工具很多,但不同的工具适合不同的场景。用错工具,就像用锤子拧螺丝,费力不讨好。
对于代码层异常,最常用的工具是IDE的调试器和日志。调试器适合本地复现的问题,可以单步执行、查看变量、设置断点。日志适合生产环境的问题,因为生产环境通常不允许调试。
对于运行时层异常,最常用的工具是堆转储分析器和线程转储分析器。堆转储分析器可以告诉你内存里有哪些对象、谁在持有它们。线程转储分析器可以告诉你线程的状态、锁的持有情况、调用栈。
对于系统层异常,最常用的工具是系统监控命令。比如查看CPU使用率、内存使用率、磁盘IO、网络IO、文件句柄数、进程状态。这些命令能快速告诉你系统资源有没有瓶颈。
对于网络层异常,最常用的工具是抓包分析器和网络诊断命令。抓包分析器可以告诉你数据包有没有到达、有没有重传、有没有被拒绝。网络诊断命令可以告诉你路由路径、延迟、丢包率。
我自己的习惯是:先用手头的工具做初步判断,确定异常的大致范围,然后再选择对应的专业工具深入分析。不要一上来就用最复杂的工具,那样反而容易迷失方向。
5. 排查思路的实战复盘:几个典型场景的完整链路
5.1 场景一:服务突然无响应,但日志里没有明显错误
这是一个非常典型的场景:监控系统报警说服务响应时间飙升,但登录服务器看日志,没有ERROR,没有WARN,只有正常的INFO日志。这种情况下,很多人会懵,因为“没有错误”本身就是最大的错误。
我的排查链路是这样的:第一步,确认服务进程是否还在运行,端口是否还在监听。如果进程不在了,那就是崩溃型异常,去看系统日志和退出码。如果进程还在,但端口不响应,那就是降级型异常,继续往下查。
第二步,检查系统资源。CPU使用率是不是满了?内存是不是快耗尽了?磁盘IO是不是打满了?网络带宽是不是跑满了?这些资源瓶颈都会导致服务无响应,但不会在应用日志里留下错误。
第三步,检查线程状态。抓取线程转储,看线程都在干什么。如果大量线程处于BLOCKED或WAITING状态,说明有锁竞争或资源等待。如果大量线程处于RUNNABLE状态但CPU使用率不高,说明有IO等待。
第四步,检查依赖服务。服务本身可能没问题,但它依赖的数据库、缓存、消息队列出了问题,导致请求堆积。这种情况下,应用日志里可能只有“请求处理超时”之类的模糊信息,真正的问题在依赖服务那边。
这个场景的关键是:不要只盯着应用日志。系统资源、线程状态、依赖服务,都是需要检查的方向。
5.2 场景二:间歇性超时,每次都是不同的接口
间歇性超时是最难排查的异常之一,尤其是当每次超时的接口都不一样时,很容易让人以为是“随机故障”。但实际上,随机故障往往有固定的根因,只是触发条件比较隐蔽。
我的排查链路是这样的:第一步,收集所有超时发生的时间点和接口,看有没有时间规律。比如是不是集中在某个时间段?是不是和某个定时任务重合?是不是和流量高峰重合?
第二步,检查超时发生时的系统状态。CPU、内存、网络、磁盘,有没有异常波动?如果有,那就是资源竞争导致的超时。
第三步,检查超时发生时的依赖服务状态。数据库的慢查询、缓存的命中率、消息队列的积压量,有没有异常?如果有,那就是依赖服务导致的超时。
第四步,检查超时发生时的代码路径。虽然每次超时的接口不同,但它们可能共享某些底层组件,比如同一个连接池、同一个线程池、同一个缓存客户端。如果这些共享组件有问题,就会导致不同接口同时出现超时。
这个场景的关键是:不要被“不同接口”迷惑,要找到它们之间的共同点。共同点往往就是根因所在。
5.3 场景三:发布后出现异常,但回滚后问题依旧
发布后出现异常,第一反应通常是回滚。但有时候回滚后问题依旧,这就说明问题不是发布引起的,或者发布只是触发了问题,而不是问题的根源。
我的排查链路是这样的:第一步,确认回滚是否彻底。有时候回滚只回滚了应用代码,但没有回滚配置、没有回滚数据库变更、没有清理缓存。这些残留的变更可能继续导致问题。
第二步,检查发布期间有没有其他变更。比如运维可能同时调整了网络策略、更新了证书、修改了负载均衡配置。这些变更不会随应用回滚而撤销。
第三步,检查问题是不是被发布“暴露”出来的。比如系统里本来就有内存泄漏,但之前流量小没暴露,发布后流量增大就暴露了。这种情况下,回滚不能解决问题,必须修复内存泄漏。
第四步,检查问题是不是和发布时间无关,只是巧合。有时候问题早就存在,只是刚好在发布后被发现。这种情况下,需要重新梳理问题的时间线,找到真正的起点。
这个场景的关键是:不要假设“回滚就能解决一切”。回滚只是排查的一个步骤,不是终点。
5.4 场景四:数据库连接池耗尽,但连接数并不高
数据库连接池耗尽是一个常见的异常,但有时候你会发现:连接池的配置是100,当前活跃连接只有50,但应用还是报“无法获取连接”。这种情况下,问题不在连接数,而在连接的获取和释放逻辑。
我的排查链路是这样的:第一步,检查有没有连接泄漏。连接泄漏是指连接被借出后没有归还,导致连接池里的可用连接越来越少。检查方法是:看连接池的活跃连接数是不是持续上升,即使请求量没有增加。
第二步,检查连接的获取超时时间。如果获取连接的超时时间设置得太短,而数据库的响应时间偶尔较长,就会导致获取连接失败。这种情况下,连接池里其实有可用连接,只是应用等不及。
第三步,检查连接的有效性检测。有些连接池会定期检测连接的有效性,如果检测逻辑有问题,可能会误杀有效连接,导致连接池里的连接数虚高但实际可用连接很少。
第四步,检查数据库端的连接限制。有时候数据库本身有最大连接数限制,即使应用端的连接池配置很大,数据库端也只能接受有限的连接。这种情况下,需要调整数据库端的配置。
这个场景的关键是:连接池耗尽不一定是因为连接不够用,也可能是连接的管理逻辑有问题。
6. 把异常变成资产:建立自己的排查知识库
排查异常这件事,最怕的不是遇到新问题,而是遇到老问题却想不起来上次是怎么解决的。我见过很多开发者,同一个类型的异常反复遇到,每次都要重新排查一遍,浪费大量时间。解决这个问题的办法只有一个:建立自己的排查知识库。
我的知识库结构很简单,就三个部分:异常特征、排查路径、根因和修复。每次解决一个异常后,花五分钟把这三部分记录下来。异常特征包括:报错信息的关键词、发生的层级、表现类型。排查路径包括:我用了哪些工具、看了哪些日志、做了哪些验证。根因和修复包括:最终定位到的原因、采取的修复措施、后续的预防方案。
这个知识库不需要多复杂,用普通的笔记软件就行。关键是要坚持记录,并且定期回顾。我自己的习惯是:每季度回顾一次,把相似的异常合并,把过时的信息更新,把新的经验补充进去。这样下来,知识库会越来越精炼,排查效率也会越来越高。
还有一个技巧:把常见的异常排查步骤做成检查清单。比如遇到“服务无响应”,就按照清单逐项检查:进程状态、端口监听、系统资源、线程状态、依赖服务。这样做的好处是:即使你很累或者很忙,也不会漏掉关键步骤。检查清单不需要很详细,每个步骤一句话就行,关键是覆盖全面。
最后说一点个人体会:异常排查的能力,本质上是对系统的理解深度。你对系统越了解,排查起来就越快。所以,平时多花时间了解系统的架构、依赖、配置、监控,比临时抱佛脚有用得多。异常不是敌人,而是帮你发现系统薄弱环节的信号。每一次成功的排查,都是对系统理解的一次加深。