欢迎光临
我们一直在努力

【SkyWalking从入门到精通】第23篇:SkyWalking与单体应用——老系统也能焕发新活力

上一篇【第22篇】SkyWalking告警配置实战——让你的系统学会“喊救命“ 下一篇【第24篇】SkyWalking Kubernetes部署实战——让监控与云原生共舞


1. 引言:单体应用没有"含金量"吗?

如果你在技术大会上提到"我们在用Spring Boot单体应用",可能会收获一些微妙的微笑。在"万物皆可微服务"的时代,“单体"似乎成了一个略带贬义的词——听起来像在说"我们还在用功能机”。

但现实是什么?根据各种技术调研报告,超过60%的中小企业系统仍然是单体架构。一个团队8个人,维护12个微服务,光是DevOps成本就够喝一壶的了。很多时候,单体应用不是"技术债务",而是一个务实的选择。

更重要的是——单体应用的监控难度,一点都不比微服务低。一个巨大的WAR包里可能有200个Controller、50个Service、30个DAO,一个请求进来经历了多少层调用,你确定你清楚?

今天我们要讨论的就是:SkyWalking怎么帮单体应用挖出隐藏的性能问题。

2. 单体应用的特点与监控痛点

2.1 什么是真正的"单体应用"

+——————————————————————+
| 单体应用典型架构 |
+——————————————————————+
| |
| ┌──────────────────────────────────────────────────────────────┐ |
| │ MySuperApp.war (1.2GB) │ |
| │ │ |
| │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ |
| │ │ 订单模块│ │ 用户模块│ │ 商品模块│ │ 支付模块│ │ |
| │ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ │ |
| │ │ │ │ │ │ |
| │ └────────────┴────────────┴────────────┘ │ |
| │ │ │ |
| │ ┌─────────┴─────────┐ │ |
| │ │ 共享Service层 │ │ |
| │ └─────────┬─────────┘ │ |
| │ │ │ |
| │ ┌─────────┴─────────┐ │ |
| │ │ 共享DAO/数据层 │ │ |
| │ └─────────┬─────────┘ │ |
| │ │ │ |
| └────────────────────────┼─────────────────────────────────────┘ |
| │ |
| ┌────────────┼────────────┐ |
| ▼ ▼ ▼ |
| ┌─────────┐ ┌─────────┐ ┌─────────┐ |
| │ MySQL │ │ Redis │ │ 外部API │ |
| └─────────┘ └─────────┘ └─────────┘ |
+——————————————————————+

2.2 单体应用的三大监控痛点

痛点描述传统解法SkyWalking解
内部调用链路不透明 一个HTTP请求进来,经过Filter→Controller→ServiceA→ServiceB→ServiceC→DAO,到底哪一层慢了? 人肉加System.currentTimeMillis() Trace自动拆解每一层
数据库慢查询发现难 200个Mapper方法,哪几个SQL是慢的?需要翻遍slow_query_log DBA定期导出慢查询日志 在Trace中直接关联SQL耗时
外部服务不可见 调了第三方短信接口、支付网关、云存储,超时了也不知道 靠用户投诉 ExitSpan记录每次外部调用的完整信息

3. SkyWalking在单体应用中的价值

3.1 接口级别的全貌监控

即使只有一个JVM进程,SkyWalking也能帮你看清每个Controller的真实表现:

+——————————————————————+
| SkyWalking 端点面板 – 单体应用视图 |
+——————————————————————+
| |
| ┌────────────────────────────────────────────────────────────┐ |
| │ 端点 | 平均RT | P99 | 调用量 │ |
| ├────────────────────────────────────────────────────────────┤ |
| │ /api/order/create {POST} | 320ms | 1.2s | 2,340/day │ |
| │ /api/order/list {GET} | 890ms | 3.5s | 8,900/day │ |
| │ /api/user/login {POST} | 150ms | 450ms | 12,000/day │ |
| │ /api/report/export {GET} | 8.2s | 15.0s | 120/day │ |
| └────────────────────────────────────────────────────────────┘ |
| |
| 🔴 /api/report/export 导出报表耗时8.2秒 — 需要立即优化 |
| 🟡 /api/order/list 订单列表P99达3.5秒 — 需要关注 |
| 🟢 /api/user/login 登录性能良好 |
+——————————————————————+

3.2 案例:诊断一个"薛定谔的慢接口"

某单体电商系统,运维反馈/api/product/detail接口"有时候很快,有时候很慢"。不规律,不好复现。

Step 1:SkyWalking端点面板看趋势

发现该接口的响应时间呈双峰分布:大部分请求100ms以内返回,但约有15%的请求耗时超过2秒。

Step 2:挑一个慢请求看Trace

Trace: /api/product/detail?id=12345 (总耗时 2.3s)
├── [EntrySpan] ProductController.detail() (2.3s)
│ ├── [LocalSpan] ProductService.getDetail() (2.28s)
│ │ ├── [ExitSpan] Redis GET product:detail:12345 (5ms) ✅
│ │ ├── [ExitSpan] MySQL SELECT * FROM products (45ms) ✅
│ │ ├── [ExitSpan] HTTP GET stock-service/stock/12345 (2.2s) ⚠️
│ │ │ └── 外部库存服务超时:连接正常但响应极慢
│ │ └── [LocalSpan] 组装数据 (8ms) ✅
│ └── [LocalSpan] 返回视图 (2ms) ✅

Step 3:根因定位

同一个商品ID的数据库和缓存都正常,问题出在外部库存服务。进一步分析发现:当商品库存大于1000时(热销品),库存服务的批量计算逻辑会全表扫描——而且这个逻辑只在少数热销品上触发,所以只有15%的请求受影响。

这就是单体应用中SkyWalking的典型价值场景——虽然只有一个进程,但调用链的复杂度藏在代码里。

3.3 单体应用Top 5慢查询

SkyWalking会自动统计数据库访问,帮你生成"单体应用慢查询清单":

— SkyWalking数据库面板显示的Top 5慢SQL

1. SELECT * FROM orders WHERE status='pending'
ORDER BY created_at — 平均耗时 1.2s,缺少(status, created_at)联合索引

2. SELECT o.*, oi.*, p.* FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.id
WHERE o.user_id = ? — 平均耗时 850ms,三表联查没有覆盖索引

3. UPDATE inventory SET stock = stock ?
WHERE product_id = ? — 平均耗时 200ms,行锁等待

4. SELECT COUNT(*) FROM operation_log
WHERE created_at > ? — 平均耗时 150ms,日志表缺少分区

5. SELECT * FROM products WHERE name LIKE '%手机%'
LIMIT 20 — 平均耗时 120ms,LIKE前缀通配无法用索引

4. Agent对单体应用的影响评估

“接Agent会不会让我的单体应用变慢?”——这是几乎所有团队的第一反应。来,我们用数据说话。

4.1 SkyWalking Agent的性能开销

+——————————————————————+
| SkyWalking Java Agent 性能开销评估 |
+——————————————————————+
| |
| ┌─────────────────┬──────────┬─────────────────────────┐ |
| │ 开销类型 │ 典型值 │ 说明 │ |
| ├─────────────────┼──────────┼─────────────────────────┤ |
| │ CPU开销 │ 1-5% │ 字节码增强的额外计算 │ |
| │ 内存开销 │ 50-200MB│ Buffer/Channel内存占用 │ |
| │ 启动延迟 │ 1-3s │ 类加载时字节码增强 │ |
| │ 请求延迟增加 │ <0.1ms │ 每个Span的创建/上报开销 │ |
| │ GC压力 │ 极低 │ DataCarrier零GC设计 │ |
| │ 网络开销 │ 可忽略 │ gRPC批量上报,压缩传输 │ |
| └─────────────────┴──────────┴─────────────────────────┘ |
| |
| ⚠️ 注意:高吞吐场景(>10000 QPS)CPU开销可能升至5-10% |
| 建议通过采样率控制(默认100%采样,可调低) |
+——————————————————————+

4.2 压测对比数据(某电商单体的实测)

以下数据来自某Spring Boot电商单体应用,4C8G的云服务器:

+——————————————————————+
| 有无Agent的性能对比 |
+——————————————————————+
| |
| 测试场景: 100并发, 持续60秒, /api/product/list |
| |
| ┌────────────────┬────────────┬────────────┬────────┐ |
| │ 指标 │ 无Agent │ 有Agent │ 差异 │ |
| ├────────────────┼────────────┼────────────┼────────┤ |
| │ TPS │ 2450 │ 2320 │ -5.3% │ |
| │ 平均响应时间 │ 41ms │ 43ms │ +4.9% │ |
| │ P99响应时间 │ 120ms │ 128ms │ +6.7% │ |
| │ P999响应时间 │ 350ms │ 380ms │ +8.6% │ |
| │ CPU使用率 │ 65% │ 70% │ +5% │ |
| │ 堆内存使用 │ 2.1GB │ 2.3GB │ +200MB│ |
| └────────────────┴────────────┴────────────┴────────┘ |
| |
| 结论:整体性能损耗在5-8%之间,属于可接受范围 |
+——————————————————————+

4.3 Agent采样率配置

如果你的单体应用QPS很高,可以通过采样率来控制开销:

# agent/config/agent.config
# 采样率:每N个请求采样一个(-1=全采样,1=全采样,2=50%采样)
agent.sample_n_per_3_secs=1

# 或者按服务名配置采样率
plugin.tomcat.collect_http_params=true

# 启动参数方式
java -javaagent:/path/to/skywalking-agent.jar \\
-Dskywalking.agent.sample_n_per_3_secs=2 \\
-jar myapp.jar

5. 单体→微服务迁移的监控过渡方案

5.1 迁移的经典路径

+——————————————————————+
| 单体到微服务迁移路线图 |
+——————————————————————+
| |
| 阶段1: 纯单体 阶段2: 逐步拆分 |
| ┌──────────────────┐ ┌─────────┐ |
| │ MySuperApp │ │ Monolith│ |
| │ 所有模块在一起 │ │ (缩减版) │ |
| └──────────────────┘ └────┬────┘ |
| │ |
| │ 拆出 |
| ▼ |
| ┌─────────────────┐ |
| │ order-service │ |
| │ (新建微服务) │ |
| └─────────────────┘ |
| |
| 阶段3: 持续拆分 阶段4: 全微服务 |
| ┌─────────┐ ┌─────────┐ |
| │ Monolith│ │ order │ |
| │ (核心) │ │ service │ |
| └────┬────┘ └─────────┘ |
| │ ┌─────────┐ |
| ├─────────────────────────────│ user │ |
| │ │ service │ |
| │ └─────────┘ |
| │ ┌─────────┐ |
| └─────────────────────────────│ product │ |
| │ service │ |
| └─────────┘ |
+——————————————————————+

5.2 SkyWalking在迁移过程中的独特价值

迁移前(阶段1):

  • 用SkyWalking识别模块间的调用热力图,"谁调谁"一目了然
  • 找出模块边界,为拆分提供数据依据

迁移中(阶段2-3):

  • 同一个Trace横跨单体和微服务,跨进程调用不再断链
  • 评估拆分后性能变化:原来模块内调用2ms,变成RPC调用20ms,这个tradeoff需要有数据支撑

迁移后(阶段4):

  • 全链路追踪无缝衔接
  • 统一告警配置,统一监控标准

5.3 过渡期的监控配置策略

# 过渡期特殊配置:混合架构下的链路追踪

# 1. 确保TraceId在单体和微服务间传递
# Spring Cloud Feign拦截器
@Component
public class FeignTraceInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// SkyWalking Agent会自动注入trace header
// 这里只需确保Feign的header转发配置正确即可
}
}

# 2. 区分监控视图
# 创建两个Dashboard:
# – "单体模块监控":只看Monolith内部的性能指标
# – "全链路监控":看整体端到端的追踪链路

6. 单体应用Agent配置实战

6.1 Agent安装(零侵入)

# 1. 下载Agent
wget https://dlcdn.apache.org/skywalking/java-agent/9.3.0/apache-skywalking-java-agent-9.3.0.tgz
tar -xzf apache-skywalking-java-agent-9.3.0.tgz -C /opt/

# 2. 启动脚本(生产环境推荐)
#!/bin/bash
export JAVA_OPTS="
-javaagent:/opt/skywalking-agent/skywalking-agent.jar
-DSW_AGENT_NAME=my-shop-monolith
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.1.50:11800
-DSW_LOGGING_LEVEL=WARN
-DSW_AGENT_SAMPLE_N_PER_3_SECS=1
"

java $JAVA_OPTS -jar my-shop-app.jar

6.2 选择性插件启用

单体应用可能使用了各种框架,按需启用Agent插件:

# 选择性启用插件——移动不需要的插件到其他目录
cd /opt/skywalking-agent/plugins

# 单体应用不需要的微服务插件
mkdir -p /tmp/skywalking-disabled
mv apm-feign-default-http-9.x-plugin-*.jar /tmp/skywalking-disabled/
mv apm-spring-cloud-gateway-*.jar /tmp/skywalking-disabled/
mv apm-servicecomb-*.jar /tmp/skywalking-disabled/

# 单体应用必需的插件保留
# apm-spring-mvc-annotation-*.jar –> SpringMVC监控
# apm-jdbc-commons-*.jar –> 数据库监控
# apm-jedis-*.jar –> Redis监控
# apm-httpClient-*.jar –> HTTP调用监控

6.3 排除不需要追踪的端点

# 单体应用不需要追踪的内容(减少开销)
-Dskywalking.trace.ignore_path=/actuator/**,/health,/metrics/**
-Dskywalking.trace.ignore_method="com.example.HealthChecker.*()"

7. 单体应用监控的典型排查流程

+——————————————————————+
| 单体应用SkyWalking排查决策树 |
+——————————————————————+
| |
| 用户反馈"系统变慢了" |
| │ |
| ▼ |
| ┌───────────────────────┐ |
| │ 打开SkyWalking端点面板 │ |
| └───────────┬───────────┘ |
| │ |
| ┌───────────┼───────────┐ |
| ▼ ▼ ▼ |
| 所有接口都慢 特定接口慢 间歇性慢 |
| │ │ │ |
| ▼ ▼ ▼ |
| 检查JVM指标 看Trace详情 采样多个 |
| (GC/CPU/Mem) ┌────┴────┐ Trace对比 |
| │ │ │ │ |
| ▼ ▼ ▼ ▼ |
| Full GC频繁 SQL慢 外部API慢 特定条件触发 |
| →调优JVM →加索引 →加超时 →看Span详情 |
| →优化SQL →加熔断 →定位触发条件 |
+——————————————————————+

8. 总结

单体应用不是"二等公民",SkyWalking对单体应用的价值一点也不打折扣:

  • 接口监控:200个Controller的性能全貌一览无余
  • 慢查询定位:SQL执行耗时直接关联到具体业务接口
  • 外部调用追踪:第三方服务的超时、错误无处遁形
  • 迁移护航:从拆分前分析到拆分后验证,全程数据支撑
  • 性能开销5-8%,换取全链路可见性,这笔账怎么算都划算。

    下一篇文章,我们将把战场从单体服务器搬到Kubernetes集群,聊聊SkyWalking在K8s上的部署实战。


    上一篇【第22篇】SkyWalking告警配置实战——让你的系统学会“喊救命“ 下一篇【第24篇】SkyWalking Kubernetes部署实战——让监控与云原生共舞


    赞(0)
    未经允许不得转载:171主机测评 » 【SkyWalking从入门到精通】第23篇:SkyWalking与单体应用——老系统也能焕发新活力
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址