跳到主要内容SkyWalking 接入 Spring Cloud Alibaba 微服务:从链路追踪到告警 | 极客日志Javajava
SkyWalking 接入 Spring Cloud Alibaba 微服务:从链路追踪到告警
记录Spring Cloud Alibaba微服务集成SkyWalking的全过程:从Docker Compose启动Nacos和SkyWalking,到构建order和inventory两个服务的调用链,然后通过Java Agent接入追踪,展示Topology和Trace详情。进一步说明了手动埋点、告警配置、日志TraceId注入和性能剖析的实用做法,最后给出了生产环境存储选型、采样率控制和安全隔离等实践建议。
一个请求要穿过七八个服务,早就不是新鲜事了。但问题一来——到底慢在哪个环节,是数据库堵了还是下游超时——光靠猜可不行。全链路追踪就是给你一双眼睛,直接看清整个调用链。
SkyWalking 在开源 APM 里算得上老牌选手了,支持分布式追踪、拓扑分析、性能指标聚合,而且与 Spring Cloud Alibaba 生态配合起来很顺手。这里就记一下我实际搭建的过程,从拉服务、接 Agent,到手动埋点和告警,把最实用的部分串一遍。
先把骨架跑起来
用 Docker Compose 一把梭启动 SkyWalking 和 Nacos,省去单独部署的麻烦。创建一个 docker-compose.yml:
version: '3.8'
services:
nacos:
image: nacos/nacos-server:v2.2.3
container_name: nacos-standalone
environment:
- MODE=standalone
ports:
- "8848:8848"
- "9848:9848"
restart: always
oap:
image: apache/skywalking-oap-server:9.7.0
container_name: skywalking-oap
ports:
- "11800:11800"
- "12800:12800"
environment:
- SW_STORAGE=h2
depends_on:
- nacos
restart: always
ui:
image: apache/skywalking-ui:9.7.0
container_name: skywalking-ui
ports:
-
"8080:8080"
environment:
-
SW_OAP_ADDRESS=oap:12800
depends_on:
-
oap
restart:
always
执行 docker-compose up -d,等个两分钟左右,Nacos 在 http://localhost:8848/nacos,SkyWalking UI 在 http://localhost:8080,就能打开了。
搭一个最简单的调用链
模拟两个服务:order-service 下单,inventory-service 扣库存。通过 Feign 调用,服务注册到 Nacos。
父工程统一管理依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>skywalking-sca-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>order-service</module>
<module>inventory-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2021.0.8</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
inventory-service:扣库存
添加 Nacos 发现依赖和 Web 起步,写一个 /deduct 接口,模拟 200ms 处理延迟:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>skywalking-sca-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>inventory-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8082
spring:
application:
name: inventory-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
package com.example.inventory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class InventoryServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}
}
package com.example.inventory.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/inventory")
public class InventoryController {
private static final Logger log = LoggerFactory.getLogger(InventoryController.class);
@PostMapping("/deduct")
public String deduct(@RequestParam String productId, @RequestParam Integer count) {
log.info("收到扣减库存请求:productId={}, count={}", productId, count);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("库存扣减成功");
return "success";
}
}
order-service:下单并调用库存
同样注册到 Nacos,通过 Feign 调 inventory-service。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>skywalking-sca-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8081
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
management:
endpoints:
web:
exposure:
include: '*'
package com.example.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "inventory-service")
public interface InventoryFeignClient {
@PostMapping("/inventory/deduct")
String deduct(@RequestParam("productId") String productId, @RequestParam("count") Integer count);
}
package com.example.order.controller;
import com.example.order.feign.InventoryFeignClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Autowired
private InventoryFeignClient inventoryFeignClient;
@PostMapping("/create")
public String createOrder(@RequestParam String userId, @RequestParam String productId, @RequestParam Integer count) {
log.info("收到创建订单请求:userId={}, productId={}, count={}", userId, productId, count);
String result = inventoryFeignClient.deduct(productId, count);
log.info("订单创建成功");
return "order created, inventory result: " + result;
}
}
现在依次启动两个服务,用 curl 模拟一下请求:
curl -X POST "http://localhost:8081/order/create?userId=1&productId=P001&count=1"
返回 order created, inventory result: success,调用链通了。但是没有追踪,下面就把 SkyWalking 接上去。
用 SkyWalking Agent 把链路照亮
SkyWalking 靠 Java Agent 实现无侵入埋点。你只需要在启动参数里加一个 jar 包路径,它就能自动拦截 Spring MVC、Feign、JDBC 等常见组件。
下载与 SkyWalking 9.7.0 对应的 Agent 包,解压到比如 /opt/skywalking-agent。里面有一个核心 skywalking-agent.jar 和 config 目录。
给每个服务准备一个配置文件,主要是声明服务名和 OAP 地址。在 config/ 下新建 order-service.config:
agent.service_name=order-service
collector.backend_service=127.0.0.1:11800
inventory-service.config:
agent.service_name=inventory-service
collector.backend_service=127.0.0.1:11800
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.config_path=/opt/skywalking-agent/config/order-service.config \
-jar order-service/target/order-service-1.0.0.jar
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.config_path=/opt/skywalking-agent/config/inventory-service.config \
-jar inventory-service/target/inventory-service-1.0.0.jar
再跑一次 curl,然后打开 SkyWalking UI。你会看到一幅拓扑图,order 到 inventory 的箭头清清楚楚。点进 Trace,整条链的瀑布流展示每段耗时——200ms 的 Thread.sleep 一眼就能看见。
埋点不够自动?自己加两笔
自动追踪覆盖了大部分场景,但有些业务方法或者日志环节你想更细粒度,可以手动创建 Span。
在 order-service 里加一个插入数据库的操作(这里用 H2 演示),然后对数据库调用打个 Span。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: password
public class Order {
private Long id;
private String userId;
private String productId;
private Integer count;
}
@Mapper
public interface OrderMapper {
@Insert("INSERT INTO orders(user_id, product_id, count) VALUES(#{userId}, #{productId}, #{count})")
void insert(Order order);
}
在 Service 里用 @Trace 给整个方法生成 Span,并对数据库保存单独包裹一个 TraceContext.createLocalSpan:
package com.example.order.service;
import com.example.order.feign.InventoryFeignClient;
import com.example.order.mapper.OrderMapper;
import com.example.order.model.Order;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private OrderMapper orderMapper;
@Trace
public String createOrder(String userId, String productId, Integer count) {
log.info("开始创建订单");
try (final var ignored = TraceContext.createLocalSpan("OrderService.saveToDB")) {
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
orderMapper.insert(order);
log.info("订单已保存到数据库");
}
String result = inventoryFeignClient.deduct(productId, count);
log.info("订单创建流程结束");
return result;
}
}
Controller 里注入 Service 调用即可。再次请求后,追踪详情里会多出一个 OrderService.saveToDB 的小片段,它的耗时就是那一次 Insert 的时间。
调一下告警,别等用户来报
能在 UI 上看到慢请求还不够,最好能自己喊出来。SkyWalking 在 OAP 端配置告警规则。
进入容器 docker exec -it skywalking-oap /bin/sh,编辑 config/alarm-settings.yml,加一条:如果 order-service 的 /order/create 平均响应时间超过 500ms,就告警:
rules:
service_resp_time_rule:
metrics-name: service_resp_time
threshold: 500
period: 10
count: 1
silence-period: 10
include-names:
- order-service
message: Response time of service {name} is more than {value} ms in {period} minutes.
webhooks: []
重启容器 docker restart skywalking-oap。然后你在 order-service 里临时 Thread.sleep(600) 并发几个请求,等一会儿 SkyWalking UI 的 Alarm 页面就会出现告警记录。
日志里带上 TraceId
追踪的另一个好处是把 TraceId 塞进日志。只需要在 logback 配置里引入 SkyWalking 的 Layout。
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.16.0</version>
</dependency>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} - %msg%n</pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
2023-10-27 10:00:00.123 [TID:1234567890abcdef] [http-nio-8081-exec-1] INFO c.e.o.c.OrderController - 收到创建订单请求...
从 UI 的 Trace 详情里进 Logs 页面,就能顺着 TraceId 把调用过程中所有的日志串起来。前提是你的日志收集工具(比如 ELK)也接入了这个格式。
性能剖析:抓住那行慢代码
有时候不是整个方法慢,而是里面某一句 SQL 或循环搞鬼。SkyWalking 的 Performance Profiling 可以在生产环境下直接在线分析。
在 Trace 页找到一个慢请求,右上角点 Profile,设置采样间隔(比如 10ms)和最大采样数(比如 1000),就开始收集线程堆栈。结束之后你会得到一张火焰图,哪个方法花的时间最久一目了然。
这个功能在某些场景里比本地调试还管用,毕竟线上的真实数据和并发环境没法复刻。
上生产前注意几点
上面为了演示,用了 H2 内存存储和默认配置。如果要认真用,几个地方要动一下:
- 存储换成 Elasticsearch:H2 重启就丢,ES 持久且扛得住大量数据。
- 版本对齐:Agent 和 OAP 尽量保持相同大版本,免得追踪数据错乱。
- 采样率:高并发下适当调低
agent.sample_n_per_3_secs,别让 OAP 被数据淹了。
- 端口安全:OAP 的 11800(gRPC)和 12800(HTTP)别暴露到公网。
- 监控 OAP 本身:OAP 要是崩了,整个可观测就瞎了,它的 CPU 和内存也要纳入日常巡检。
这些点处理好了,SkyWalking 能长期稳定地帮你看住微服务调用链。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online