线上服务突然CPU飙高100%、接口超时频发、FullGC每隔几分钟一次、线程死锁导致服务卡死、出现异常却没有打印日志,重启服务后问题消失却找不到根因,下次还会复现?传统的jstack、jmap、jhat工具不仅操作繁琐,还需要重启服务、无法热修改代码、线上环境权限受限,根本无法应对分钟级的线上故障。Arthas作为阿里巴巴开源的Java诊断利器,无需重启服务、无需修改代码、全场景覆盖JVM故障排查,是每个Java工程师必备的线上排障神器。本文从高阶核心用法、全链路定位流程、真实线上实战三个维度,带你彻底掌握Arthas,实现线上JVM故障从被动救火到主动定位,从重启解决到根因修复。
Arthas核心工作原理与前置准备
核心工作原理
Arthas的核心是利用JVM的Instrumentation API,在目标JVM进程启动后动态挂载Agent,通过字节码增强技术修改已加载类的字节码,实现方法执行监控、入参出参捕获、类热替换等能力,全程无需停止目标进程,无需修改业务代码,对业务的侵入性极低。
快速安装与启动
使用官方一键安装脚本,直接在目标服务器执行:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
执行后会列出当前服务器所有运行的Java进程,输入目标进程对应的序号,即可完成attach,进入Arthas交互控制台。
项目环境依赖
本文所有实例基于以下环境构建:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>com.jam</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Arthas Troubleshooting</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.1.0-jre</guava.version>
<springdoc.version>2.5.0</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
基础表结构
实例使用MySQL 8.0,表结构如下:
CREATE TABLE `t_order` (
`order_id` varchar(64) NOT NULL COMMENT '订单ID',
`user_id` varchar(64) NOT NULL COMMENT '用户ID',
`amount` decimal(10,2) NOT NULL COMMENT '订单金额',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态 0-待支付 1-已支付 2-已取消',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`order_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表';
核心业务代码
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体
* @author ken
* @date 2026-03-16
*/
@Data
@TableName("t_order")
@Schema(description = "订单实体")
public class Order {
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "订单ID")
private String orderId;
@Schema(description = "用户ID")
private String userId;
@Schema(description = "订单金额")
private BigDecimal amount;
@Schema(description = "订单状态")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.Order;
import org.apache.ibatis.annotations.Mapper;
/**
* 订单Mapper接口
* @author ken
* @date 2026-03-16
*/
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.Order;
/**
* 订单服务接口
* @author ken
* @date 2026-03-16
*/
public interface OrderService extends IService<Order> {
/**
* 根据订单ID查询订单
* @param orderId 订单ID
* @return 订单实体
*/
Order getOrderById(String orderId);
}
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.Order;
import com.jam.demo.mapper.OrderMapper;
import com.jam.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 订单服务实现类
* @author ken
* @date 2026-03-16
*/
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Override
public Order getOrderById(String orderId) {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Order::getOrderId, orderId);
return this.getOne(queryWrapper);
}
}
package com.jam.demo.controller;
import com.jam.demo.entity.Order;
import com.jam.demo.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
/**
* 订单控制器
* @author ken
* @date 2026-03-16
*/
@Slf4j
@RestController
@RequestMapping("/order")
@Tag(name = "订单管理", description = "订单相关接口")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 查询订单详情
* @param orderId 订单ID
* @return 订单详情
*/
@GetMapping("/detail")
@Operation(summary = "查询订单详情", description = "根据订单ID查询订单详情")
public Order getOrderDetail(@RequestParam String orderId) {
log.info("开始查询订单详情,订单ID:{}", orderId);
Order order = orderService.getOrderById(orderId);
log.info("查询订单详情完成,订单ID:{}", orderId);
return order;
}
}
package com.jam.demo.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 死锁复现控制器
* @author ken
* @date 2026-03-16
*/
@Slf4j
@RestController
@RequestMapping("/deadlock")
@Tag(name = "死锁复现", description = "死锁复现接口")
public class DeadlockController {
private static final Object LOCK_A = new Object();
private static final Object LOCK_B = new Object();
/**
* 触发死锁
*/
@GetMapping("/trigger")
@Operation(summary = "触发死锁", description = "触发线程死锁,用于Arthas死锁检测演示")
public String triggerDeadlock() {
log.info("开始触发死锁");
Thread thread1 = new Thread(() -> {
synchronized (LOCK_A) {
log.info("线程1持有LOCK_A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程1中断", e);
}
synchronized (LOCK_B) {
log.info("线程1持有LOCK_B");
}
}
}, "Thread-0");
Thread thread2 = new Thread(() -> {
synchronized (LOCK_B) {
log.info("线程2持有LOCK_B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程2中断", e);
}
synchronized (LOCK_A) {
log.info("线程2持有LOCK_A");
}
}
}, "Thread-1");
thread1.start();
thread2.start();
log.info("死锁触发完成");
return "死锁已触发";
}
}
Arthas高阶核心用法
一、热代码替换:线上bug秒修复,无需重启服务
热替换是Arthas最核心的能力之一,支持在线修改已加载类的方法体,无需重启服务,分钟级修复线上bug。这里明确区分两个易混淆的核心命令:
- retransform:基于Arthas字节码增强框架实现,支持叠加增强,不会重置之前的监控增强,仅支持修改方法体,不支持新增字段/方法/修改方法签名,是线上热修复的首选
- redefine:直接加载外部编译好的class文件替换JVM已加载类,会重置所有Arthas之前做的增强,适用场景为完全替换类的字节码
完整热修复实战
线上OrderController的getOrderDetail方法存在bug,传入空orderId会触发异常,无需重启服务,通过以下步骤修复:
- 反编译获取源代码
jad --source-only com.jam.demo.controller.OrderController > /tmp/OrderController.java
- 修改源代码修复bug编辑/tmp/OrderController.java,在方法内添加参数校验逻辑:
@GetMapping("/detail")
@Operation(summary = "查询订单详情", description = "根据订单ID查询订单详情")
public Order getOrderDetail(@RequestParam String orderId) {
log.info("开始查询订单详情,订单ID:{}", orderId);
if (!StringUtils.hasText(orderId)) {
log.error("订单ID不能为空");
throw new IllegalArgumentException("订单ID不能为空");
}
Order order = orderService.getOrderById(orderId);
log.info("查询订单详情完成,订单ID:{}", orderId);
return order;
}
- 获取类加载器Hash值
sc -d com.jam.demo.controller.OrderController | grep classLoaderHash
- 编译修复后的Java文件将上一步获取的hash值替换到-c参数后:
mc -c 1be6f5c0 /tmp/OrderController.java -d /tmp
- 热加载修复后的Class文件
retransform /tmp/com/jam/demo/controller/OrderController.class
- 验证修复结果调用/order/detail?orderId=接口,会返回规范的参数异常,而非未捕获的空指针,修复完成,全程无服务重启。
热替换核心限制
- 仅支持修改方法体,不支持新增类的字段、方法、构造函数
- 不支持修改类的继承关系、实现的接口、方法签名
- 不支持修改注解、枚举值等类的元信息
- JVM重启后热修改会失效,永久修复需更新代码重新发布
二、全链路性能追踪:接口超时根因秒定位
针对线上接口超时、慢调用、偶现异常等问题,Arthas提供了trace、watch、tt三大核心命令,无需新增日志,即可全链路观测方法执行的完整上下文。
1. trace:全链路耗时追踪
trace命令用于追踪方法内部的调用链路,统计每个节点的耗时占比,精准定位慢调用代码行,高阶用法如下:
- 基础追踪:排除JDK内部方法,减少无关输出
trace -j com.jam.demo.controller.OrderController getOrderDetail
执行后调用接口,会输出完整调用链路与耗时分布:
---ts=2026-03-16 10:00:00;thread_name=http-nio-8080-exec-1;id=23;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1be6f5c0
---[1200ms] com.jam.demo.controller.OrderController:getOrderDetail()
+---[0.23ms] org.slf4j.Logger:info() #24
+---[0.05ms] org.springframework.util.StringUtils:hasText() #25
+---[1195ms] com.jam.demo.service.impl.OrderServiceImpl:getOrderById() #29
+---[0.18ms] org.slf4j.Logger:info() #31
---[0.02ms] return #32
- 条件过滤:仅追踪耗时超过1000ms的慢调用
trace -j com.jam.demo.controller.OrderController getOrderDetail '#cost > 1000'
- 多方法递归追踪:同时追踪Controller与Service层的方法
trace -j -E com.jam.demo.controller.OrderController|com.jam.demo.service.impl.OrderServiceImpl getOrderDetail|getOrderById
2. watch:方法上下文全观测
watch命令用于观测方法的入参、出参、返回值、异常对象,甚至方法内部的局部变量,无需新增日志即可获取完整执行上下文:
- 全场景观测:同时捕获方法执行前、正常返回、异常抛出、执行结束四个节点的上下文
watch com.jam.demo.controller.OrderController getOrderDetail "{params,returnObj,throwExp}" -b -e -s -f
- 条件过滤:仅观测指定入参的调用
watch com.jam.demo.controller.OrderController getOrderDetail "{params,returnObj}" "params[0].equals('123456')" -s
- 内部变量观测:查看方法内部的局部变量值
watch com.jam.demo.service.impl.OrderServiceImpl getOrderById "{target, #queryWrapper}" -s
3. tt:时间隧道,偶现问题终极解决方案
tt(TimeTunnel)命令会记录方法的每一次调用的完整上下文,包括入参、出参、异常、耗时、线程信息,支持事后回放、重新调用,完美解决线上偶现问题无法复现的痛点:
- 开启调用记录
tt -t com.jam.demo.controller.OrderController getOrderDetail
- 查看所有记录的调用
tt -l
- 查看指定调用的详细信息(index为记录的序号)
tt -i 1001
- 回放指定调用,用当时的入参重新执行方法,完美复现问题
tt -i 1001 -p
三、线程问题终极定位:CPU飙高、死锁、阻塞一键排查
Arthas的thread命令基于JVM的ThreadMXBean实现,比传统jstack更强大,支持实时CPU排序、线程状态过滤、死锁自动检测,无需手动分析堆栈。
1. CPU飙高快速定位
线上服务CPU使用率100%,通过以下步骤30秒定位根因:
- 查看CPU占用Top3的线程,直接输出线程堆栈
thread -n 3
- 输出结果会直接显示高CPU线程的堆栈信息,定位到具体代码行:
"http-nio-8080-exec-2" Id=24 cpuUsage=80.2% RUNNABLE
at com.jam.demo.service.impl.OrderServiceImpl.calculateOrderAmount(OrderServiceImpl.java:88)
at com.jam.demo.service.impl.OrderServiceImpl.getOrderById(OrderServiceImpl.java:30)
at com.jam.demo.controller.OrderController.getOrderDetail(OrderController.java:29)
2. 死锁自动检测
无需手动分析jstack堆栈,一键自动检测死锁:
thread -b
如果存在死锁,会直接输出死锁线程、持有锁对象、等待锁对象、完整堆栈信息:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8a9b0c0000 (object 0x0000000700a00000, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8a9b0c0008 (object 0x0000000700a00008, a java.lang.Object),
which is held by "Thread-1"
3. 线程状态过滤
查看所有处于阻塞状态的线程,定位线程卡顿根因:
thread --state BLOCKED
四、内存与GC问题深度排查:OOM、FullGC频繁根因定位
针对线上FullGC频繁、内存泄漏、OOM等问题,Arthas提供了完整的排查工具链,无需重启服务,实时查看内存与GC状态,动态修改JVM参数。
1. 全局状态实时查看
dashboard命令实时刷新JVM整体运行状态,是故障排查的第一步:
dashboard
输出内容包括:
- 堆内存:eden区、survivor区、老年代的容量与使用率
- 非堆内存:元空间、代码缓存的使用情况
- GC统计:YoungGC、FullGC的次数与总耗时
- 线程统计:活跃线程数、守护线程数、峰值线程数
- CPU使用率:进程CPU、系统CPU使用率
2. GC详情统计
查看GC的详细统计信息,判断GC是否异常:
gc
3. 堆转储与内存泄漏分析
线上出现内存泄漏、FullGC频繁,生成堆转储文件用于深度分析:
heapdump --live /tmp/heapdump.hprof
--live参数仅dumpFullGC后仍存活的对象,排除可回收的垃圾对象,大幅减小dump文件大小,更精准定位内存泄漏。生成的hprof文件可通过MAT、JProfiler等工具分析,找到占用内存最多的对象,定位泄漏根因。
4. 动态修改JVM参数
无需重启服务,动态修改可管理的JVM参数:
# 查看所有JVM参数
vmoption
# 开启OOM时自动生成堆dump
vmoption HeapDumpOnOutOfMemoryError true
# 修改元空间最大大小
vmoption MaxMetaspaceSize 512m
五、类与类加载问题排查:类冲突、代码未生效一键定位
线上出现NoClassDefFoundError、ClassNotFoundException、代码发布后未生效等问题,通过Arthas的sc、sm、jad、classloader命令可快速定位。
1. sc:搜索已加载的类
查看JVM中是否加载了目标类,以及类的详细信息:
# 搜索所有Order相关的类
sc *Order*
# 查看类的详细信息,包括类加载器、源码位置、注解等
sc -d com.jam.demo.controller.OrderController
2. jad:反编译已加载的类
查看JVM中实际运行的源代码,排查代码未生效、类冲突问题:
jad --source-only com.jam.demo.controller.OrderController
3. classloader:类加载器分析
查看类加载器的层级结构、加载的类数量、URL,排查双亲委派破坏、类冲突问题:
# 查看所有类加载器统计信息
classloader
# 查看类加载器层级结构
classloader -t
# 查看指定类加载器加载的所有类
classloader -c 1be6f5c0 -l
六、进阶黑科技:火焰图、动态日志、OGNL万能执行
1. profiler火焰图:性能瓶颈终极定位
Arthas集成了async-profiler,可生成CPU、内存、锁的火焰图,无需重启服务,线上直接使用,精准定位性能瓶颈:
# 启动CPU火焰图采集,时长30秒
profiler start --duration 30 --event cpu
# 查看采集状态
profiler status
# 停止采集,生成HTML格式火焰图
profiler stop --format html
生成的火焰图可直接在浏览器打开,横向宽度代表CPU占用时间,纵向代表调用栈,一眼定位高耗时方法。
2. 动态修改日志级别
线上排查问题需要DEBUG日志,无需重启服务,动态修改日志级别:
# 查看指定类的日志配置
logger -n com.jam.demo.controller.OrderController
# 修改指定类的日志级别为DEBUG
logger -n com.jam.demo.controller.OrderController -l DEBUG
# 修改根日志级别为INFO
logger -l INFO
3. OGNL高阶用法:线上万能执行器
Arthas支持OGNL表达式,可执行任意Java代码,调用Spring Bean、获取静态变量、执行方法,是线上排查的万能工具:
- 获取Spring容器中的Bean,调用业务方法
ognl '@org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext().getBean("orderServiceImpl").getOrderById("123456")'
- 获取类的静态变量
ognl '@com.jam.demo.controller.DeadlockController@LOCK_A'
- 执行数据库查询
ognl '@org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext().getBean("orderMapper").selectById("123456")'
JVM线上故障全链路定位黄金流程
通用排查总流程
分场景详细排查流程
1. CPU飙高排查流程
2. 内存泄漏/FullGC频繁排查流程
3. 接口超时/慢调用排查流程
4. 死锁/线程阻塞排查流程
线上真实故障实战案例
案例一:线上CPU飙高100%,5分钟定位修复
现象:线上服务CPU使用率持续100%,接口响应超时,告警触发,重启服务后10分钟复现。排查过程:
- Arthas attach目标进程,执行dashboard,看到进程CPU使用率98%,单线程占用85%CPU。
- 执行thread -n 1,定位到高耗时代码在OrderServiceImpl的calculateOrderAmount方法,for循环终止条件写错,i--写成i++导致死循环。
- 通过jad反编译、mc编译、retransform热修复,CPU使用率立即降至10%以下,接口恢复正常。
- 发布正式版本修复,问题彻底解决。
案例二:线上FullGC频繁,10分钟定位内存泄漏
现象:线上服务每3分钟触发一次FullGC,老年代回收后使用率仍达85%,内存持续上涨,最终OOM。排查过程:
- 执行dashboard,看到老年代使用率2分钟内从50%涨到95%,FullGC频繁。
- 执行heapdump --live生成堆转储文件,MAT分析发现100万+Order对象占用1GB内存,被静态List持有。
- 通过ognl命令清空静态List,老年代使用率立即降至20%,FullGC停止。
- 修复代码移除静态集合,发布正式版本,问题彻底解决。
案例三:线上接口偶现超时,tt命令完美定位
现象:线上订单查询接口偶现超时,每天出现3-5次,无法稳定复现,本地测试正常,无异常日志。排查过程:
- 执行tt -t记录接口所有调用,等待1小时后捕获到超时调用,index=1008,耗时1200ms。
- 执行tt -i 1008 -p回放调用,trace追踪发现数据库查询耗时1190ms。
- watch查看SQL语句,发现orderId字段为varchar类型,传入数字类型导致隐式类型转换,索引失效,全表扫描。
- 修复代码统一参数类型,发布正式版本,问题彻底解决。
线上使用避坑指南与最佳实践
核心避坑点
- 热替换避坑:热修改前必须备份原class文件;生产环境禁止修改核心框架类;排查完成后执行reset命令重置所有增强,避免性能损耗。
- 安全规范:生产环境禁止使用stop命令,会重置所有增强类;禁止长时间运行trace、tt等命令,避免占用过多JVM资源;heapdump必须加--live参数,避免磁盘IO打满;禁止执行危险的ognl表达式修改业务数据。
- 性能影响:单命令性能损耗低于1%,但同时开启多个增强命令会导致损耗上升,排查完成后必须执行reset重置。
- 版本兼容:JDK 9+需开启--enable-native-access参数;SpringBoot 3.x需使用Arthas 3.6.0+版本兼容Jakarta EE。
最佳实践
- 故障排查先执行dashboard,整体了解JVM状态,再针对性排查。
- 偶现问题优先使用tt命令记录上下文,事后回放复现。
- 慢调用优先使用trace命令定位耗时节点,逐层递归排查。
- CPU飙高优先使用thread -n命令定位高耗时代码行。
- 内存泄漏优先使用heapdump生成堆转储文件,结合MAT分析。
- 排查完成后必须执行reset重置所有增强,quit退出Arthas。
总结
Arthas作为Java线上故障排查的神器,覆盖了JVM故障的全场景,从CPU、内存、线程、类加载到接口性能、代码热修复,全程无需重启服务,无需修改代码,极大提升了线上故障排查的效率。本文的高阶用法和全链路定位流程,均经过线上生产环境验证,掌握这些能力,你就能从容应对任何JVM线上故障,从被动救火的业务开发,进阶为能快速定位根因、解决问题的技术专家。