本文记录一次完整的 SkyWalking 落地过程:服务端部署(11.0.0)、本地项目接入(JDK 8~21)、以及真正天天用得到的排障方法。
环境:CentOS 7 + JDK 17(服务端)+ Elasticsearch 7.9.3 + SkyWalking 11.0.0 + Java Agent 9.8.0(本地 Java 21 项目)
很多人装了 SkyWalking 却用不起来,是因为没分清这几个东西各自在哪、干什么:
┌─────────────────────────┐ ┌──────────────────────────┐ ┌──────────────┐ │ 被监控的应用 │ 11800 │ OAP Server │ 9200 │ Storage │ │ JVM + Java Agent │ ──────> │ (接收/解析/聚合) │ ─────> │ ES/BanyanDB │ └─────────────────────────┘ └──────────────────────────┘ └──────────────┘ │ 12800 ▼ ┌──────────────────────────┐ │ UI (Horizon, 8080) │ └──────────────────────────┘
| 角色 | 位置 | 作用 | 必须吗 |
|---|---|---|---|
| Java Agent | 被监控应用内 | 字节码增强,采集链路数据 | ✅ 必须 |
| OAP Server | 服务端 | 接收数据、解析、聚合、写存储 | ✅ 必须 |
| Storage | ES / BanyanDB / H2 | 存 traces、metrics、logs | ✅ 必须(可换) |
| UI | 服务端 | 浏览器看的页面 | ✅ 必须 |
三个关键认知:
apache-skywalking-apm-bin 只有服务端,Agent 要单独下。| 端口 | 用途 | 需要公网暴露吗 |
|---|---|---|
| 11800 | Agent 上报(gRPC) | ✅ 要(给你的应用所在机器) |
| 11800 | 内部通信 | — |
| 12800 | GraphQL 查询 | ❌ UI 内部调用 |
| 17128 | Admin API | ❌ UI 内部调用 |
| 8080 | UI 页面 | ✅ 要 |
只需要对公网开 11800(上报) 和 8080(看页面),其余走内网。
SkyWalking 11 要求 Java 11+,官方推荐 17。
这是第一个坑:用 JDK 8 启动会得到
UnsupportedClassVersionError: class file version 55.0。 class 55 = Java 11 编译,52 = Java 8。
如果服务器上已经有 JDK 8 跑着别的服务,不要动它,装一个独立的 JDK 17:
bashmkdir -p /usr/local/java
tar -xzf jdk-17.0.2_linux-x64_bin.tar.gz -C /usr/local/java
/usr/local/java/jdk-17.0.2/bin/java -version
只需要在启动 SkyWalking 时指定 JAVA_HOME,不影响全局。
bashmkdir -p /opt/skywalking && cd /opt/skywalking
tar -xzf apache-skywalking-apm-11.0.0-bin.tar.gz
cd apache-skywalking-apm-bin
目录结构:
bin/ # oapService.sh(启动脚本) config/ # application.yml(核心配置) oap-libs/ # 依赖 jar logs/ # 日志 ← 排错看这里
编辑 config/application.yml,找到 storage 段:
yamlstorage:
selector: ${SW_STORAGE:elasticsearch} # 默认是 banyandb,改成自己的
elasticsearch:
clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:127.0.0.1:9200}
protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"}
user: ${SW_ES_USER:"elastic"}
password: ${SW_ES_PASSWORD:"<你的密码>"}
indexShardsNumber: ${SW_STORAGE_ES_INDEX_SHARDS_NUMBER:1}
indexReplicasNumber: ${SW_STORAGE_ES_INDEX_REPLICAS_NUMBER:0} # 单机建议改 0
两个建议:
indexReplicasNumber 改成 0。ES 单节点下副本会一直 yellow,浪费空间还告警。*、!)时用双引号包住。默认 -Xms256M -Xmx4096M,小机器扛不住。用环境变量覆盖:
bashexport JAVA_HOME=/usr/local/java/jdk-17.0.2
export JAVA_OPTS="-Xms512M -Xmx1024M"
./bin/oapService.sh
注意:oapService.sh 是前台 exec 启动的(不是后台),所以要用 systemd 或 nohup,否则终端一断进程就没了。
/etc/systemd/system/skywalking-oap.service:
ini[Unit]
Description=SkyWalking OAP 11.0.0
After=network-online.target docker.service
Wants=network-online.target docker.service
[Service]
Type=simple
WorkingDirectory=/opt/skywalking/apache-skywalking-apm-bin
Environment=JAVA_HOME=/usr/local/java/jdk-17.0.2
Environment=JAVA_OPTS=-Xms512M -Xmx1024M
Environment=OAP_LOG_DIR=/opt/skywalking/apache-skywalking-apm-bin/logs
ExecStart=/opt/skywalking/apache-skywalking-apm-bin/bin/oapService.sh
Restart=on-failure
RestartSec=15
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
bashsystemctl daemon-reload
systemctl enable --now skywalking-oap
这是第二个坑:apache-skywalking-apm-bin 里没有 webapp 目录。
从 10.x 开始官方把 UI 拆出去了,到 11.0 直接换成了全新的 Horizon UI(独立项目,Node.js 写的)。旧的 booster-ui 不随包发布了。
下载:
bashcurl -LO https://downloads.apache.org/skywalking/horizon-ui/1.0.1/apache-skywalking-horizon-ui-1.0.1-bin.tar.gz
mkdir -p /opt/skywalking/horizon
tar -xzf apache-skywalking-horizon-ui-1.0.1-bin.tar.gz -C /opt/skywalking/horizon --strip-components=1
改 horizon.yaml:
yamlserver:
host: "${HORIZON_SERVER_HOST:0.0.0.0}" # 默认 127.0.0.1,外部访问不了
port: ${HORIZON_SERVER_PORT:8080} # 默认 8081
oap:
queryUrl: "${HORIZON_OAP_QUERY_URL:http://127.0.0.1:12800}"
adminUrl: "${HORIZON_OAP_ADMIN_URL:http://127.0.0.1:17128}"
zipkinUrl: "${HORIZON_OAP_ZIPKIN_URL:http://127.0.0.1:12800/zipkin}"
它需要 Node 20+。CentOS 7 上装不了(glibc 2.17 < 2.28),用 Docker 最省事:
bashdocker run -d --name skywalking-horizon --restart=always \ --network host \ -v /opt/skywalking/horizon:/app -w /app \ --env-file /opt/skywalking/horizon/horizon.env \ node:22-alpine node server.js
必须创建登录账号,否则页面能打开但登录一定失败:
bash# 生成 argon2 哈希(用容器里的 argon2 库)
docker exec skywalking-horizon node -e \
'const a=require("/app/node_modules/argon2"); a.hash("你的密码").then(console.log)'
把输出写进 horizon.env:
HORIZON_AUTH_LOCAL_USERS=[{"username":"admin","passwordHash":"$argon2id$v=19$m=65536,t=3,p=4$...","roles":["admin"]}]
horizon.yaml里其实早就写了:With local and no users, the BFF boots but no login succeeds—— 没有默认的 admin/admin,必须自己生成。
bashsystemctl is-active skywalking-oap # active
ss -lntp | grep -E ':(11800|12800|17128)' # 都在监听
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
-H 'Content-Type: application/json' \
-d '{"query":"{version}"}' http://127.0.0.1:12800/graphql # 200
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/ # 200
在 ES 里能看到 sw_* 索引,说明存储也通了:
bashcurl -u 'elastic:xxx' 'http://127.0.0.1:9200/_cat/indices' | grep sw_
# sw_segment-20261024 sw_metrics-all-20261024 sw_log-20261024 sw_management
Agent 是独立包,不在 apm-bin 里:
https://skywalking.apache.org/downloads/ → Java Agent
结构:
skywalking-agent/ ├── skywalking-agent.jar ← 就是这个 ├── config/agent.config ← 也可以把参数写这里 ├── plugins/ ← 框架插件,按需加载 ├── optional-plugins/ ← 需要手动拷进 plugins 才生效 └── logs/ ← agent 自己的日志
在 Run/Debug Configurations → VM options:
-javaagent:D:\skywalking\skywalking-agent\skywalking-agent.jar -Dskywalking.agent.service_name=nft-turbo-business -Dskywalking.collector.backend_service=<OAP_IP>:11800
| 参数 | 含义 | 常见错误 |
|---|---|---|
-javaagent | 探针 jar 路径 | 指到 .tgz 或目录 → Error opening zip file |
service_name | 页面显示的服务名 | 多实例用同一个名字才会聚合 |
backend_service | OAP 地址,端口是 11800 | 写成 8080 / 12800 → 连不上 |
bashjava -javaagent:/opt/skywalking-agent/skywalking-agent.jar \ -Dskywalking.agent.service_name=my-app \ -Dskywalking.collector.backend_service=<OAP_IP>:11800 \ -jar app.jar
-javaagent 必须在 -jar 之前,否则会被当成应用参数。
控制台出现:
INFO SkyWalkingAgent - SkyWalking agent started
agent 日志 skywalking-agent/logs/skywalking-api.log 里会打印连接结果。30 秒内页面「服务」列表出现你的服务名,就成功了。
| # | 现象 | 根因 | 解法 |
|---|---|---|---|
| 1 | UnsupportedClassVersionError: class file version 55.0 | 用 JDK 8 跑 JDK 11 编译的 class | 装 JDK 17,独立于已有 JDK 8 |
| 2 | 解压后找不到 webapp 目录 | 10.x 起 UI 从主包剥离,11.0 换成 Horizon UI | 单独下载 Horizon UI |
| 3 | GLIBC_2.28 not found(装 Node 20) | CentOS 7 是 glibc 2.17 | 用 Docker 跑 node:22-alpine |
| 4 | UI 能打开但登录失败 | auth.local.users 为空,没有默认账号 | 生成 argon2 哈希写进配置 |
| 5 | Error opening zip file | -javaagent 指向了 .tgz 压缩包 | 解压,指向 skywalking-agent.jar |
| 6 | Agent 启动正常但页面没数据 | 11800 端口没放行 / 写成了 8080 | 安全组放行 11800,参数确认 |
CentOS 7 的 glibc 是 2.17,而 Node 18+ 的官方二进制要求 glibc 2.28+。硬装会报:
node: /lib64/libm.so.6: version `GLIBC_2.27' not found
升级 glibc 风险极高(容易把系统搞崩),用 Docker 是最优解。Horizon 的依赖里有 argon2 这个 native 模块,但包内自带了 linux-x64/argon2.musl.node,所以 alpine 镜像能用。
SkyWalking 的价值不在"有链路图",而在于能回答"慢在哪"。
打开「服务仪表板」,看三个核心指标:
| 指标 | 含义 | 异常表现 |
|---|---|---|
| 吞吐量 (CPM) | 每分钟调用次数 | 突降 → 上游挂了 / 流量没了 |
| 响应时间 | P50 / P75 / P90 / P99 | P99 飙升但 P50 正常 → 长尾问题 |
| SLA | 成功率 | 跌破阈值 → 有异常 |
判断技巧:
这一步的作用是把问题缩小到某个服务、某个时间段。
进入「追踪」,两个关键操作:
这一步的作用是拿到一条具体的慢请求。
这是最关键的一步,也是新手最容易看错的地方。
打开后你会看到一棵 Span 树,每个节点有:操作名、耗时、开始偏移。
正确的看法:找"自身耗时"最大的,不是"总耗时"最大的。
GET /user/getUserInfo 442ms ← 总耗时 ├─ Redisson/GET 0ms ├─ Redisson/GET 0ms ├─ ShardingSphere/routeSQL 43ms ├─ Druid/Connection/getConnection 0ms ← 连接池健康 ├─ ShardingSphere/executeSQL 43ms ├─ MySQL/PreparedStatement/execute 42ms └─ Redisson/PSETEX 0ms
所有子 Span 加起来只有 128ms,剩下 314ms 是"空白" —— 这部分是应用自身逻辑(GC、序列化、锁等待、CPU 计算)。
空白大 ≠ 一定有问题,但它是排查的起点。
这是我总结的最实用的一张表:
| 现象 | 大概率原因 | 下一步 |
|---|---|---|
| DB Span 耗时高,且 SQL 相同 | 慢 SQL / 缺索引 | 看 Span 标签里的 SQL,EXPLAIN |
| 同一 SQL 出现 N 次 | N+1 查询 | 改成批量查询 / join |
Druid/getConnection 耗时高 | 连接池不够 / 有慢查询占着连接 | 调大 maxActive,查慢 SQL |
| Redis Span 耗时高 | 大 key / 热 key / 网络往返多 | 看 key 大小,考虑 pipeline |
| 两个 Span 之间有大片空白 | GC / 线程阻塞 / 锁竞争 / 序列化 | 开 Profiling 看 CPU 火焰图 |
| 外部 HTTP Span 耗时高 | 下游服务慢 | 看下游自己的 Trace |
| Span 上有红色 Error 标记 | 抛异常了 | 点开看异常堆栈 |
| 同一个服务不同实例耗时差异大 | 负载不均 / 某台机器有问题 | 切到「实例」维度对比 |
空白大时,按这个顺序排除:
jstat -gc 看,Full GC 会造成明显停顿BLOCKED按日常使用频率排序。
场景:某个接口慢、某个请求报错、想看清楚一次调用经过哪些组件。
关键操作:
Trace ID 是打通"链路"和"日志"的钥匙。后面第八节讲怎么配。
场景:线上告警了、想确认发版后性能有没有退化、想对比昨天和今天。
看 P99 曲线的拐点,和发版时间对齐,能快速判断"是不是这次发布引入的"。
场景:梳理系统调用关系、排查"到底是谁在调我"。
节点上的红色/黄色代表健康度。当你发现一个自己都不知道的服务在调你,这里能看出来。
场景:服务有 3 个实例,整体指标正常但偶发超时。
切到「实例」维度,对比各实例的响应时间。如果只有一台高,大概率是那台机器的问题(CPU、内存、网络、磁盘)。
场景:Trace 里看到报错,想看当时的业务日志。
前提是把日志接进来(用 log4j2/logback 的 SkyWalking appender,或用 traceId 关联 ELK)。没接入的话,这一项是空的。
场景:不想一直盯着页面,让系统自己报警。
在「告警规则」里配置,典型规则:
SkyWalking 的告警是基于规则引擎的,可以配 webhook 推到钉钉/企业微信。
场景:Trace 里看到大片空白,但不知道代码卡在哪。
可以对某个端点做线程级采样,生成火焰图。这是定位"没有 Span 覆盖的耗时"的终极手段。
注意:Profiling 对性能有影响,不要在生产高峰期对高频接口开。
拿一条真实 Trace 演示完整排查过程。
GET /collection/collectionList 8144ms ← 明显异常 GET /user/getUserInfo 442ms cn.hollis.nft.turbo.api.user... 698ms
第 1 步:先看 8.1 秒那条
点进去看瀑布图,重点找两样东西:
如果是几百个小 Span → 典型的 N+1 问题。在 Trace 详情里上下滚动,看同一个操作重复出现的次数。
如果是一个 MySQL Span 8 秒 → 点开标签看具体 SQL,去数据库 EXPLAIN。
第 2 步:再看 442ms 那条
这条很有意思,是个典型的"教科书案例":
Span 总和 ≈ 128ms,总耗时 442ms → 空白 314ms
子 Span 里最慢的是:
ShardingSphere/routeSQL 43ms —— SQL 解析路由,分库分表框架的固定开销MySQL/PreparedStatement/execute 42ms —— 真正的数据库执行Druid/Connection/getConnection 0ms —— 连接池健康,可以直接排除所以这条链路里,数据库不是主要问题,314ms 的空白才是。往 GC / 序列化 / 业务逻辑方向查。
第 3 步:验证结论
getConnection 耗时为 0 是很强的信号:连接池没问题,别再往这个方向查这是投入产出比最高的一步。配置方式(logback 示例):
xml<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%X{tid}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
%X{tid} 会从 SkyWalking 的上下文里取 Trace ID。
之后在日志里搜 Trace ID,就能把"链路"和"业务日志"对上。
默认采样率 100%,高并发下会拖慢应用、撑爆存储。在 agent.config 里:
propertiesagent.sample_n_per_3_secs=100
意思是每 3 秒最多采样 100 条。
排查偶发问题时可以临时调到 100%,平时调低。 注意:采样率低的时候,不是每个请求都有 Trace,别因为搜不到就以为没上报。
健康检查、监控探针这类接口会产生大量垃圾数据:
propertiesagent.ignore_suffix=.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.svg
用 namespace 区分环境,避免测试环境和生产环境的数据混在一起:
yaml# OAP 侧
storage:
elasticsearch:
namespace: ${SW_NAMESPACE:"prod"}
properties# Agent 侧 agent.namespace=prod
bashsystemctl status skywalking-oap
systemctl restart skywalking-oap
tail -f /opt/skywalking/apache-skywalking-apm-bin/logs/skywalking-oap-server.log
docker logs -f skywalking-horizon # UI 日志
docker restart skywalking-horizon
bashss -lntp | grep -E ':(11800|12800|17128|8080)'
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"query":"{version}"}' http://127.0.0.1:12800/graphql
-javaagent:<path>/skywalking-agent.jar -Dskywalking.agent.service_name=<服务名> -Dskywalking.collector.backend_service=<OAP_IP>:11800
bashtail -f skywalking-agent/logs/skywalking-api.log # agent 日志
| 内容 | 路径 |
|---|---|
| OAP 配置 | config/application.yml |
| OAP 日志 | logs/skywalking-oap-server.log |
| UI 配置 | horizon.yaml |
| UI 登录用户 | horizon.env |
| Agent 配置 | skywalking-agent/config/agent.config |
| Agent 日志 | skywalking-agent/logs/skywalking-api.log |
SkyWalking 这类 APM 工具,装起来只是 20% 的工作量,剩下 80% 是"看到数据之后怎么用"。
很多团队装了之后,只在出故障时打开看一眼拓扑图,然后继续 grep 日志——这就浪费了它最大的价值:把"用户反馈慢"这种模糊描述,精确到某一层、某一条 SQL、某一毫秒。
我自己的使用习惯是:
最后一句:别只盯着最慢的那个 Span。总耗时 442ms 的请求里,最慢的子 Span 只有 43ms,真正的问题藏在 314ms 的空白里——这个反直觉的点,是新手和老手最大的差别。


本文作者:章鱼哥
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!