【架构实战】链路追踪SkyWalking:让请求无所遁形

一、一次排查让我崩溃的经历

那年双十一,凌晨两点,系统突然报警:订单成功率从99%掉到了85%。

我开始排查:

  • 看监控:订单服务的RT升高了
  • 看日志:没有明显的错误
  • 看调用:不确定是哪个环节慢

一个接口涉及5个服务,每个服务都有日志。我翻遍了所有日志,翻了整整3个小时,才定位到是Redis连接池被打满了。

那一刻,我崩溃了。

后来接触到SkyWalking,我才知道:原来请求链路追踪可以这么简单。


二、为什么需要链路追踪?

2.1 微服务架构的问题

复制代码
一个用户下单请求的背后:

用户 → 网关 → 订单服务 → 用户服务
                      ↓
                  库存服务 → Redis
                      ↓
                  支付服务 → 第三方支付
                      ↓
                  物流服务 → 消息队列 → 物流系统

问题:
1. 一次请求涉及10+个服务
2. 日志分散在各个服务
3. 出现问题时,不知道是哪个环节的锅
4. 调用关系复杂,无法直观看到

2.2 链路追踪的核心概念

java 复制代码
// Trace:一次完整的请求链路
// Span:链路中的一个操作节点
// Context:传递上下文(TraceId、SpanId等)

/*
┌──────────── Trace ────────────────────────────────────────────┐
│                                                              │
│  Span1: 网关处理                                              │
│    ├─ Span2: 订单服务                                         │
│    │    ├─ Span3: 用户服务(RPC)                              │
│    │    ├─ Span4: 库存服务(RPC)                              │
│    │    │    └─ Span5: Redis查询                              │
│    │    └─ Span6: 支付服务(RPC)                              │
│    │         └─ Span7: 第三方支付(HTTP)                      │
│    └─ Span8: 记录日志                                         │
│                                                              │
└──────────────────────────────────────────────────────────────┘

每个Span包含:
- operationName: 操作名称
- startTime: 开始时间
- duration: 耗时
- tags: 标签(如http.status_code=200)
- logs: 日志(异常信息等)
*/

三、SkyWalking架构

3.1 整体架构

复制代码
┌──────────────────────────────────────────────────────────────────┐
│                         SkyWalking                               │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │              SkyWalking UI(可视化界面)                    │ │
│  │  - 链路拓扑图                                               │ │
│  │  - 调用链路详情                                             │ │
│  │  - 性能指标                                                 │ │
│  │  - 告警配置                                                 │ │
│  └────────────────────────────────────────────────────────────┘ │
│                              ↑                                   │
│                              │ REST/gRPC                        │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │              OAP Server(分析服务)                          │ │
│  │  - 接收Trace数据                                            │ │
│  │  - 聚合分析                                                 │ │
│  │  - 指标计算                                                 │ │
│  │  - 告警触发                                                 │ │
│  └────────────────────────────────────────────────────────────┘ │
│                              ↑                                   │
│                              │ Kafka/GRPC                      │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │              SkyWalking Agent(探针)                        │ │
│  │  Java Agent: javaagent:skywalking-agent.jar                 │ │
│  │  自动埋点:Spring Cloud、Dubbo、gRPC、OkHttp、Redis...       │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Agent → OAP Server → UI

3.2 数据流转

复制代码
┌─────────┐    gRPC/RabbitMQ/Kafka    ┌──────────┐    gRPC/REST    ┌─────────┐
│ Service │ ─────────────────────────▶ │ OAP      │ ──────────────▶ │   UI    │
│ + Agent │                            │ Server   │                 │         │
└─────────┘                            └──────────┘                 └─────────┘
                                           ↑
                                           │ H2/ES/MySQL/TiDB
                                    ┌──────────────┐
                                    │  Storage     │
                                    │  (时序存储)  │
                                    └──────────────┘

四、SkyWalking快速安装

4.1 下载安装

bash 复制代码
# 下载SkyWalking(选择带ES7的版本)
wget https://archive.apache.org/dist/skywalking/9.5.0/apache-skywalking-apm-9.5.0.tar.gz

# 解压
tar -xzf apache-skywalking-apm-9.5.0.tar.gz
cd apache-skywalking-apm-bin

# 目录结构
ls -la
# bin/          启动脚本
# agent/        Java Agent探针
# config/       配置文件
# oap-libs/     OAP服务依赖
# webapp/       UI前端

# 配置存储(使用H2内置数据库,生产建议用ES)
# 修改 config/application.yml 中的 storage 配置

4.2 启动OAP服务

bash 复制代码
# 启动OAP服务(默认端口11800 gRPC,12800 REST)
./bin/startup.sh

# 或者分开启动
./bin/oapService.sh     # 启动OAP
./bin/webappService.sh  # 启动UI

# 验证
curl http://localhost:12800/health
# 返回 {"status":"UP"} 表示正常

4.3 访问UI

复制代码
UI地址:http://localhost:12800

默认功能:
- Dashboard:总览仪表盘
- Topology:服务拓扑图
- Trace:链路追踪
- Alarm:告警记录
- Service:服务列表

五、Java应用接入SkyWalking

5.1 引入依赖

xml 复制代码
<!-- Spring Boot项目无需额外引入依赖,只需加载Agent -->
<!-- 但如果有特殊需求,可以引入以下依赖 -->

<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>9.5.0</version>
</dependency>

<!-- 链路上下文传递 -->
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>9.5.0</version>
</dependency>

<!-- OpenTelemetry支持 -->
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-opentelemetry</artifactId>
    <version>9.5.0</version>
</dependency>

5.2 Agent配置

bash 复制代码
# 启动参数
java -javaagent:skywalking-agent.jar \
     -Dskywalking.agent.service_name=order-service \
     -Dskywalking.collector.backend_service=localhost:11800 \
     -jar order-service.jar
yaml 复制代码
# skywalking-agent.conf 配置
agent:
  # 服务名
  service_name=${SW_AGENT_NAME:order-service}
  # 实例名,默认是主机名+IP
  instance_name=${SW_AGENT_INSTANCE_NAME:order-instance-1}
  # 每3秒采样一个请求,0表示全采样
  sample_n_per_3_secs=0
  # 最长追踪链路深度
  max_depth=500
  # 队列最大缓冲数
  buffer_size=30000

collector:
  # OAP服务地址
  backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:localhost:11800}
  # gRPC通信方式
  grpc_channel_check_interval=5000
  grpc_channel_reconnect_period=30

plugin:
  # 追踪的段最大跨度
  trace.ignore_path=/health,/info,/metrics
  # Spring Bean异步方法追踪
  spring.bean.invoke.enabled=true
  # Kotlin协程追踪
  kotlin.coroutine.enabled=true

5.3 Docker部署配置

dockerfile 复制代码
# Dockerfile
FROM openjdk:8-jdk-alpine

WORKDIR /app

COPY target/order-service.jar app.jar
COPY skywalking-agent agent/

ENV JAVA_OPTS="-javaagent:agent/skywalking-agent.jar \
               -Dskywalking.agent.service_name=order-service \
               -Dskywalking.collector.backend_service=oap:11800"

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
yaml 复制代码
# docker-compose.yml
version: '3.8'
services:
  oap:
    image: apache/skywalking-oap-server:9.5.0-es7
    ports:
      - "11800:11800"
      - "12800:12800"
  
  order-service:
    build: ./order-service
    environment:
      SW_AGENT_NAME: order-service
      SW_AGENT_COLLECTOR_BACKEND_SERVICES: oap:11800

六、手动埋点:让追踪更精确

6.1 获取当前Trace上下文

java 复制代码
import org.apache.skywalking.apm.toolkit.trace.TraceContext;

@Service
@Slf4j
public class OrderService {

    public void createOrder(OrderDTO order) {
        // 获取当前TraceID
        String traceId = TraceContext.traceId();
        log.info("当前TraceID: {}", traceId);
        
        // 获取当前SpanID
        int spanId = TraceContext.spanId();
        log.info("当前SpanID: {}", spanId);
    }
}

6.2 自定义埋点

java 复制代码
import org.apache.skywalking.apm.toolkit.trace.Traced;

@Service
@Slf4j
public class PaymentService {

    /**
     * @Traced 标记此方法为一个独立的Span
     */
    @Traced(operationName = "PaymentService.processPayment")
    public PaymentResult processPayment(String orderId, BigDecimal amount) {
        long start = System.currentTimeMillis();
        
        try {
            // 调用支付渠道
            PaymentResponse response = callPaymentGateway(orderId, amount);
            
            // 记录结果标签
            ActiveSpan.tag("payment.status", response.getStatus());
            ActiveSpan.tag("payment.channel", response.getChannel());
            
            return PaymentResult.success(response);
            
        } catch (Exception e) {
            // 记录错误日志到Span
            ActiveSpan.error();
            ActiveSpan.log(e);
            return PaymentResult.fail(e.getMessage());
        } finally {
            log.info("支付耗时: {}ms", System.currentTimeMillis() - start);
        }
    }
    
    /**
     * 带参数追踪
     */
    @Traced(operationName = "PaymentService.callChannel",
            argsWith = {"orderId", "amount"})
    private PaymentResponse callPaymentGateway(String orderId, BigDecimal amount) {
        // ...
        return null;
    }
}

6.3 异步任务追踪

java 复制代码
@Service
@Slf4j
public class AsyncTaskService {

    @Autowired
    private Executor asyncExecutor;

    /**
     * 异步方法追踪
     * 需要使用 @TraceChain 配合
     */
    @Async
    public void sendNotificationAsync(String userId, String message) {
        // SkyWalking Agent会自动处理@Async的追踪
        notificationClient.send(userId, message);
    }

    /**
     * 手动传递TraceContext
     */
    public void executeWithContext(String taskId) {
        // 在主线程获取TraceContext
        String traceId = TraceContext.traceId();
        String segmentId = TraceContext.segmentId();
        int spanId = TraceContext.spanId();
        
        // 在新线程中继续追踪
        asyncExecutor.execute(() -> {
            // 继续之前的链路
            TraceContext continued = TraceContext.continued(
                new TraceSegment.Builder(traceId, segmentId, spanId)
            );
            
            try (ActiveSpan scope = continued.start()) {
                // 执行异步任务
                processTask(taskId);
            }
        });
    }
}

6.4 跨进程传递

java 复制代码
// Feign调用时自动传递TraceId
@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor traceInterceptor() {
        return (Template template, feign.RequestTemplate requestTemplate) -> {
            // 添加TraceId到请求头
            requestTemplate.header("X-Trace-Id", TraceContext.traceId());
            requestTemplate.header("X-Span-Id", String.valueOf(TraceContext.spanId()));
        };
    }
}

// 或者使用SkyWalking提供的Header传递
@Configuration
public class SkyWalkingConfig {

    @Bean
    public RequestInterceptor skyWalkingInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                // SkyWalking会自动在header中添加sw8相关header
                // 只需要确保下游服务也接入了Agent即可
            }
        };
    }
}

七、实战:订单服务链路追踪

7.1 场景描述

复制代码
用户下单请求链路:

用户 → 网关 → 订单服务 → 用户服务(RPC)
                  ↓
              库存服务(RPC)→ Redis缓存
                  ↓
              支付服务(RPC)→ 第三方支付(HTTP)
                  ↓
              消息队列 → 物流服务

7.2 完整代码

java 复制代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 创建订单
     */
    @PostMapping("/create")
    @Traced(operationName = "OrderController.createOrder")
    public Result<Order> createOrder(@RequestBody @Validated CreateOrderRequest request) {
        // 获取TraceID
        String traceId = TraceContext.traceId();
        log.info("创建订单开始, traceId={}, userId={}", traceId, request.getUserId());
        
        // 记录请求标签
        ActiveSpan.tag("user.id", request.getUserId());
        ActiveSpan.tag("order.type", request.getOrderType());
        
        try {
            Order order = orderService.createOrder(request);
            log.info("创建订单成功, traceId={}, orderId={}", traceId, order.getId());
            return Result.success(order);
        } catch (Exception e) {
            ActiveSpan.error();
            ActiveSpan.log(e);
            log.error("创建订单失败, traceId={}", traceId, e);
            throw e;
        }
    }
}

@Service
@Slf4j
public class OrderService {

    @Autowired
    private UserClient userClient;
    
    @Autowired
    private InventoryClient inventoryClient;
    
    @Autowired
    private PaymentClient paymentClient;
    
    @Autowired
    private RocketMQTemplate mqTemplate;

    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        // 1. 验证用户
        @Traced(operationName = "OrderService.validateUser")
        User user = userClient.getUser(request.getUserId());
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        ActiveSpan.tag("user.status", user.getStatus());

        // 2. 检查库存
        @Traced(operationName = "OrderService.checkInventory")
        Inventory inventory = inventoryClient.check(request.getSkuId(), request.getQuantity());
        if (!inventory.isAvailable()) {
            throw new BusinessException("库存不足");
        }
        ActiveSpan.tag("inventory.available", String.valueOf(inventory.getAvailableQuantity()));

        // 3. 计算价格
        BigDecimal totalAmount = calculatePrice(request);
        ActiveSpan.tag("order.amount", totalAmount.toString());

        // 4. 创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setTotalAmount(totalAmount);
        order.setStatus(OrderStatus.PENDING);
        order = orderRepository.save(order);

        // 5. 扣减库存
        inventoryClient.deduct(request.getSkuId(), request.getQuantity());

        // 6. 发送订单创建消息
        mqTemplate.convertAndSend("order-topic", "order-create", order);

        return order;
    }

    private BigDecimal calculatePrice(CreateOrderRequest request) {
        // 实际计算逻辑
        return request.getQuantity().multiply(new BigDecimal("100"));
    }
}

// Feign客户端
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
    
    @GetMapping("/user/{userId}")
    User getUser(@PathVariable String userId);
}

@Component
@Slf4j
public class UserClientFallback implements UserClient {
    
    @Override
    public User getUser(String userId) {
        log.warn("User服务调用失败,降级返回, userId={}", userId);
        User fallback = new User();
        fallback.setId(userId);
        fallback.setName("Unknown");
        fallback.setStatus("UNKNOWN");
        return fallback;
    }
}

八、告警配置

8.1 告警规则

yaml 复制代码
# alarm-settings.yml
rules:
  # 服务响应时间告警
  service_resp_time_rule:
    metrics-name: service_resp_time
    op: ">"
    threshold: 1000  # 超过1000ms告警
    period: 10  # 10分钟内
    count: 3  # 出现3次触发告警
    silence-period: 5  # 沉默5分钟
    message: "服务 {name} 响应时间超过 {threshold}ms"

  # 服务成功率告警
  service_success_rate_rule:
    metrics-name: service_success_rate
    op: "<"
    threshold: 95  # 成功率低于95%
    period: 10
    count: 3
    message: "服务 {name} 成功率低于 {threshold}%"

  # 慢SQL告警
  database_access_resp_time_rule:
    metrics-name: database_access_resp_time
    op: ">"
    threshold: 500
    period: 10
    count: 2
    message: "数据库访问超过 {threshold}ms"

  # 熔断告警
  circuit_breaker_error_rate:
    metrics-name: service_circuit_breaker_error_rate
    op: ">"
    threshold: 0.5  # 熔断错误率超过50%
    period: 5
    count: 1
    message: "服务 {name} 触发熔断"

webhooks:
  # 告警回调地址
  - http://192.168.1.100:8080/alert/webhook

8.2 自定义告警

java 复制代码
@Component
@Slf4j
public class CustomAlertHandler {
    
    /**
     * 接收SkyWalking告警
     */
    @PostMapping("/alert/webhook")
    public void receiveAlert(@RequestBody SkyWalkingAlert alert) {
        log.info("收到SkyWalking告警: id={}, name={}, severity={}",
            alert.getId(), alert.getName(), alert.getScope());
        
        for (AlertItem item : alert.getAlerts()) {
            // 发送通知
            sendNotification(item);
        }
    }
    
    private void sendNotification(AlertItem item) {
        // 1. 发送企业微信通知
        wechatClient.send(item);
        
        // 2. 发送钉钉通知
        dingTalkClient.send(item);
        
        // 3. 发送邮件
        emailClient.send(item);
    }
}

九、常见问题排查

9.1 服务没有出现在拓扑图

bash 复制代码
# 排查步骤:
# 1. 检查Agent是否启动成功
# 查看Agent日志
tail -f logs/skywalking-api.log

# 2. 检查Agent配置
# 确认 service_name 和 backend_service 配置正确

# 3. 检查网络连通性
telnet oap-server 11800

# 4. 确认应用启动参数
jps -l | grep order-service
ps aux | grep skywalking-agent

9.2 链路中断

yaml 复制代码
# 如果链路在某个服务后中断,可能是以下原因:

# 1. 服务没有接入Agent
# 解决方案:添加 -javaagent 参数

# 2. 服务使用了异步线程池
# 解决方案:配置异步追踪
spring:
  cloud:
    gateway:
      filter:
        # 配置异步传递
        request-trace-enabled: true

# 3. 服务使用了消息队列
# 解决方案:配置MQ追踪插件
# SkyWalking已支持 Kafka、RocketMQ、RabbitMQ 等

9.3 数据丢失

bash 复制代码
# 如果Trace数据丢失,检查:
# 1. OAP服务的存储是否正常
# 2. Kafka队列是否积压
# 3. Agent的buffer_size是否足够

# 增加buffer配置
agent:
  buffer_size: 30000  # 增加缓冲区大小

十、踩坑实录

坑1:Agent版本与OAP版本不匹配

新手上路时,OAP用9.5.0,Agent用了9.3.0,结果数据对不上。

教训:SkyWalking要求Agent版本和OAP版本必须匹配,版本差异太大会导致不兼容。

坑2:生产环境性能问题

生产环境接了SkyWalking后,CPU飙升了15%。

原因:采样率设置太高,链路数据太多。

解决:调整采样率,不追踪静态资源。

yaml 复制代码
# 生产环境配置
agent:
  # 每3秒采样一个请求,减少开销
  sample_n_per_3_secs=1
  
  # 不追踪健康检查接口
  exclude_activities=/actuator/**
  
  # 不追踪静态资源
  plugin:
    trace:
      ignore_path=/health,/metrics,/static/**

坑3:异步任务链路丢失

使用线程池的异步任务,链路总是中断。

解决 :使用SkyWalking提供的TracingRunnableTracingCallable

java 复制代码
@Service
public class AsyncService {

    private Executor executor = Executors.newFixedThreadPool(10);

    public void executeAsync() {
        // 错误方式:链路会丢失
        executor.execute(() -> doSomething());

        // 正确方式:传递Trace上下文
        Runnable wrapped = TracingRunnable.of(new Runnable() {
            @Override
            public void run() {
                doSomething();
            }
        }, "async-task");
        executor.execute(wrapped);
    }
}

坑4:日志中没有TraceID

日志满天飞,但不知道哪条对应哪个请求。

解决:使用SkyWalking的Logback插件,自动在日志中添加TraceID。

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>9.5.0</version>
</dependency>

<!-- logback.xml -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{tid}] %-5level %logger{36} - %msg%n</pattern>
        <!-- %X{tid} 会自动输出TraceID -->
    </encoder>
</appender>

十一、总结

SkyWalking是微服务链路追踪的利器:

  • 无侵入接入:Java Agent方式,无需修改代码
  • 自动埋点:支持主流框架(Spring Cloud、Dubbo等)
  • 链路追踪:清晰看到每个请求的完整链路
  • 拓扑图:直观展示服务调用关系
  • 告警:可配置多种告警规则

最佳实践:

  1. Agent和OAP版本必须一致
  2. 生产环境要调整采样率
  3. 日志中要包含TraceID
  4. 异步任务要手动传递Trace上下文
  5. 定期清理历史数据,避免存储爆炸

血的教训:

不要以为接入了SkyWalking就高枕无忧了。如果采样率配置不对,数据量会非常大,影响性能。另外,生产环境的告警阈值要设置合理,太敏感会收到很多无用告警,太迟钝又可能漏掉真正的问题。

思考题: 你的项目有用链路追踪吗?如果链路在某处中断了,通常是什么原因?


个人观点,仅供参考

相关推荐
ZStack开发者社区1 小时前
智算云时代,ZStack如何在实践中重塑全栈硬件加速架构?
架构
代钦塔拉1 小时前
CPU架构篇:Intel、AMD与x86、x64、ARM全解析
arm开发·架构
郑寿昌1 小时前
AI重构存储:2026智能数据革命
人工智能·架构
日取其半万世不竭1 小时前
Consul:服务发现与健康检查,微服务架构的注册中心
架构·服务发现·consul
平凡灵感码头1 小时前
MCU 组成原理详解—— 从硬件框图透视微控制器的完整架构
单片机·嵌入式硬件·架构
小短腿的代码世界2 小时前
Qwt实时FFT频谱分析深度解析:从信号采集到可视化渲染的完整架构设计
前端·qt·架构·交互
richard_yuu2 小时前
鸿蒙Stage模型实战|心晴驿站分层架构与隐私安全设计
安全·架构·harmonyos
上海云盾第一敬业销售2 小时前
深度解析:CDN如何实现动态内容加速架构解析
架构
零壹AI实验室2 小时前
Qwen3 技术报告精读:阿里通义千问第三代架构全解析
架构