十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

线上故障排查:如何揪出隐藏真凶?从连接池到线程池的根因分析

线上故障排查:如何揪出隐藏真凶?从连接池到线程池的根因分析 排查线上问题时我们经常说“找到根因了”但很多时候这个“根因”只是第一嫌疑对象真正的问题往往藏在另一层。先共享一段让我印象很深的经历有一次接口偶发超时监控里数据库慢查询一塌糊涂于是团队把索引调了一遍又一遍SQL 改写了好几版结果收效甚微。最后才发现数据库慢只是“表象”真正的凶手是应用侧连接池参数不合理大量线程在排队等连接把整个请求链路拖垮了。那一刻的感受正是什么嘛凶手竟然不是这个人。这篇文章不打算只讲一个孤立的案例而是把这种“排查方向被带偏”的场景提炼成一套方法论。我会通过三个真实常见的线上问题案例拆解排查思路、工具使用、验证手段和修复方案再总结一些防止误判的工程建议。无论是后端开发、运维工程师还是正在学习分布式系统排查的同学这篇文章应该都能给你一些启发。1. 背景Bug 追凶现场怀疑对象不一定是真凶1.1 什么是“真凶误判”在软件开发中我们每天都要面对各种异常现象请求超时、CPU 飙高、内存溢出、数据不一致。面对这些现象第一反应往往是找出最可疑的那个环节就像侦探看到一个案发现场最先注意到最显眼的线索一样。但显眼的线索不一定导向真凶甚至可能是凶手故意留下的烟雾弹。典型的“真凶误判”过程是这样的发生线上事故监控系统弹出一堆告警。告警中最刺眼的指标比如慢 SQL、某个接口错误率暴涨变成了首要怀疑对象。顺着这个方向深挖做了很多优化但问题依旧。偶然间换个角度重新排查才发现最初怀疑的问题其实是另一个底层故障的结果而不是原因。这类问题之所以常见是因为分布式系统里模块多、链路长故障会在组件之间传导。一个组件的异常会以另一种形式体现在下游组件上所以在排查时容易“顺着结果找原因”结果找到的只是中间环节。1.2 为什么容易怀疑错对象从我的经验来看怀疑错对象的原因主要有三类第一类是监控数据带来误导。很多监控平台默认把数据库慢查询、接口错误率这类指标放在最显眼的位置这些指标最容易触发告警也最容易让人先入为主地认为问题出在那里。第二类是“经验主义”带来的误判。比如某个服务之前因为 SQL 慢出过问题再出现延迟时团队成员会条件反射式地先去查 SQL忽略了这次问题的触发场景已经发生了变化。第三类是问题表现与根因在时间上存在错位。举个例子当应用线程池耗尽后请求全部积压后续所有的新请求都在排队此时数据库的活跃连接数可能因为长时间占用而飙升慢查询也随之出现。如果不看全局链路只盯着数据库就会把处理时间线搞反。1.3 用“侦探思维”替代“直觉判断”破案讲究动机、线索、时间线、证据闭环排障其实也是一样的。我们不能只凭直觉锁定嫌疑人而应该先梳理完整的调用链路明确问题的“传播路径”。列出所有可能的嫌疑人按可能性排序而不是按“显眼程度”排序。每个嫌疑人必须用数据或实验来验证不能靠猜测。最后找到“第一现场”也就是最基础的故障源而不是中途被影响的组件。这套思路我把它称为“系统追凶思维”。下面三个案例都是这种思维的实战演练。2. 排查基础设施先把“案发现场”保护起来在没有现场保护的情况下谈分析都是空中楼阁。线上问题排查也是如此。如果没有监控、日志、链路追踪这些基础设施哪怕我们再懂方法论也无从下手。2.1 必备的排查工具清单做一次高质量的问题排查通常需要以下四类信息信息类型典型工具作用系统指标top、vmstat、free、iostat查看 CPU、内存、磁盘、负载应用日志ELK、Loki、阿里云 SLS聚合检索业务日志、异常堆栈链路追踪SkyWalking、Zipkin、Jaeger还原一次请求的完整调用链数据库监控MySQL 慢查询日志、Performance Schema查看连接数、慢 SQL、锁等待在实际工作中建议尽量把这些能力在测试环境也跑起来不要只在生产环境运维侧使用。开发人员如果能自己随时拉取链路数据和指标排查效率会高非常多。2.2 建立统一的排障入口很多团队的问题排查效率低不是因为信息不够而是信息太分散。应用日志在一套系统、数据库监控在另一套系统、链路追踪又需要单独登录。一旦发生问题光收集信息就要花掉很多时间。比较好的做法是搭建一个统一的可观测平台把日志、指标、链路三部分数据做关联。这样在排查时可以很快做时间轴对齐看出哪个组件先出现异常哪个组件是被带崩的。本文的案例演示中我会使用常见的命令行工具和开源监控思路来操作。如果你所在团队已经有商业化可观测产品按平台能力替换即可底层排查逻辑是一样的。2.3 排障前先记录现场在实际操作中我强烈建议在做任何变更之前先记录现场数据。这里的“现场”包括当前时间点和持续时长异常接口、异常实例的范围CPU、内存、磁盘、网络的实时状态连接池、线程池的关键指标日志中的首条异常时间。这一步很容易被忽略尤其是在紧急恢复的场景下。但如果没有现场快照问题恢复之后很多数据就丢失了只能等下一次复现才能继续分析。遇到偶发性问题错过现场基本等于错过真相。3. 案例一数据库很慢真正凶手是连接池3.1 案件现象这是一个非常典型的 Spring Boot 应用业务高峰期时接口 P99 延迟从 80ms 飙升到 1.8s同时监控面板上数据库慢查询数量明显增加DBA 很紧张地开始排查 SQL发现几条 SQL 平时执行都在 30ms 以内当时却跑到了 300ms 以上。从表面上看所有证据都指向“数据库出问题了”数据库 CPU 升高、慢查询变多、接口变慢。于是团队做了几件常规操作给表加索引改慢 SQL 写法排查锁等待。但奇怪的是SQL 优化之后慢查询数量和接口延迟并没有明显好转。3.2 换条线索分析连接池视角之后我们不再盯着 SQL 本身而是把视角从数据库实例转移到应用与数据库之间。这里查的是应用侧的数据库连接池状态Spring Boot 中默认的连接池是 HikariCP关键指标包括当前活跃连接数Active Connections当前空闲连接数Idle Connections等待获取连接的线程数Pending Threads连接获取超时次数Connection Timeout Count。如果是在 Spring Boot 中使用 Actuator可以通过下面的配置暴露连接池指标management.endpoints.web.exposure.includehealth,metrics,prometheus management.endpoint.metrics.enabledtrue management.metrics.enable.jdbctrue然后访问http://localhost:8080/actuator/metrics/hikaricp.connections.active等端点查看实时数据。在真实案例中我们发现连接池的“等待获取连接”线程数非常高大量线程在getConnection()上阻塞。这就意味着应用虽然有慢查询但慢查询并不是根因真正的问题是连接池被耗尽导致每个请求都在排队等连接。3.3 真凶浮出水面连接池耗尽那连接池为什么会被耗尽呢顺着这个问题继续追查发现业务代码中有一个外部接口调用逻辑存在严重的性能隐患。为了简化这里用一个模拟代码来说明问题// 文件路径src/main/java/com/example/demo/service/OrderService.java Service public class OrderService { private final JdbcTemplate jdbcTemplate; public OrderService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Transactional public void processOrder(String orderId) { // 模拟数据库查询获取订单信息 Order order jdbcTemplate.queryForObject( SELECT * FROM orders WHERE id ?, new Object[]{orderId}, (rs, rowNum) - new Order(rs.getString(id), rs.getString(status)) ); // 模拟调用外部仓储系统 String warehouseStatus callWarehouseApi(order.getProductId()); order.setWarehouseStatus(warehouseStatus); // 更新订单状态 jdbcTemplate.update( UPDATE orders SET warehouse_status ? WHERE id ?, warehouseStatus, orderId ); } private String callWarehouseApi(String productId) { // 这里假设外部API正常情况下耗时100ms但高峰期可能达到2s try { Thread.sleep(1500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return SUCCESS; } }这段代码的问题在于Transactional开启事务后数据库连接会在整个方法执行期间被持有包括中间那 1.5 秒的外部 API 调用。而 HikariCP 默认的maximum-pool-size通常只有 10也就是同时只能有 10 个请求在执行这种操作。高峰期一旦并发超过 10后面的请求就会进入等待队列等待获取数据库连接。连接获取越来越慢事务处理越来越慢连接的占用时间越来越长形成恶性循环。数据库因此看到大量 SQL 在排队执行表现为慢查询但实际上 SQL 本身没有性能问题。3.4 修复方案缩小事务、扩大连接池、超时保护修复方案分两步走。第一步先给外部调用加上超时和重试机制避免长时间阻塞事务。如果外部 API 超时会话连接也被释放不会一直占着。第二步缩事务范围。把外部调用从Transactional方法中移出去先查数据再调用外部 API最后再开启一个新事务执行更新// 文件路径src/main/java/com/example/demo/service/OrderService.java Service public class OrderService { private final JdbcTemplate jdbcTemplate; public OrderService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } public void processOrder(String orderId) { // 第一步无事务查询订单信息 Order order jdbcTemplate.queryForObject( SELECT * FROM orders WHERE id ?, new Object[]{orderId}, (rs, rowNum) - new Order(rs.getString(id), rs.getString(status)) ); // 第二步外部API调用不占用数据库连接 String warehouseStatus callWarehouseApi(order.getProductId()); // 第三步短事务更新订单状态 updateOrderStatus(orderId, warehouseStatus); } Transactional public void updateOrderStatus(String orderId, String warehouseStatus) { jdbcTemplate.update( UPDATE orders SET warehouse_status ? WHERE id ?, warehouseStatus, orderId ); } private String callWarehouseApi(String productId) { // 添加超时控制 try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return SUCCESS; } }另外针对外部 API 调用建议使用RestTemplate或HttpClient时显式配置连接超时和读取超时// 文件路径src/main/java/com/example/demo/config/HttpClientConfig.java Configuration public class HttpClientConfig { Bean public RestTemplate restTemplate() { HttpClient httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(2)) .build(); RestTemplate restTemplate new RestTemplate(); restTemplate.setRequestFactory(new JdkClientHttpRequestFactory(httpClient)); return restTemplate; } }同时也可以适度调整 HikariCP 参数但不能盲目调大否则数据库本身的连接数会超上限。更合理的做法是spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.connection-timeout3000 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.idle-timeout600000这里connection-timeout3000的作用是当连接池没有空闲连接时应用程序最多等待 3 秒超时直接报错避免请求无限堆积。对于一个有降级预案的系统来说快速失败比无限等待更健康。3.5 案件小结这个案例最有意思的地方在于数据库慢查询其实是连接池耗尽的结果而不是原因。所有人在第一轮排查时都把注意力放在 SQL 上属于典型的“被结果误导”。如果当时没有看连接池指标这个问题的定位周期会非常长。4. 案例二接口偶发超时真正凶手是线程池4.1 案件现象第二个案例是一个内部管理系统最典型的现象是某一个查询接口没有任何代码变更每天下午会出现几分钟的偶发超时。由于超时集中在每天固定时间一开始团队怀疑是不是有定时任务在跑导致资源争抢。排查过程先从定时任务出发查看了所有 cron 表达式没有发现集中在那个时间点的任务。于是又把目标转向数据库 binlog 备份、慢查询等例行任务仍然没有收获。4.2 从线程池视角找突破口这个系统的超时只影响某一个接口而其他接口响应正常。于是我们把关注点缩小到该接口的处理线程上。通过查看应用线程池的运行情况发现 Tomcat 的工作线程池在超时期间几乎满负荷大量线程处于RUNNABLE或WAITING状态。这里就需要解释一个非常常见的误区接口慢不一定就是下游慢也可能是线程资源被其他任务占用了。Tomcat 默认工作线程池数量一般在 200 左右如果某些任务执行时间很长比如几秒钟就会快速占满整个线程池。通过线程 dump我们看到了大量业务线程阻塞在一个文件上传解析的逻辑中。进一步查看代码发现这个接口会先调用一个内部文件导入功能而文件导入的代码里不小心使用了同步的FileInputStream读取大文件并且没有做分片处理。这个导入逻辑原本是由一个异步任务执行的但后来在某次迭代中有人把调用方式从异步改成了同步导致一个本来应该脱离请求链路的任务直接跑在 Tomcat 工作线程里。在文件体积大的时候单个请求占住线程长达十几秒线程池很快被打满。4.3 模拟复现与验证为了验证这个判断可以写一个简单的测试接口模拟耗时操作占用线程// 文件路径src/main/java/com/example/demo/controller/DemoController.java RestController public class DemoController { GetMapping(/slow) public String slow() { try { // 模拟长时间占用线程的操作 Thread.sleep(10_000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return slow; } }然后使用压测工具比如 Apache Bench 或 wrk 发送并发请求同时观察线程池状态wrk -t4 -c100 -d30s http://localhost:8080/slow压测期间其他接口的响应也会明显变慢。这说明只要工作线程被长期占满即使另一个接口逻辑本身很快也无法获得执行机会。4.4 修复方案恢复异步、限制并发、线程池隔离定位到问题之后修复方向就很清楚了。第一步把文件导入操作重新改成异步执行不占用 Tomcat 工作线程。如果使用的是 Spring可以直接使用Async前提是开启异步支持// 文件路径src/main/java/com/example/demo/config/AsyncConfig.java Configuration EnableAsync public class AsyncConfig { Bean(name importExecutor) public Executor importExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(8); executor.setQueueCapacity(100); executor.setThreadNamePrefix(import-); executor.initialize(); return executor; } }然后在导入服务中指定这个执行器// 文件路径src/main/java/com/example/demo/service/ImportService.java Service public class ImportService { Async(importExecutor) public void importFileAsync(String filePath) { // 异步执行文件导入逻辑 } }第二步如果某些场景必须同步等待导入结果那么至少要做线程池隔离避免所有接口共享 Tomcat 工作线程。可以使用单独的ExecutorService来执行导入任务并设置队列大小和拒绝策略。第三步为 Tomcat 线程池增加监控。Spring Boot 2.x 之后可以通过 Actuator 暴露 Tomcat 线程池指标management.endpoint.health.show-detailsalways management.metrics.enable.tomcattrue然后监控tomcat.threads.busy和tomcat.threads.current两个指标。如果 busy 长期接近 current说明线程资源已经非常紧张需要及时处理。4.5 案件小结这个案例的迷惑性在于“定时任务”这个时间线索。发现问题集中在某个时间段后很容易把注意力放在“这个时间段内发生了什么”却忘记了真正的问题是线程池被打满。线程调度是操作系统的底层行为不依赖具体的时间点任何突发的高耗时操作都可能引发这种超时。5. 案例三内存一直涨真正凶手是日志异步队列5.1 案件现象第三个案例是一个 Java 服务运行一段时间后内存使用率持续上升最终触发 OOM容器被重启。由于服务重启后内存又恢复正常因此不能简单地通过堆快照来观察问题因为重启后堆已经被回收了。第一次排查时怀疑是业务代码中存在内存泄漏比如静态集合不断添加数据、缓存没有过期时间等。于是让业务团队复查代码连续查了两轮没有发现明显的泄漏点。5.2 从日志框架找突破口后来在 OOM 发生前的 GC 日志中发现了一个非常有意思的现象内存使用率在某个时间点急速攀升但业务请求量并没有明显变化说明有额外的内存堆积源。顺着时间点继续查发现同时段的一个特征是错误日志数量暴增。这里的关键角色是 Logback 的异步日志。很多项目为了减少日志 I/O 对业务线程的阻塞会使用AsyncAppender它内部有一个阻塞队列来缓存日志事件再由后台线程异步写入磁盘。当写入磁盘的速度赶不上入队的速度时队列就会持续积压占用大量堆内存。如果因为磁盘故障、日志文件权限问题或者日志量突增导致写入变慢AsyncAppender队列中的日志事件会一直堆积内存随之上升直到 OOM。5.3 定位方法查看堆内存中的对象分布要确认是不是日志队列堆积可以通过堆转储来分析。在服务启动时加上参数OOM 时自动生成堆转储文件java -Xmx1g -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/opt/logs/heap.hprof -jar app.jarOOM 后使用 Eclipse MAT 或 JProfile 打开堆转储文件查看Dominator Tree重点观察是否存在大量ch.qos.logback.core.spi.LoggingEvent对象。如果看到成百上千上万个LoggingEvent实例且它们被一个AsyncAppender的队列引用那么根因基本就确认了。5.4 修复方案队列容量、丢弃策略、磁盘空间告警这个问题可以从三个层面来修第一层为AsyncAppender设置合理的队列容量。默认情况下队列容量是 256如果日志突发量很大可以适当调大到 1024 或 2048但不能无限调大否则内存风险更高。!-- 文件路径src/main/resources/logback-spring.xml -- configuration appender nameASYNC classch.qos.logback.core.AsyncAppender queueSize1024/queueSize discardingThreshold0/discardingThreshold neverBlocktrue/neverBlock appender-ref refCONSOLE/ /appender appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender root levelINFO appender-ref refASYNC/ /root /configuration这里有两个参数值得重点解释neverBlocktrue当队列满时不阻塞业务线程直接丢弃日志。对于日志系统来说丢失少量日志通常比拖垮业务系统更容易接受。discardingThreshold0禁用“当队列剩余容量低于阈值时丢弃 INFO/DEBUG 日志”的默认策略。正常情况下建议保持默认值如果是核心链路日志可以按需调整。第二层为日志目录配置磁盘空间监控。日志写入变慢的最大物理原因是磁盘空间不足或磁盘 I/O 饱和。可以在运维平台为日志磁盘设置 85% 报警阈值同时配置日志清理策略。第三层降低无意义的日志输出。很多系统在异常时会在循环里打印大量堆栈日志例如for (Item item : itemList) { try { process(item); } catch (Exception e) { log.error(process error, itemId{}, item.getId(), e); } }如果itemList有十万条数据且有大量异常这个循环会一次性生成十万条异常日志。这种场景应该改为失败统计 批量错误摘要避免日志刷屏。5.5 案件小结这个案例的隐蔽性非常强。应用内存上升通常不会第一时间怀疑日志框架因为日志框架在绝大多数情况下都是“可靠”的。但只要出现日志写入慢、队列积压它就可能变成内存事故的源头。这也提醒我们所有持有序列缓冲的组件都值得在排障时多看一眼。6. 排查方法论为什么你总会怀疑错对象6.1 从单点怀疑升级为链路分析通过上面的三个案例你应该能感受到一个共同点最初的“凶手”都不是真正的问题源而只是链条上被波及的一环。要避免这种误判最根本的方法是建立全链路排查意识。一个请求从客户端发出经过网关、应用服务、数据库、缓存、消息队列等多个节点任何一个节点出问题都可能导致下游表现为“超时”或“错误”。如果我们只看最后一个环节很容易判错案。所以每次排查前先问自己三个问题这个现象是从哪个节点开始出现的从首条异常时间点到问题爆发时间点之间性能指标是如何变化的当前观察到的指标是“原因”还是“结果”6.2 用时间轴和依赖图还原现场推荐大家在排查时在文档或白板上画一个简单的时间轴和依赖图。不用特别复杂只需要两列时间点事件14:00:00数据库 CPU 开始上升14:00:10慢查询数量增加14:00:30应用接口 P99 明显上涨14:00:45系统告警触发如果真实数据中“数据库 CPU 开始上升”早于“应用接口 P99 上涨”那么数据库是源头应用是被影响方。反过来如果是应用接口先上涨数据库指标后上涨那数据库只是被拖累的。这个时间顺序往往比任何指标数值都重要。6.3 先提问再动手改很多同学在排查时其实还没有完全理解问题就开始尝试修复最常见的做法是重启服务、扩容机器、加索引。这些操作有时候能临时缓解症状但会让真正的根因隐藏得更深。正确的做法是在动手修改配置或代码之前先把“问题陈述”写清楚包括影响面哪些用户、哪些接口、哪些实例受影响时间窗口开始时间、持续时间、是否周期性异常特征错误码、延迟模式、日志关键字已排除项哪些可能性已经被数据排除。当你可以在纸上完整地复述问题并找到一条无法被推翻的证据链时再开始变更。否则所有变更都像是在盲猜。7. 常见误判场景与快速排查清单7.1 四个高频误判场景以下是日常开发中非常容易“判错案”的场景供大家参考误判场景表面现象容易被怀疑的原因真正的常见根因接口变慢数据库慢查询飙升SQL 执行效率低、缺索引连接池耗尽、事务过长、锁等待偶发超时单个接口超时率升高网络波动、下游 API 慢Tomcat 线程池被占满、线程阻塞内存上涨堆内存持续攀升业务代码内存泄漏日志异步队列积压、缓存无上限应用卡顿CPU 使用率高业务代码算法效率低GC 频繁、死循环、锁竞争激烈7.2 快速排查清单当你遇到线上问题时建议按这个顺序来收集信息而不是一上来就打开代码确认告警时间点记录首条异常日志时间查看 CPU、内存、磁盘、网络四项基础设施指标查看应用线程池、连接池、GC 状态通过链路追踪还原调用链最后才回到代码层面检查业务逻辑。这套清单的好处是它能先帮我们把问题框定在“基础设施”“应用资源”还是“业务逻辑”三层之一大幅缩小搜索范围。7.3 如何验证你找到的就是真凶验证真凶的唯一标准是修复后问题现象消失并且确认没有副作用。但要注意有些问题本身可能是间歇性的修复后恰好不再出现并不代表修复就是有效的。更严谨的做法是分三步验证第一步在测试环境复现问题保证问题可以稳定出现 第二步应用修复方案确认问题不再复现 第三步把修复方案回退一次确认问题再次出现然后再正式修复。第三点在实际工程中很难做到因为生产环境不允许随便回退。但至少在测试环境复现阶段应该做一次“对照组”实验避免把偶发现象当成修复成果。8. 最佳实践如何减少“误判凶手”的次数8.1 为关键资源建立水位线连接池、线程池、队列这些容易成为“隐藏凶手”的资源最好在监控面板上建立水位线。不要只监控最终的业务指标比如请求延迟、错误率还要监控资源消耗指标。流量永远会有高峰低谷资源指标也是如此。只有当资源使用率超过阈值时才告警否则大量正常波动会产生频繁告警反而让团队对告警麻木。8.2 缩短事务边界降低连接占用时间事务是数据库连接池的重要占有者。在 Spring 中Transactional默认在方法开始前开启连接在方法结束后释放。如果方法里包含了耗时较长的 RPC 调用、文件操作、消息发送等逻辑连接就会被白白占用。最佳实践是事务方法只做数据库操作外部调用放在事务之外事务内避免同步等待如果需要执行较长逻辑先查出主键提交事务后再处理最后再按主键更新。8.3 为核心线程池配置独立资源池在微服务架构中不同接口对资源的消耗是不均衡的。有的接口是纯数据库查询响应快有的接口会调用外部系统响应慢。如果所有接口共用 Tomcat 默认线程池那么慢接口会拖垮快接口。更合理的做法是将有耗时操作的重点业务配置独立的线程池进行线程池隔离。比如文件导入、批量发送、报表导出这类耗时操作一律使用自定义 executor不占用 Tomcat 工作线程。8.4 日志系统要设计降级能力日志系统看起来简单但一旦磁盘写满或 I/O 饱和整个应用都会受影响。建议为日志系统配置好降级能力AsyncAppender设置neverBlocktrue日志目录监控磁盘空间达到阈值自动清理限制单个日志文件的大小和保留时长对异常日志做频率限制避免循环刷屏。8.5 建立“排障复盘”文化每次事故处理完后团队应该做一次复盘。复盘的重点不是追责而是把“这次是怎么误判的”“为什么会被现象带偏”“下次如何更快定位”写成文档。这些积累下来的复盘文档是团队排障能力提升最宝贵的学习材料。很多问题之所以反复出现就是因为每次都在重新踩同一个坑没有把经验沉淀下来。9. 总结这三个案例来自不同的技术栈和不同的故障类型但它们都有一个共同点最初锁定的“那个人”都不是真凶。数据库慢查询只是连接池耗尽的结果接口偶发超时是因为工作线程被耗时任务占满内存持续上涨的源头是日志异步队列堆积。每一个看起来逻辑自洽的怀疑最后都输给了真实数据。如果你是一线开发可以从今天开始养成一个习惯遇到线上问题先不要急着改代码先花十分钟把时间轴、资源指标、调用链这三样东西拉出来看一眼。很多时候真凶不在你目光所及的地方它藏在另一个资源层级里。希望这篇文章能在你下次排障时给你多一个视角。当再次面对“什么嘛凶手竟然不是这个人”的情况时你手里已经有一套系统追凶的方法了。
返回列表