第八阶段:可观测性(线上排障能力)— 讲解与面试题

第八阶段:可观测性(线上排障能力)— 讲解与面试题

本文档定位: 对应《简历技术栈学习指南》第八阶段。你简历中写的是“搭建SkyWalking全链路追踪 + Prometheus/Grafana监控告警体系,线上故障定位耗时从小时级降至分钟级,系统可用性提升至99.9%”,技能清单还有“Arthas”。这是高级工程师和新手的分水岭——面试官会重点考察“线上出问题你怎么查”的实战能力。

使用方法: 每个知识点按 小白讲解 → 名词详解(举例) → 结合简历案例 → 代码/配置示例 → 面试题 组织。⭐ 越多越高频。


📖 本文档名词速查(看到不认识的缩写,先查这里)

完整的白话解释、生活类比、面试话术见 00-名词速查手册.md。 下面只列本文档用到的名词,按出现频率排序。

缩写 / 术语 英文全称 一句话说明 出处
SkyWalking SkyWalking 国产 APM,分布式链路追踪 第8章
Prometheus Prometheus 时序数据库 + 监控系统,拉模式采集 第8章
Span Span(跨度) 调用链中的一段(一个服务/一次调用) 第8章
Arthas Arthas 阿里开源的 Java 诊断工具,线上排查神器 第8章
GC Garbage Collection 垃圾回收,自动回收不用的对象 第4章
TraceId Trace ID 一次请求的唯一标识,串起整条调用链 第8章
JVM Java Virtual Machine Java 虚拟机,让 Java 代码“一次编译到处运行”的那个东西 第4章
Grafana Grafana 可视化面板,把 Prometheus 的数据画成图 第8章
API Application Programming Interface 应用程序接口,别人能调用你的功能 第13章
ThreadLocal ThreadLocal(线程本地) 每个线程一份独立副本,线程间互不影响 第4章
HTTP HyperText Transfer Protocol 超文本传输协议,Web 通信的基础 第13章
Kafka Kafka 最初为日志设计的消息队列,吞吐量极高 第2章
QPS Queries Per Second 每秒请求数(系统能扛多少流量) 第8章
Metrics Metrics(指标) 可聚合的数字(CPU、QPS、错误率) 第8章
HBase Hadoop Database 分布式的列族数据库,适合海量数据的随机读写 第15章
RowKey Row Key HBase 的“主键”,数据按它排序存储,设计好坏直接决定性能 第15章
OOM Out Of Memory 内存溢出,内存不够用了 第4章
Logging Logging(日志) 离散的事件记录 第8章
Tracing Distributed Tracing(链路追踪) 一个请求在微服务间的完整调用链 第8章
MQ Message Queue 消息队列,存消息的“中转站”,让两个服务不必同时在线 第2章
Gateway API Gateway 网关,所有请求的统一入口(鉴权/路由/限流) 第7章
SLO Service Level Objective 服务等级目标:内部定的目标(如“可用性 99.95%”) 第8章
Pod Pod(豆荚) K8s 的最小调度单位,一个 Pod 里可有多个容器 第9章
LLM Large Language Model 大语言模型,就是 ChatGPT 背后那个“大脑” 第11章
JSON JavaScript Object Notation 轻量级数据交换格式({} 和 []) 第13章
Executor Executor(执行器) 真正干活的工作进程,跑在从节点上,一个节点可以跑多个 第15章
向量 Vector 一组数字,表示文本在语义空间中的位置 第11章
💡 为什么要有这张表?

技术文档最大的阅读障碍不是原理难,而是缩写不认识 —— 看到“2PC 在数据库层加锁” 这句话,如果不知道 2PC 是什么,整段就废了。

这张表保证你在任何位置遇到不认识的词,都能当场查到,不用跳出文档。 想知道“为什么是这样”“面试怎么答”,再点进手册看完整版。


目录


第一章:可观测性三大支柱(总纲)

1.1 什么是可观测性(Observability)?

小白讲解

一句话:系统出问题时,你能从外部“看见”它内部正在发生什么,并且能快速定位到根因。

传统监控(Monitoring)只能回答“你知道要问的问题”——比如“CPU 高不高、服务挂没挂”。可观测性更进一步,能回答你没预料到的问题——比如“为什么这个用户的请求失败了”。

名词详解:三大支柱

支柱 英文 解决什么问题 举例(以你的 RAG 系统为例)
指标(Metrics) 数值时间序列 “有没有问题”——趋势、告警 /chat 接口最近 5 分钟 P99 延迟涨到 8 秒了
日志(Logging) 离散文本事件 “具体发生什么”——细节还原 某条 ERROR 日志:向量化调用 Qwen API 超时 timeout after 3000ms
追踪(Tracing) 请求级调用链 “问题在哪一环”——跨服务定位 这次请求:Gateway(2ms) → chat-service(5ms) → 向量检索(120ms) → LLM调用(7800ms) ← 瓶颈在这

三者配合的排查路径(必背):

告警触发(Metrics:P99 延迟超标)
    ↓ 拿到时间点 + TraceId(从日志/告警标签)
打开 Trace 链路(Tracing:发现慢在 LLM 调用环节)
    ↓ 看该 Span 关联日志
看日志细节(Logging:Qwen API 超时,重试 2 次都失败)
    ↓ 根因
第三方 API 限流 → 加熔断降级(Sentinel)+ 缓存高频答案

你简历中的案例话术

“我们能源平台有 12 个微服务,之前排查一个’数据大屏打开慢’的问题,要挨个登服务器 grep 日志,一个问题排查一两个小时。我搭了 SkyWalking + Prometheus/Grafana 之后:告警看大盘定位异常时间和接口 → TraceId 拉出全链路 → 直接看到慢在哪个服务的哪个方法。平均故障定位时间从小时级降到分钟级,年终复盘里系统可用性从 99.5% 提升到 99.9%。”

99.9% 是什么概念(面试官可能追问): 一年 8760 小时 × 0.1% ≈ 全年停机不能超过 8.76 小时。99.99%(四个九)≈ 52 分钟/年。数字越多 9,代价指数级上升。


第二章:SkyWalking 全链路追踪

2.1 全链路追踪的核心名词

小白讲解

一次前端请求经过 12 个微服务,就像快递经过 12 个中转站。全链路追踪就是给每个包裹(请求)发一个运单号(TraceId),每个中转站的分拣动作都记录时间——最后你能看到整条路线哪段耗时最长。

名词详解(APM 领域必懂词汇)

名词 解释 举例
Trace(链路) 一次完整请求的所有 Span 集合,用全局唯一 TraceId 标识 一次“上传文档并解析” = 1 个 Trace
Span(跨度) 链路中的一个工作单元,含开始/结束时间、标签、日志 DocumentService.parse() 是一个 Span,pgvector 相似度检索是子 Span
TraceContext(上下文) 跨进程传递 TraceId 的载体 HTTP Header sw8,跨服务自动透传
OpenTracing / OpenTelemetry 链路追踪的行业标准规范(API + 数据格式) SkyWalking、Zipkin、Jaeger 都兼容,避免厂商锁定
采样率(Sampling) 全量追踪开销大,按比例采集 生产设 agent.sample_n_per_3_secs=10,每 3 秒采 10 条;慢请求/错误请求 100% 采集
字节码增强(Bytecode Instrumentation) 不改业务代码,JVM 类加载时动态注入探针逻辑 java -javaagent:skywalking-agent.jar 启动即可埋点,业务零侵入
OAP Server SkyWalking 的后端分析集群(Observability Analysis Platform) 接收 Agent 上报数据,聚合分析后存入 ES
拓扑图(Topology) 服务依赖关系的自动生成图 一眼看到 gateway → instruction-service → kafka → hbase

Trace 数据结构举例

TraceId: a12bf3c8d9e0f1a2
├─ Span1: Gateway.filter()           [0ms ────────── 4210ms]  gateway服务
│   tags: http.url=/instruction/create, http.status=200
│
├─ Span2: InstructionController.create()  [5ms ──── 4200ms]   instruction服务
│   │
│   ├── Span3: InstructionService.save()  [10ms ── 300ms]
│   │      tags: db.statement=INSERT INTO t_instruction...
│   │
│   ├── Span4: KafkaTemplate.send()       [305ms ─ 315ms]
│   │      tags: mq.topic=instruction-status
│   │
│   └── Span5: HBaseClient.put()          [320ms ─ 4190ms]  ★ 最慢的 Span!
│          logs: exception=RpcTimeoutException

→ 一眼定位:HBase 写入超时是这次请求慢的根因。这就是“小时级→分钟级”的具体含义。

2.2 SkyWalking 架构与原理(面试必问原理)

架构图

┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│  Java 服务   │   │  Java 服务   │   │  前端/浏览器  │
│ ┌─────────┐ │   │ ┌─────────┐ │   └─────────────┘
│ │业务代码  │ │   │ │业务代码  │ │        ↑
│ └────┬────┘ │   │ └────┬────┘ │        │
│ ┌────▼────┐ │   │ ┌────▼────┐ │  ┌─────▼──────┐
│ │ SkyWalking│ │   │ │ SkyWalking│  │  UI 界面    │  查询/拓扑/Trace
│ │  Agent   │ │   │ │  Agent   │  └─────▲──────┘
│ └────┬────┘ │   │ └────┬────┘ │        │
└──────┼──────┘   └──────┼──────┘  ┌─────┴──────┐
       │    gRPC 上报     │         │ OAP Server  │  聚合分析
       └────────┬─────────┘         └─────▲──────┘
                └─────────────────────────┘
                                  ┌───────┴──────┐
                                  │Elasticsearch │  存储(也支持 H2/B MySQL)
                                  └──────────────┘

字节码增强原理(背下这段话)

面试话术:

“SkyWalking 用的是 Java Agent + 字节码增强(基于 ByteBuddy 库)。JVM 类加载器加载类的时候,Agent 会拦截类加载事件,找到被插件声明的拦截点(比如 Spring 的 Controller 方法、MyBatis 的 Executor、Kafka 的 Producer),在方法前后动态插入 Span 的创建和结束逻辑。所以业务代码零侵入,只需要启动命令加一个 -javaagent 参数。原理上和 Spring AOP 的动态代理不同——代理是运行时换对象,字节码增强是加载时改类。”

TraceId 跨服务怎么传?(高频追问)

服务A 调用 服务B:
服务A Agent 在发 HTTP 请求时,在 Header 塞入 sw8: 1-a12bf3c8...-spanId-parentService
    ↓ (HTTP Header 随请求传递)
服务B Agent 收到请求,解析 sw8 Header
    ↓ 继承同一个 TraceId,创建子 Span
    ↓ 跨线程:ThreadLocal 复制到子线程(SkyWalking 处理了线程池场景)

坑点(体现深度): 线程池/异步场景(比如你的 DAG 引擎用 CompletableFuture)ThreadLocal 会丢上下文,SkyWalking 插件对常见线程池做了增强;如果用了自定义线程工厂,需要 Supplier 包装 Runnable 来手工传递 ContextSnapshot。

2.3 落地配置(结合简历:12个微服务接入)

接入方式(只需改启动命令):

# K8s Deployment 里加环境变量 + 镜像内置 agent
env:
- name: JAVA_TOOL_OPTIONS
  value: >-
    -javaagent:/skywalking/agent/skywalking-agent.jar
    -Dskywalking.agent.service_name=instruction-service
    -Dskywalking.collector.backend_service=oap.observability:11800
    -Dskywalking.agent.sample_n_per_3_secs=50

自定义业务埋点(你简历写了“自定义业务指标埋点”):

// 方式一:注解埋点(最简单)
@Trace                      // 该方法自动成为链路中的一个 Span
public MatchResult settleMatch(String instructionId) { ... }

// 方式二:主动打 Tag(在 Span 上记录业务信息,排查时直接看得到)
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;

ActiveSpan.tag("instruction.id", instructionId);   // 链路里直接搜指令号定位请求
ActiveSpan.tag("settle.type", "SECURITY");         // 标记交收类型:证券交收

面试话术(自定义埋点): “除了框架级自动埋点,我们在关键业务方法上加了 @Trace 注解和业务 Tag——比如托管指令的指令号、交收类型。这样线上出问题时,用户报一个指令号,我在 SkyWalking 里直接按 Tag 搜出整条链路,不用再从网关日志开始逐层翻。”

2.4 SkyWalking 面试题

Q1:SkyWalking 是怎么做到无侵入的?(高频 ⭐⭐⭐⭐⭐)

答:Java Agent 机制 + 字节码增强。启动参数挂载 -javaagent 后,Agent 的 premain 在类加载前生效,通过 ByteBuddy 修改目标类的字节码(如 Controller、MyBatis Executor),在方法前后插入 Span 创建/结束与上下文传播逻辑。业务代码零改动,升级探针也不用改代码。

Q2:TraceId 是怎么跨服务传递的?(高频 ⭐⭐⭐⭐⭐)

答:进程内用 ThreadLocal 存 TraceContext;跨进程时 Agent 把 TraceId、SpanId 序列化进 HTTP Header(sw8)或 MQ 消息头;下游 Agent 解析后加入同一条链路。异步线程池场景需要包装 Runnable 传递 ContextSnapshot。

Q3:全量采集 Trace 有什么问题?采样策略怎么选?(中频 ⭐⭐⭐⭐)

答:Span 数据量大(一次请求几十个 Span),全量采集会占用网络、存储(ES)和应用 CPU。策略:① 概率采样(如 10%)+ ② 错误请求和慢请求(超过阈值的 Span)强制保留 ③ 核心链路 100% 采样、边缘服务低采样。原则是“宁可少而准,不能没有慢/错样本”。

Q4:SkyWalking 数据存哪里?为什么用 ES?(中频 ⭐⭐⭐)

答:默认 ES。Trace/Span 是典型写入密集 + 按时间范围查询的数据,ES 的倒排索引 + 按天滚动索引(index rollover)+ TTL 删除(保留 7-15 天)非常契合。也支持 H2(演示)、MySQL(小规模)、BanyanDB(官方新存储)。

Q5:你们线上 P99 突然飙升,用 SkyWalking 怎么排查?(场景题 ⭐⭐⭐⭐⭐)

答:① 拓扑图看是不是某一环整体变红变慢;② 打开该服务的 Trace 列表按耗时排序,取最慢的几条 Trace;③ 对比多个慢 Trace 的公共环节(比如都卡在同一个下游);④ 进入根因 Span 看异常日志和 Tag;⑤ 关联 Metrics(该环节所在实例 CPU/GC)判断是资源问题还是代码问题。


第三章:Prometheus + Grafana 监控告警

3.1 Prometheus 核心名词详解

小白讲解

Prometheus = 一个定时抽屉:每隔 15 秒去你每个服务的 /actuator/prometheus 抽屉里抄一遍所有数字(QPS、延迟、内存、GC……),存成时序数据库;Grafana 负责把这些数字画成图;Alertmanager 负责“数字越界就打电话”。

名词详解

名词 解释 举例
Pull 模型 Prometheus 主动拉取(HTTP GET)目标的数据,而非等目标推送 配置 metrics_path=/actuator/prometheus 每 15s 抓一次
Exporter 把第三方系统的指标暴露成 Prometheus 格式的“翻译官” node_exporter 暴露服务器 CPU/内存/磁盘;kafka_exporter 暴露消费组积压
PromQL Prometheus 的查询语言 rate(http_server_requests_seconds_count[5m]) = 5分钟内的 QPS
Counter(计数器) 只增不减的累计值 请求总数、错误总数、GC 次数
Gauge(仪表盘) 可增可减的瞬时值 当前堆内存、活跃线程数、Kafka 积压量
Histogram(直方图) 按桶统计分布 → 算 P95/P99 http_server_requests_seconds_bucket{le="1.0"} 1秒内完成的请求数
Summary 客户端直接算好分位数(对比 Histogram) P99 由应用自己算好上报
服务发现(SD) 自动发现抓取目标,不用手写 IP 列表 kubernetes_sd_configs 自动发现带注解的 Pod
Alertmanager 告警路由/静默/抑制/分组组件 告警发钉钉/企业微信;“节点down”时抑制其上所有服务告警

Counter vs Gauge vs Histogram(举例讲透)

Counter:http_requests_total = 102345(从启动开始累计,只增)
  → 求增速必须用 rate():
  rate(http_requests_total[5m]) = 最近5分钟平均QPS

Gauge:jvm_memory_used_bytes = 536870912(当前值,可上可下)
  → 直接画图就是内存曲线

Histogram:http_server_requests_seconds_bucket{le="0.1"} = 950
           http_server_requests_seconds_bucket{le="1.0"} = 990
           http_server_requests_seconds_bucket{le="+Inf"} = 1000
  → P99 = 99% 的请求落在这个 le 桶以内:
  histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m]))

为什么 P99 要用 Histogram 而不是平均值: 平均值会骗人——100 个请求里 99 个 10ms、1 个 10s,平均才 109ms “看起来很好”,但那 1 个用户已经骂人了。长尾才是体验杀手,所以 SLO 都定 P99。

3.2 Spring Boot 接入(Micrometer 名词)

Micrometer = 监控界的 SLF4J(门面)。你的代码只写 Micrometer API,底层可以换 Prometheus / Datadog / Influxdb。Spring Boot Actuator 内置了它。

# application.yml — 暴露指标端点
management:
  endpoints:
    web:
      exposure:
        include: health,prometheus
  metrics:
    tags:
      application: instruction-service   # 所有指标自动带应用名标签

自定义业务指标(简历写了“自定义业务指标”):

@Component
@RequiredArgsConstructor
public class InstructionMetrics {

    private final MeterRegistry registry;

    // Gauge:当前待复核指令数(仪表盘实时值)
    public InstructionMetrics(MeterRegistry registry) {
        this.registry = registry;
        registry.gauge("instruction_pending_review", new AtomicLong(0));
    }

    // Counter:指令状态流转计数(按状态打标签 → 每个状态一条曲线)
    public void recordTransition(String from, String to) {
        registry.counter("instruction_transition_total",
                "from", from, "to", to).increment();
    }

    // Timer:交收匹配耗时分布(自动算 P95/P99)
    public void recordMatchDuration() {
        Timer.Sample sample = Timer.start(registry);
        // ... 业务逻辑
        sample.stop(registry.timer("instruction_match_duration"));
    }
}

面试话术: “除了 JVM/HTTP 等技术指标,我给核心业务加了自定义指标——比如托管指令各状态的流转速率 Counter、待复核积压量 Gauge。上线后 Grafana 上能直接看到’复核环节积压了 3000 条指令’这种业务级异常,而不是等用户投诉才知道。”

3.3 常用 PromQL(必背 6 条)

# 1. QPS(按接口分组)
sum(rate(http_server_requests_seconds_count[5m])) by (uri)

# 2. P99 延迟
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))

# 3. 错误率(5xx 占比)
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m]))

# 4. JVM 堆内存使用率
jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}

# 5. Full GC 频率(G1)
rate(jvm_gc_pause_seconds_count{action="end of major GC"}[10m])

# 6. Kafka 消费组积压(Lag)
kafka_consumergroup_lag{consumergroup="instruction-group"}

3.4 告警规则 + Alertmanager(结合简历“告警体系”)

# Prometheus 告警规则示例(贴合你的项目场景)
groups:
- name: instruction-service-alerts
  rules:
  # P99 延迟超 2 秒持续 5 分钟
  - alert: HighLatencyP99
    expr: histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) > 2
    for: 5m                       # 持续5分钟才告警(过滤毛刺)
    labels:
      severity: warning
    annotations:
      summary: "{{ $labels.instance }} P99 超过 2s(当前 {{ $value }}s)"

  # 错误率超 5%
  - alert: HighErrorRate
    expr: |
      sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
      / sum(rate(http_server_requests_seconds_count[5m])) > 0.05
    for: 3m
    labels:
      severity: critical

  # 服务实例挂了
  - alert: ServiceDown
    expr: up{job="instruction-service"} == 0
    for: 1m
    labels:
      severity: critical

Alertmanager 三大降噪能力(防告警风暴,加分项):

能力 解释 举例
分组(Grouping) 相似告警合并成一条通知 12 个服务同时抖动 → 合并成 1 条钉钉消息,而不是 12 条轰炸
抑制(Inhibition) 高级告警触发时压制低级告警 “机房网络断”触发时,自动抑制所有“服务不可达”告警
静默(Silence) 维护窗口内不告警 发布窗口 22:00-23:00 设置静默

3.5 Grafana 名词(了解即可)

  • DataSource:接 Prometheus(数据从哪来)
  • Dashboard / Panel:面板;Panel 类型有 Time series(曲线)、Stat(大数字)、Table、Heatmap(热点图,看延迟分布)
  • 变量(Variable):下拉框按 service/instance 切换,一套面板看所有服务

3.6 Prometheus 面试题

Q1:为什么用 Pull 不用 Push?(高频 ⭐⭐⭐⭐)

答:① Prometheus 主动拉,目标只暴露 /metrics 端口,实现极简、无状态;② 拉取失败即健康检查(up==0 直接告警);③ 易于水平扩容和调试(curl 就能看数据);④ 天然防止应用侧阻塞(推模式在监控端故障时会拖垮应用)。短生命周期任务例外,用 Pushgateway 中转。

Q2:Prometheus 数据存多久?怎么做长期存储?(中频 ⭐⭐⭐)

答:本地 TSDB 一般保留 15-30 天(时序数据自带时间戳+标签压缩,压缩率高)。长期存储用 Thanos / VictoriaMetrics 远端存储,或者按公司要求归档到对象存储。生产 Prometheus 建议双副本 + Alertmanager 集群防单点。

Q3:指标四类型区别?(高频 ⭐⭐⭐⭐)

答:Counter 只增(累计请求量,需 rate 求速率);Gauge 瞬时可增减(内存、积压);Histogram 按桶采样分布(可聚合算 P99,推荐);Summary 客户端预算分位数(无法跨实例聚合,一般不用)。

Q4:你们的告警怎么分级?怎么防止告警疲劳?(结合简历 ⭐⭐⭐⭐)

答:三级——P0 critical(服务不可用/错误率>5%)打电话+钉钉;P1 warning(P99 超阈值/积压超限)钉钉群;P2 info 只进日报。降噪靠:告警必须持续 for N 分钟、Alertmanager 分组、根因抑制(节点挂了不重复报上面所有服务)、发布窗口静默。


第四章:日志体系与 ELK

4.1 名词详解

名词 解释 举例
结构化日志 JSON 格式而非纯文本,机器可解析 {"ts":"...","level":"ERROR","traceId":"a12bf","msg":"匹配失败","instructionId":"I20260820001"}
MDC(Mapped Diagnostic Context) Logback 的 ThreadLocal 上下文,自动把 TraceId 打进每行日志 每行日志自动带 traceId → 日志和链路联动
ELK / EFK Elasticsearch(存储检索)+ Logstash/Filebeat(采集)+ Kibana(查询界面) 容器场景常用 Filebeat(轻量)或直接日志输出到 Kafka 再进 ES

结合简历案例(日志规范)

面试话术:

“我定了团队日志规范:JSON 结构化输出,必须带 traceId(从 SkyWalking TraceContext 取)和业务主键(指令号)。Pod 日志打到 stdout 被统一采集进 ES。这样一次故障排查 = 告警定位时间 → SkyWalking 看链路 → 拿 TraceId 在 Kibana 一搜,三个系统一个 ID 串起来。”

// logback 里自动注入 traceId(每行日志都带)
<pattern>%d{HH:mm:ss} %-5level [%X{tid}] [%X{instructionId}] %logger{36} - %msg%n</pattern>

// 代码里放业务主键进 MDC
MDC.put("instructionId", instructionId);
try {
    // 业务逻辑
} finally {
    MDC.remove("instructionId");   // 线程复用,必须清理防串号
}

第五章:Arthas 线上诊断

5.1 Arthas 是什么?

小白讲解

线上 JVM 出问题(CPU 飙高、内存泄漏、方法莫名变慢),但没有接入监控、不能重启、不能加日志重新发版——这时候把 Arthas 挂到正在运行的 Java 进程上(attach 机制),像“听诊器”一样实时观察:哪个线程吃 CPU、某个方法参数返回值是什么、哪里对象暴涨。

核心机制: JVM 的 Attach API——Arthas 进程通过它把自己注入目标 JVM,加载自己的 Agent,从而能反编译类、插桩观察方法,全程不需要重启应用。

5.2 必会命令(结合场景记)

命令 作用 你项目里的使用场景
dashboard 总览:线程/内存/GC 第一眼全局判断:是不是 GC 频繁
thread -n 3 最忙的 3 个线程 + 栈 CPU 飙高 → 看是不是正则回溯/死循环
thread --state BLOCKED 找死锁/锁竞争 交收匹配批量处理卡住 → 定位锁竞争
jad 类名 反编译运行中的类 确认线上跑的代码版本对不对(发版发了没?热修生效没?)
sc -d 类名 查类从哪个 jar 加载 排查 jar 包冲突/类加载了旧版本
watch 类 方法 '{params,returnObj}' -x 2 观察方法入参/返回值 某些指令匹配结果为空 → 看入参传的什么
watch ... '#cost>200' 只看耗时超 200ms 的调用 抓慢调用
trace 类 方法 方法内部调用链各级耗时 settleMatch 慢 → 看是 SQL 慢还是 HBase 慢
tt -t 类 方法 TimeTunnel,记录每次调用可回放 偶现 Bug,回放当时的调用现场
profiler start / profiler stop 生成 CPU 火焰图 性能瓶颈直观定位
heapdump 导出堆快照 OOM 排查(配合 MAT 分析大对象)
ognl 直接执行表达式 动态改 logger 级别:logger --name com.xxx --level DEBUG

场景剧本一:CPU 100% 排查(必背)

# 1. 看全局
arthas > dashboard
# 发现某线程 CPU 90%+

# 2. 找到最忙线程及其栈
arthas > thread -n 1
# 栈显示卡在 XxxRegexMatcher.match() —— 正则回溯灾难

# 3. trace 确认耗时分布
arthas > trace com.xxx.service.SettleService matchRule '#cost > 100'
# 100%+ 时间在正则匹配

# 4. 根因:用户输入触发了灾难性回溯的正则 → 改写正则/预编译+加超时

场景剧本二:接口偶现变慢(tt 回放)

# 记录该方法最近 100 次调用
arthas > tt -t com.xxx.SettleService settleMatch -n 100
# 发现某几次 cost 超过 3s,入参里 amountScale=8(小数位特别多)

# 回放那次慢调用
arthas > tt -i 1001
# 根因:BigDecimal 高精度 scale 计算慢 → 优化为统一 scale 预处理

5.3 Arthas 面试题

Q1:线上 CPU 飙高怎么排查?(超高频 ⭐⭐⭐⭐⭐)

答:两条路都要会——① 传统三板斧:top 找进程 → top -Hp <pid> 找线程 → printf '%x' 线程号转十六进制 → jstack <pid> | grep -A 20 <hex> 看栈;② Arthas 一条龙:dashboard 看全局 → thread -n 3 直接给最忙线程和栈 → trace 定位到具体方法。看到栈后再分情况:死循环/正则回溯(改代码)、频繁 GC(dashboard 看 GC 曲线,转内存排查)、锁竞争(thread --state BLOCKED)。

Q2:线上内存泄漏/OOM 怎么排查?(超高频 ⭐⭐⭐⭐⭐)

答:① jmap -histo <pid> 快速看对象排行(或 Arthas dashboard 看堆趋势);② 疑似泄漏 → heapdump(或 jmap -dump:format=b,file=heap.hprof)导出快照;③ 用 MAT(Memory Analyzer)看支配树/Dominator Tree,找 GC Root 引用链,定位是谁持有大对象;④ 常见根因:静态 Map 只进不出(本地缓存无淘汰)、ThreadLocal 未 remove、监听器/连接未关闭、大查询一次拉全表。

Q3:Arthas 是怎么做到不重启就能观察运行中方法的?(中频 ⭐⭐⭐⭐)

答:基于 JVM Attach API。Arthas 客户端通过 Attach 连接目标 JVM 加载 Arthas Agent,Agent 用 Instrumentation API + 字节码增强(ASM)在目标方法前后插入观察逻辑,watch/trace 的数据就是插桩后采集的。jad 反编译的是方法区/元空间里的运行时字节码,所以能确认“线上真实运行的代码”。


第六章:故障排查实战剧本(面试杀手锏)

面试官问“讲一个你排查过的线上故障”,用 STAR 结构讲下面的剧本(基于你简历项目改编,务必练熟一个完整故事)。

剧本:日终清算窗口拖长(项目一真实场景改编)

S(背景): 资产托管系统日终清算有时间窗口约束,某天清算任务耗时从 3 分钟涨到 40 分钟,影响次日开盘前的对账。

T(任务): 我负责定位并恢复性能。

A(排查动作,层层递进):

  1. 看大盘(Prometheus/Grafana):清算时段 instruction-service CPU 90%、老年代锯齿异常密集 → 不是网络/下游问题,是本服务自身
  2. 看链路(SkyWalking):对清算 Trace 按耗时排序,慢 Trace 全部卡在同一个 Span:HBase 批量写入
  3. 看细节(Arthas):trace 到 HBase Put 构造环节,发现 RowKey 生成逻辑里 String.format 在千万级循环里反复调用;再看 dashboard 的 GC 压力来自临时对象暴涨
  4. 根因确认:当天的数据量翻倍(新接入一类产品),放大了两个问题——热点 RowKey 前缀冲突(Region 热点)+ 循环内对象创建过多导致 Young GC 风暴

R(结果): RowKey 加盐(业务日期+指令ID 前缀打散,正好呼应你简历写的“RowKey前缀策略”)、批量写入改为攒批 flush、String.format 换 StringBuilder。清算耗时回落到 3 分钟,并给清算任务耗时加了 Prometheus 告警(超 10 分钟预警),同类问题以后能在恶化前发现。

点题收尾(必说): “这件事也推动我们把’指标-链路-日志’三件套在故障复盘里串成了标准动作,是故障定位从小时级降到分钟级的典型例子。”


第七章:面试速查表

可观测性总纲

考点 一句话答案
三大支柱 Metrics(有没有问题)+ Logging(具体发生什么)+ Tracing(问题在哪一环)
排查路径 告警定位异常 → Trace 看链路瓶颈 → 日志看根因细节
99.9% 可用性 全年停机 ≤ 8.76 小时

SkyWalking

考点 一句话答案
无侵入原理 -javaagent + ByteBuddy 字节码增强,类加载时在方法前后插桩
TraceId 跨服务 进程内 ThreadLocal,跨进程走 HTTP Header(sw8)/ MQ 消息头
Trace/Span Trace=一次请求全链路;Span=链路中一个工作单元(含耗时/Tag/日志)
采样率 概率采样+错误/慢请求强制保留;防开销过大
架构 Agent 上报 → OAP 聚合分析 → ES 存储 → UI 展示
异步丢上下文 线程池 ThreadLocal 不传递,需包装 Runnable 传 ContextSnapshot

Prometheus + Grafana

考点 一句话答案
Pull 模型 Prometheus 主动抓 /metrics;抓不到即 up=0 告警;应用无阻塞风险
四类指标 Counter 只增需 rate / Gauge 瞬时 / Histogram 分桶算 P99 / Summary 预算分位不可聚合
P99 为什么 平均值掩盖长尾,SLO 看分位数
Micrometer 监控门面(类 SLF4J),Actuator 内置,暴露 /actuator/prometheus
自定义指标 Counter 业务流转计数 / Gauge 积压量 / Timer 耗时分布
告警降噪 for 持续时间过滤毛刺 + Alertmanager 分组/抑制/静默
长期存储 本地 TSDB 15-30 天,长期用 Thanos/VictoriaMetrics

Arthas

考点 一句话答案
原理 Attach API 注入 Agent + Instrumentation 字节码增强,无需重启
CPU 飙高 thread -n 3 看栈 → trace 定位方法(或 top+jstack 传统三板斧)
偶现问题 tt -t 录制调用现场,tt -i 回放
确认线上代码 jad 反编译运行时字节码 / sc -d 看类从哪个 jar 加载
OOM 排查 dashboard 看趋势 → heapdump → MAT 支配树找 GC Root 引用链
常见内存泄漏 静态集合无淘汰 / ThreadLocal 未 remove / 连接未关闭 / 大查询

本文档对应《简历技术栈学习指南》第八阶段,最后更新:2026-08-20