1. 从单体到微服务:为什么我们需要链路追踪
如果你是从单体应用时代走过来的开发者,第一次接触微服务架构时,大概率会被一个“简单”的请求搞懵。在单体应用里,一个用户登录的请求,从Controller到Service再到DAO,所有逻辑都在一个进程里,出了问题,日志文件一翻,调用栈一看,基本能定位个八九不离十。但到了微服务时代,这个登录请求可能先经过网关,然后调用用户服务验证密码,用户服务又去调权限服务获取角色,权限服务可能还要查询一下缓存服务。这一连串调用横跨了四五个甚至更多的独立进程和服务器。
这时候,当用户反馈“登录很慢”或者“偶尔登录失败”,问题就来了:慢,是慢在哪个环节?是网关转发慢了,还是用户服务数据库查询慢了,亦或是权限服务的网络通信出了问题?失败,是哪个服务抛的异常?异常信息在哪个服务的日志里?传统的日志分散在各个服务的本地文件或日志中心,你需要像侦探一样,根据大概的时间戳,去多个地方翻找、拼接线索,效率极低,而且对于偶发问题,几乎无从下手。
这就是链路追踪要解决的核心问题:在分布式系统中,完整地记录一个请求从发起到结束,所流经的所有服务节点,并收集每个节点的耗时、状态等关键信息,最终形成一个可视化的调用链。它就像给整个分布式系统装上了“X光机”和“行车记录仪”,不仅能看清内部结构,还能回放每一次请求的完整路径。
SkyWalking 正是这个领域的佼佼者之一。它是一个开源的APM(应用性能监控)系统,特别为微服务、云原生和容器化架构设计。它通过探针的方式,以对业务代码极低侵入性的代价,自动采集、聚合、分析和可视化服务之间的调用链路与性能指标。简单说,你只需要在服务中引入一个Agent,它就能帮你把上面提到的“侦探工作”自动化、可视化。
对于正在使用或学习Spring Cloud的开发者来说,集成SkyWalking几乎是构建可观测性体系的必选项。它能帮你:
- 快速定位性能瓶颈:一眼看出调用链中哪个服务、哪个接口、哪个数据库操作耗时最长。
- 精准排查故障:当某个请求失败时,能直接定位到抛出异常的具体服务和方法,并看到完整的错误上下文。
- 理解服务依赖:直观地看到服务之间的调用关系图,对于梳理和治理复杂的微服务架构至关重要。
- 监控服务健康:提供服务的吞吐量、响应时间、SLA(服务等级协议)等关键指标。
接下来,我们就从零开始,看看如何在Spring Cloud项目中集成和使用SkyWalking,让它成为你微服务运维中的“火眼金睛”。
2. SkyWalking 核心架构与部署模式选择
在动手集成之前,花几分钟理解SkyWalking的架构和部署选项,能让你在后续的配置和问题排查中更加得心应手。它的架构非常清晰,主要分为四个部分:探针、后端、存储和UI。
2.1 四大核心组件详解
1. Agent(探针)这是集成到你的业务应用中的部分。它通常以Java Agent的形式(通过-javaagent命令行参数)启动,利用字节码增强技术,在运行时动态修改你的应用类,植入追踪逻辑。它的工作是:
- 采集数据:收集当前应用的调用链路(Trace)、指标(Metrics)和日志(Log)。
- 上下文传递:在服务间调用时(通过HTTP、gRPC、RocketMQ等),自动在请求头中注入Trace ID、Span ID等信息,保证整个调用链的连续性。
- 上报数据:将格式化后的数据,通过gRPC或HTTP协议,发送给后端的OAP Server。
注意:Agent的“字节码增强”方式决定了它的低侵入性。你几乎不需要修改业务代码,这比那些需要手动在代码中埋点(打日志)的方案要友好得多。但这也意味着,它对某些非常规的框架或深度定制的代码可能支持不佳,需要检查官方支持的组件列表。
2. OAP Server(后端)这是SkyWalking的大脑,全称是Observability Analysis Platform Server。它负责:
- 接收数据:接收来自多个Agent上报的链路和指标数据。
- 聚合分析:对数据进行实时流式分析,例如计算服务的每分钟请求量、平均响应时间、错误率等。
- 数据持久化:将处理后的数据存储到配置的存储介质中。
- 提供查询接口:为UI界面提供数据查询的API。
3. Storage(存储)SkyWalking处理后的数据需要存下来。它支持多种存储后端:
- Elasticsearch:生产环境最推荐的选择。它强大的搜索和分析能力非常适合存储和查询链路数据,且SkyWalking对其支持最完善,社区案例最多。
- H2:内嵌的数据库,仅用于演示和快速入门。重启后数据会丢失,绝对不要用于生产环境。
- MySQL/TiDB/PostgreSQL等:也支持关系型数据库,但在处理大规模链路数据时,性能和扩展性通常不如Elasticsearch。
4. UI这就是我们看到的Web管理界面,用于可视化地展示拓扑图、调用链、服务指标、告警信息等。它通过调用OAP Server的GraphQL接口获取数据。
2.2 部署模式:如何选择“全家桶”还是“混合部署”
根据你的团队基础设施现状,通常有两种部署方式:
方式一:独立部署SkyWalking“全家桶”这是最经典的方式。你需要自己部署OAP Server、Storage(如Elasticsearch集群)和UI。这种方式控制力最强,可以针对SkyWalking的特性对存储集群进行独立调优(比如Elasticsearch的分片、副本策略),也方便进行独立的版本升级和维护。
- 适用场景:有一定运维能力的中大型团队,或对数据管控有严格要求的环境。
- 部署复杂度:较高,需要维护至少Elasticsearch和OAP Server两个服务。
方式二:OAP Server + 现有Elasticsearch集群如果你的公司已经有成熟的Elasticsearch集群(通常用于日志存储ELK),那么这是一种非常经济高效的方式。你只需要部署OAP Server和UI,让OAP Server将数据写入现有的ES集群即可。
- 适用场景:已有ES运维能力的团队,希望降低中间件维护成本。
- 优点:资源复用,运维统一。需要注意SkyWalking的数据可能会占用较大的磁盘空间,需要提前和运维团队规划好索引的生命周期管理(ILM)策略。
对于学习和测试,我们当然选择最简单的方式:使用官方Docker镜像快速启动一个包含H2存储的完整环境。但对于生产环境,强烈建议采用“方式二”,即使用独立的Elasticsearch集群或复用现有集群。
3. 实战:使用Docker快速搭建SkyWalking环境
理论清楚了,我们立刻动手搭建一个可用于开发和测试的SkyWalking环境。Docker是最快捷的方式。
3.1 一键启动SkyWalking后端与UI
官方提供了skywalking-oap-server和skywalking-ui的镜像。我们可以使用docker-compose来编排,这样更清晰。
首先,创建一个docker-compose.yml文件:
version: '3.8' services: oap: image: apache/skywalking-oap-server:9.7.0 container_name: skywalking-oap restart: always ports: - "11800:11800" # gRPC端口,用于Agent上报数据 - "12800:12800" # HTTP端口,用于UI界面查询数据 environment: SW_STORAGE: h2 # 使用H2内存数据库,仅用于测试 SW_HEALTH_CHECKER: default JAVA_OPTS: "-Xms256m -Xmx512m" # 根据机器配置调整JVM内存 networks: - skywalking-net ui: image: apache/skywalking-ui:9.7.0 container_name: skywalking-ui depends_on: - oap restart: always ports: - "8080:8080" environment: SW_OAP_ADDRESS: oap:12800 # UI连接OAP Server的地址 networks: - skywalking-net networks: skywalking-net: driver: bridge在这个配置中:
- 我们指定了
9.7.0版本,请根据需要替换为最新稳定版。 - OAP Server 暴露了两个关键端口:
11800(gRPC):这是Agent上报数据的端口,非常重要。12800(HTTP):这是UI和外部API查询数据的端口。
- 存储使用了
h2,重启容器数据即丢失。 - UI 通过环境变量
SW_OAP_ADDRESS指向了OAP服务。
在包含docker-compose.yml的目录下,执行命令启动服务:
docker-compose up -d使用docker ps查看容器状态,当两个容器都处于Up状态后,访问http://localhost:8080即可打开SkyWalking UI界面。
3.2 界面初探与核心概念对应
打开UI界面,你可能觉得有点空,因为还没有应用上报数据。我们先熟悉一下几个核心菜单,它们直接对应了链路追踪的核心概念:
- 仪表盘:这是概览页面,未来会有服务的全局指标,如请求量、响应时间、SLA、慢端点排名等。
- 拓扑图:这是服务依赖关系的可视化。当有多个微服务接入后,你会在这里看到一个清晰的网络图,展示服务之间谁调用了谁,以及调用链路的健康状态(颜色)。
- 追踪:这是查看具体调用链的地方。你可以在这里输入Trace ID(如果你有的话),或者通过服务、端点、时间范围等条件,搜索具体的请求链路。点击一条链路,可以看到详细的树状结构,每个Span(跨度)的耗时、状态一目了然。
- 性能剖析:这是一个更高级的功能,可以对特定端点进行采样剖析,生成火焰图,深入定位代码级别的性能热点。
- 日志:集成应用日志,可以在查看链路的同时,关联查看该请求在对应服务中打印的日志。
- 告警:配置规则,当某些指标达到阈值(如错误率超过5%)时,触发告警,并可以通过Webhook通知到钉钉、企业微信等。
现在,环境已经就绪,只待我们的Spring Cloud应用接入。
4. Spring Cloud应用集成SkyWalking Agent
集成Agent是整个过程中最关键的一步。目标是让我们的Spring Boot应用在启动时,加载SkyWalking Agent,从而自动实现链路追踪。
4.1 获取并配置Agent探针
首先,你需要下载SkyWalking Agent。前往 Apache SkyWalking 官网下载页面 ,找到对应版本的发布包,选择“Binary Distribution for Java Agent”进行下载。解压后,你会得到一个agent目录,其结构如下:
agent/ ├── config/ │ └── agent.config # 主配置文件 ├── plugins/ # 各种插件(支持SpringCloud, Dubbo, MySQL, Redis等) ├── optional-plugins/ # 可选插件 ├── bootstrap-plugins/ # 启动类加载器插件 └── skywalking-agent.jar # 核心Agent Jar包我们主要关心agent.config和skywalking-agent.jar。
配置agent.config:用编辑器打开它,找到并修改以下几个核心配置项:
# 指定服务在SkyWalking中显示的名称 agent.service_name=${SW_AGENT_NAME:Your_Application_Name} # 指定后端OAP Server的地址。我们部署在本机,所以是127.0.0.1 collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800} # 采样率,10000表示100%采样。生产环境可调低以节省存储,如1000(10%) agent.sample_n_per_3_secs=${SW_AGENT_SAMPLE:10000} # 每批上报的链路数据条数 agent.force_tls=${SW_AGENT_FORCE_TLS:false}实操心得:
agent.service_name是你在UI上看到的服务名。建议遵循一定的命名规范,例如{部门}-{项目}-{服务名},如trade-center-payment-service,这样在服务很多时便于管理。配置可以通过环境变量覆盖,这在容器化部署时非常有用(如-DSW_AGENT_NAME=xxx)。
4.2 在IDEA中启动应用并接入Agent(开发环境)
在开发阶段,我们通常直接在IDE(如IntelliJ IDEA)中运行Spring Boot应用。如何加载Agent呢?
1. 通过JVM参数启动(推荐)这是最标准的方式。你需要修改应用的启动配置,添加JVM参数。
- 打开
Run/Debug Configurations。 - 在
VM options一栏中,添加:
请将-javaagent:/path/to/your/agent/skywalking-agent.jar/path/to/your/agent替换为你本地skywalking-agent.jar文件所在的绝对路径。 - 同时,你可以在这里覆盖配置文件中的项,例如:
-javaagent:/path/to/agent/skywalking-agent.jar -Dskywalking.agent.service_name=user-service-dev -Dskywalking.collector.backend_service=127.0.0.1:11800-D参数优先级高于agent.config文件中的配置。
2. 通过环境变量启动SkyWalking Agent也支持通过环境变量读取配置。你可以在启动配置的Environment variables中添加:
SW_AGENT_NAME=user-service-dev;SW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800然后在VM options中只指定-javaagent路径。这种方式在容器化部署时更为常见。
启动你的Spring Boot应用,如果控制台没有报错,并且看到类似[SkyWalking Agent] INFO - SkyWalking agent has been installed on current process.的日志,说明Agent接入成功。
4.3 在JAR包或Docker中启动应用(生产环境)
对于打好的JAR包,在启动命令中加入-javaagent参数即可:
java -javaagent:/opt/skywalking/agent/skywalking-agent.jar \ -Dskywalking.agent.service_name=prod-user-service \ -Dskywalking.collector.backend_service=skywalking-oap:11800 \ -jar your-application.jar对于Docker容器,通常有两种方式将Agent挂载进去:
- 方式一:构建包含Agent的镜像。在Dockerfile中,将Agent目录COPY到镜像内,并在ENTRYPOINT或CMD的java命令中指定
-javaagent。这种方式镜像体积会增大,但部署简单。FROM openjdk:11-jre-slim COPY skywalking-agent /opt/skywalking/agent COPY app.jar /app.jar ENTRYPOINT ["java", "-javaagent:/opt/skywalking/agent/skywalking-agent.jar", "-jar", "/app.jar"] - 方式二:通过Volume挂载Agent。将宿主机上的Agent目录挂载到容器内,通过环境变量传递配置。这种方式更灵活,便于统一升级Agent版本,但需要保证宿主机上有Agent文件。
# docker-compose 示例 services: user-service: image: your-java-app:latest volumes: - /host/path/to/agent:/opt/skywalking/agent environment: JAVA_OPTS: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.service_name=user-service" command: ["java", "-jar", "/app.jar"]踩坑提醒:使用Volume挂载时,务必确保容器内的Java进程有权限读取
/opt/skywalking/agent目录下的文件,否则Agent会加载失败且日志可能不明显。
5. 验证与观测:你的第一个调用链
假设我们有两个简单的Spring Cloud服务:user-service(端口8081)和order-service(端口8082)。user-service提供了一个/user/{id}的接口,当调用它时,它会通过Feign或RestTemplate去调用order-service的/orders?userId={id}接口。
按照第4节的方法,为两个服务分别配置好Agent并启动,确保它们的agent.service_name配置不同(如user-service-dev,order-service-dev),并指向同一个OAP Server。
5.1 生成调用流量并查看拓扑图
使用Postman或浏览器,访问user-service的接口:http://localhost:8081/user/1。
然后,打开SkyWalking UI (http://localhost:8080),进入“拓扑图”页面。稍等几秒钟(数据上报和聚合有短暂延迟),你应该能看到一个简单的拓扑图,显示了两个服务节点,以及一条从user-service指向order-service的连线。连线的颜色和粗细代表了调用的健康状态和流量。
为什么拓扑图如此重要?在微服务架构演进的初期或中期,服务间的依赖关系可能连开发人员都不完全清楚。这个自动生成的拓扑图,是厘清架构、识别循环依赖、发现不合理调用的第一手可视化资料。当新同学加入项目时,让他先看拓扑图,比看一万字文档都管用。
5.2 追踪一次具体的请求
点击拓扑图上的user-service节点,或者直接进入“追踪”页面。在查询条件中,选择服务为user-service,点击查询。
你会看到一条或多条调用记录。点击其中一条,页面会展开一个详细的树状调用链视图。这个视图就是一次分布式请求的完整“故事线”:
- 第一层:一个
EntrySpan,代表进入user-service的HTTP请求,比如GET:/user/{id}。这里会显示总耗时。 - 第二层:在
user-service内部,可能会有一个LocalSpan代表业务逻辑处理,然后下面跟着一个ExitSpan,代表通过Feign调用order-service。这个ExitSpan会显示目标地址和接口。 - 第三层:在
order-service侧,会对应一个EntrySpan,接收来自user-service的请求,执行自己的逻辑。
每一个Span块上,都清晰地标明了耗时。如果order-service的数据库查询很慢,你可能会发现order-service内部的某个LocalSpan耗时特别长。点击这个Span,在右侧的标签页中,你可以看到更详细的信息,比如“日志”标签页里可能会有该Span关联的ERROR日志,“标签”里会有HTTP状态码、URL参数等上下文信息。
排查实战场景:假设用户报障说/user/1接口时快时慢。你不需要去两个服务里翻日志,只需要在“追踪”页面筛选该接口,按耗时排序,找到那些慢的Trace。点开慢的Trace,对比快的Trace,差异点一目了然——可能是某一次order-service的数据库查询突然慢了500ms。你的排查范围瞬间从一个模糊的“系统慢”缩小到了一个具体的服务和一个具体的数据库操作。
5.3 理解Trace、Span、Segment与Context
看到UI上的数据后,我们来深化一下对底层概念的理解,这有助于你读懂SkyWalking的数据模型和后续的进阶配置。
- Trace:代表一个完整的请求链路。例如,一次从网关到用户服务再到订单服务的请求,就是一个Trace。它在整个链路中保持唯一,即Trace ID。
- Span:代表一个完整链路中的一个环节,是基本的工作单元。例如,在用户服务内处理业务逻辑是一个Span,用户服务调用订单服务是另一个Span。每个Span有自己的Span ID。
- Segment:这是SkyWalking特有的概念。一个Segment 代表一个服务内部的一段执行片段。一个Trace由多个服务的Segment组成,一个Segment包含多个Span。例如,整个
user-service对这次请求的处理,会生成一个Segment,里面包含了接收请求、业务逻辑、调用下游等多个Span。 - Context:上下文,用于在服务间传递Trace ID、Span ID、采样决策等信息。这是保证链路连续性的关键,Agent会自动通过HTTP头(如
sw8)、MQ消息头等方式进行传播。
当你在UI上点击一个Trace时,看到的树状结构,实际上是以Segment为维度组织起来的Span树。理解这些概念,能帮助你在阅读官方文档或排查复杂链路问题时更加清晰。
6. 生产环境进阶配置与避坑指南
将SkyWalking用于生产环境,远不止启动Agent那么简单。下面是一些关键的进阶配置和实践中容易踩到的坑。
6.1 存储选型与Elasticsearch配置
如前所述,生产环境必须抛弃H2,选择Elasticsearch。修改OAP Server的配置(如果是Docker部署,可以挂载配置文件或使用环境变量)。
关键配置 (config/application.yml或环境变量):
# 选择 Elasticsearch 7.x 作为存储 storage: selector: ${SW_STORAGE:elasticsearch7} elasticsearch7: namespace: ${SW_NAMESPACE:""} clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200} protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"} connectTimeout: ${SW_STORAGE_ES_CONNECT_TIMEOUT:5000} socketTimeout: ${SW_STORAGE_ES_SOCKET_TIMEOUT:30000} user: ${SW_ES_USER:""} password: ${SW_ES_PASSWORD:""} indexShardsNumber: ${SW_STORAGE_ES_INDEX_SHARDS_NUMBER:2} # 索引分片数,根据数据量调整 indexReplicasNumber: ${SW_STORAGE_ES_INDEX_REPLICAS_NUMBER:1} # 索引副本数,建议>=1保证高可用 # 索引滚动策略,非常重要!控制历史数据保留 recordDataTTL: ${SW_STORAGE_ES_RECORD_DATA_TTL:90} # 链路明细数据保留90天 otherMetricsDataTTL: ${SW_STORAGE_ES_OTHER_METRIC_DATA_TTL:45} # 其他指标数据保留45天 monthMetricsDataTTL: ${SW_STORAGE_ES_MONTH_METRIC_DATA_TTL:18} # 月度聚合数据保留18个月clusterNodes: 填写你的ES集群地址,多个节点用逗号分隔。indexShardsNumber和indexReplicasNumber: 需要根据你的数据规模和ES集群规模来设定。分片数过多或过少都会影响性能。recordDataTTL:这是最重要的配置之一。链路明细数据(就是你在“追踪”页面看到的数据)体积增长非常快。默认只保留3天,生产环境建议根据存储容量和排查需求设置,比如7天或30天。设置太短,可能无法排查几天前的问题;设置太长,存储成本会激增。
血泪教训:曾经有一次线上事故,排查时需要看三天前的调用链,发现已经查不到了,因为TTL设置的是默认的3天。从此以后,上线前必查此配置。同时,务必为ES集群监控磁盘使用量,并设置好索引的ILM(生命周期管理)策略,避免磁盘被撑满。
6.2 Agent采样率与性能开销权衡
Agent的采样率 (agent.sample_n_per_3_secs) 决定了收集多少请求的链路数据。10000代表100%采样。对于超高QPS的服务,全量采样会给OAP Server和存储带来巨大压力。
配置建议:
- 核心交易链路、错误请求:建议保持高采样率或全采样,确保问题能被捕捉。
- 高流量、非核心查询接口:可以降低采样率,如10%(配置为1000)或1%(配置为100)。
- 动态采样:SkyWalking支持基于特定条件的采样,例如对所有错误请求(status code >= 500)进行全采样,对正常请求进行低采样。这需要在
agent.config中配置更复杂的agent.sampler参数。
性能开销:Agent本身带来的性能损耗(主要是CPU)通常在3%~5%以内,对于大多数应用是可接受的。主要的压力在于后端的数据处理与存储。通过合理的采样率配置,可以将其控制在合理范围。
6.3 插件管理与自定义增强
SkyWalking通过插件来支持各种框架和组件。agent/plugins目录下的jar包就是插件。默认情况下,Agent会加载所有插件。
- 禁用不需要的插件:如果你的应用没有使用Kafka、RabbitMQ等,可以将对应的插件jar包从
plugins目录移到optional-plugins目录,以减少启动时的类加载和潜在冲突。 - 自定义追踪:如果你想追踪一些SkyWalking默认不支持的组件或自定义方法,可以使用
@Trace注解或TraceContextAPI进行手动埋点。这属于进阶用法,在官方文档中有详细说明。 - 插件冲突:极少数情况下,SkyWalking的插件可能会和其他基于字节码增强的组件(如某些APM工具、热部署工具)冲突。如果遇到无法解释的ClassNotFound或方法签名错误,可以尝试通过
agent.ignore_suffix配置排除某些类的增强,或者排查插件冲突。
6.4 常见问题排查链路
问题一:UI上看不到服务或没有数据
- 检查Agent日志:应用启动时,查看控制台是否有Agent成功加载的日志,是否有连接OAP Server失败的异常。
- 检查OAP Server日志:查看OAP Server容器或进程的日志,看是否有收到数据上报,是否有处理错误。
docker logs -f skywalking-oap - 检查网络连通性:确保应用所在机器能访问OAP Server的
11800端口。可以在应用服务器上用telnet oap-server-ip 11800测试。 - 检查配置:双重检查
agent.config或JVM参数中的service_name和backend_service是否正确。
问题二:调用链不连续,在某个服务处断掉
- 检查上下文传播:这通常是因为服务间的调用没有正确传递Trace上下文。确保你使用的是SkyWalking支持版本的RPC框架(如Spring Cloud OpenFeign, RestTemplate等)。对于自定义的HTTP客户端,需要手动从
ContextManager.getGlobalTraceContext()中获取并设置sw8头。 - 检查采样率:如果调用链在入口服务有记录,下游没有,可能是下游服务的采样率设置过低,导致该请求未被采样。
- 检查插件支持:确认你使用的通信组件(如某个特定版本的MQ)是否有对应的插件支持,或者插件是否已启用。
问题三:OAP Server CPU或内存占用过高
- 检查数据量:首先去ES中查看SkyWalking索引的数据量和增长速度。过高的采样率是首要怀疑对象。
- 调整OAP JVM参数:在OAP启动脚本中增加JVM内存参数,如
-Xms4g -Xmx4g。 - 调整OAP处理线程:在
application.yml中调整core.default.grpcThreadPoolSize和core.default.restThreadPoolSize等参数。 - 考虑集群部署:对于超大规模系统,可以将OAP Server部署为集群模式,分担压力。
将SkyWalking集成到Spring Cloud只是第一步,让它真正在你的生产环境中稳定、高效地运行,需要根据实际的流量、架构和运维能力进行细致的调优和长期的观察。它不再是一个简单的工具,而是你微服务系统可观测性体系中不可或缺的一部分。