微服务链路追踪组件 SkyWalking
前言#
对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会有以下问题,比如
- 如何串联整个调用链路,快速定位问题
- 如何捋清各个微服务的依赖关系
- 如何进行各个微服务接口的性能分析
- 如何跟踪整个业务流程的调用处理顺序
一、什么是 SkyWalking#
skywalking 是一个国产开源框架,2015年开源,2017年加入 Apache 孵化器。skywalking 是分布式系统的应用程序性能监视工具,转为微服务、云原生架构和基于容器(Docker、k8s、Mesos)架构而设计的。它是一款优秀的 APM(Application Performance Management)工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等
1.1、链路追踪框架对比#
- Zipkin 是 Twitter 开源的调用链分析工具,目前基于 springcloud sleuth 得到了广泛的使用,特点是轻量,使用部署简单。
- Pinpoint 是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI 功能强大,接入端无代码侵入。
- SkyWalking 是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI 功能较强,接入端无代码侵入。目前已加入 Apache 孵化器。
- CAT 是大众点评开源的基于编码和配置的调用链分析,应用监控分析,日志采集,监控报警等一系列的监控平台工具。
项目 | Cat | Zipkin | Skywalking |
---|---|---|---|
调用链可视化 | 有 | 有 | 有 |
聚合报告 | 非常丰富 | 少 | 较丰富 |
服务依赖图 | 简单 | 简单 | 好 |
埋点方式 | 侵入式 | 侵入式 | 非侵入,字节码增强 |
VM 监控指标 | 好 | 无 | 有 |
支持语言 | java/.net | 丰富 | java/.net/Nodejs/php/go |
存储机制 | mysql (报表)、本地文件 / HDFS (调用链) | 内存、es、mysql 等 | H2、es 等 |
社区支持 | 主要在国内 | 国外主流 | Apache 支持 |
使用案例 | 美团、携程、陆金所 | 京东、阿里定之后不开源 | 华为、小米、当当、微众银行 |
APM | 是 | 否 | 是 |
开发基础 | eBay cal | Google Dapper | Google Dapper |
是否支持 webflux | 否 | 是 | 是 |
Github stars(2022.8) | 17.1k | 15.6k | 20k |
在三种链路监控组件中,skywalking 的探针对吞吐量的影响最小。
1.2、Skywalking 主要特性#
1、多种监控手段,可以通过语言探针和 service mesh 获得监控的数据。
2、支持多种语言自动探针(agent),包括 Java,.NET Core 和 Node.JS。
3、轻量高效,无需大数据平台和大量的服务器资源。
4、模块化,UI、存储、集群管理都有多种机制可选。
5、支持告警。
6、优秀的可视化解决方案。
二、SkyWalking 环境搭建部署#
- skywalking agent 和业务系统绑定在一起,负责收集各种监控数据
- Skywalking oapservice 是负责处理监控数据的,比如接受 skywalking agent 的监控数据,并存储在数据库中;接受 skywalking webapp 的前端请求,从数据库查询数据,并返回数据给前端。Skywalking oapservice 通常以集群的形式存在。
- skywalking webapp,前端界面,用于展示数据。
- 用于存储监控数据的数据库,比如 mysql、elasticsearch 等。
2.1、下载 SkyWalking#
OAP Server 下载
Java-agent 接入
OAP 目录结构
- webapp:UI 前端(web 监控页面)的 jar 包和配置文件
- oap-libs:后台应用的 jar 包,以及它依赖的 jar 包,里面有一个 server-starter.jar 启动程序
- config:启动后台应用程序的配置文件,是使用的各种配置
- bin:各种脚本,一般使用脚本 startup.sh 来启动 web 页面和对应的后台应用:
oapService.*
:默认使用后台程序的启动脚本;(使用的是默认模式启动,还支持其他模式)oapServiceInit.*
:使用 init 模式启动;在此模式下,OAP 服务器启动以执行初始化工作,用于集群环境。为了防止多节点同时启动导致冲突,单节点执行 oapServiceInit.sh 进行初始化,其他节点执行 oapServiceNoInit.sh 等待初始化完成后再启动。oapServiceNoInit.*
:使用 no init 模式启动;在此模式下,OAP 服务器不进行初始化webappService.*
:UI 前端的启动脚本startup.*
:同时启动oapService.*
、webappService.*
脚本;
Java Agents 目录结构
- activations:工具包
- bootstrap-plugins:启动插件,默认加载
- config:配置文件
- optional-plugins:可选拓展插件,启动不加载,如需加载将其移入 plugins 目录下
- optional-reporter-plugins:可选统计类插件,启动不加载
- plugins:服务类插件
- skywalking-agent.jar:客户端主程序,需要被服务启动时引用
2.2、搭建 SkyWalking OAP 服务#
为了方式端口冲突,将前端页面地址进行修改
vim webapp/webapp.yml
server:
port: 8868
SkyWalking UI 界面是通过请求 SkyWalking OAP 服务来获得的
启动脚本 bin/startup.sh
$ bash startup.sh
SkyWalking OAP started successfully!
SkyWalking Web Application started successfully!
日志信息存储在 logs 目录
logs/
├── oap.log
├── skywalking-oap-server.log
└── webapp-console.log
启动成功之后会启动两个服务,一个是 skywalking-oap-server,一个是 skywalking-web-ui
skywalking-oap-server 服务启动后会暴露 11800 和 12800 两个端口,分别为手机监控数据的端口 11800 和接受前端请求的端口 12800,修改端口可以修改 config/application.yml
2.3、SkyWalking 中三个概念#
- 服务(Service): 表示对请求提供相同行为的一系列或一组工作负载,在使用 Agent 时,可以定义服务的名字;
- 服务实例(Service Instance):上述的一组工作负载中的每一个工作负载称为一 个实例,一个服务实例实际就是操作系统上的一个真实进程;
- 端点 (Endpoint):对于特定服务所接收的请求路径,如 HTTP 的 URI 路径和 gRPC 服务的类名 + 方法签名;
三、SkyWalking 接入微服务#
3.1、linux 通过 jar 包方式接入#
准备一个 springboot 程序,打包成可执行 jar 包,写一个 shell 脚本,在启动项目的 shell 脚本上,通过 -javaagent 参数进行配置 skywalking agent 来跟踪微服务;
startup.sh 脚本
#! /bin/bash
# SkyWalking Agent 配置
# Agent 名字,一般使用`spring.application.name`
export SW_AGENT_NAME=springboot-skywalking-demo
# 配置 Collector 地址
export SW_AGENT_COLLECTOR_BACKEND_SERVICERS=127.0.0.1:11800
export JAVA_AGENT=-javaagent:skywalking-agent.java 地址
# jar 启动
java $JAVA_AGENT -jar spring-boot-skywalking-demo.jar
等同于
java -javaagent:skywalking-agent.jar 地址 -DSW_AGENT_COLLECTOR_BACKEND_SERVICERS=127.0.0.1:11800 -DSW_AGENT_NAME=springboot-skywalking-demo -jar spring-boot-skywalking-demo.jar
参数名对应 agent/config/agent.config 配置文件中的属性
属性对应源码:org.apache.skywalking.apm.agent.core.conf.Config.java
# The service name in UI
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
# Backend service addresses
collector.backend_sercvice=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800}
3.2、Windows 环境 - IDEA 中使用 SkyWalking#
在运行程序中配置 jvm 参数
# 指定 agent.jar 所在位置
-javaagent:/Users/hudu/Environment/skywalking/skywalking-agent/skywalking-agent.jar
# 指定服务名字
-DSW_AGENT_NAME=服务名称
# 指定 skywalking 的 collector 服务的 IP 及端口
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
-javaagent 必须绑定本机物理路径的 skywalking-agent.jar
具体效果参考下面的接入多个微服务
3.2、Skywalking 跨多个微服务跟踪#
Skywalking 跨多个微服务跟踪,只需要每个微服务启动时添加 javaagent 参数即可。
-javaagent:/Users/hudu/Environment/skywalking/skywalking-agent/skywalking-agent.jar
-DSW_AGENT_NAME=服务名称
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
测试:
启动微服务,接入 seata 的 order 和 stock 服务
请求一个接口进行测试 http://localhost:8088/order/add
,可以看到 skywalking 控制台中已经有服务记录,调用链路图如下所示。
四、Skywalking 持久化跟踪数据#
默认使用的是 H2 内存数据库
config/application.yml
storage:
selector: ${SW_STORAGE:mysql}
mysql:
properties:
jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://localhost:3306/swtest?rewriteBatchedStatements=true"}
dataSource.user: ${SW_DATA_SOURCE_USER:root}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:root@1234}
创建 swtest 数据库
启动 skywalking 服务的时候会自动创建需要的表
oap-lib
目录中添加 mysql-connector-java.jar 包
五、自定义 SkyWalking 链路追踪#
如果我们希望对项目中的业务方法实现链路追踪,方便我们排查问题,可以使用如下的代码
5.1、引入依赖#
<!--SkyWalking 工具类-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.11.0</version>
</dependency>
5.2、@Trace 将方法加入追踪链路#
如果一个业务方法想再 ui 界面的跟踪链路上显示出来,只需要再业务方法上加上 @Trace
注解即可
5.3、加入 @Tags 或 @Tag#
我们还可以为追踪链路增加其他额外的信息,比如记录参数和返回信息。实现方式:在方法上增加 @Tag 或者 @Tags。
@Tag 注解中,key = 方法名,value=returnedObj 返回值,arg [0] 参数
@GetMapping("/get/{id}")
@Trace
@Tags({@Tag(key="result",value = "returnedObj"),
@Tag(key="param",value = "arg[0]")})
public Order get(@PathVariable Integer id) {
return orderService.get(id);
}
可以看到方法参数和返回值都追踪到了。
六、性能分析#
skywalking 的性能分析,在根据服务名称、端点名称,以及相应的规则建立了任务列表后,在调用了此任务列表的端点后。skywalking 会自动记录,剖析当前端口,生成剖析结果,具体流程如图:
然后请求服务至少三次,由于设置了采样次数
七、集成日志框架#
这里以 logback 为例
7.1、引入依赖#
<!--配置-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.11.0</version>
</dependency>
logback-spring.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 控制台输出 -->
<appender name="console" 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>${CONSOLE_LOG_PATTERN}</Pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console" />
</root>
</configuration>
添加 tid
通过 日志的 tid 去 skywalking 中查询接口
通过 gRpc 方式将日志反馈到 skywalking 中去
logback-spring.xml 中 添加
<!--日志配置-->
<appender name="grpc-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console" />
<appender-ref ref="grpc-log" />
</root>
此时可以看到 skywalking 中增加了日志信息。
注意事项#
如果 Skywalking 与服务不在同一服务器内,需要进行配置的修改,否则日志无法上报。
在 skywalking-agent/config/agent.config
配置文件中添加如下配置
plugin.toolkit.log.grpc.reporter.server_host=$[SW_GRPC_LOG_SERVER_HOST:192.168.33.62]
plugin.toolkit.log.grpc.reporter.server_port=$[SW_GRPC_LOG_ SERVER_PORT:11800]
plugin.toolkit.log.grpc.reporter.max_message_size=$[SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760]
plugin.toolkit.log.grpc.reporter.upstream_timeout=$[SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30]
以上配置是默认配置信息,agent 与 oap 在本地的可以不配
配置名 | 解释 | 默认值 |
---|---|---|
plugin.toolkit.log.transmit_formatted | 是否以格式化或未格式化的格式传输记录的数据 | true |
plugin.toolkit.grpc.reporter.server_host | 指定要向其报告日志数据的 grpc 服务器的主机 | 127.0.0.1 |
plugin.toolkit.grpc.reporter.server_port | 指定要向其报告日志数据的 grpc 服务器的端口 | 11800 |
plugin.toolkit.log.grpc.reporter.max_message_size | 指定 grcp 客户端要报告的日志数据的最大大小 | 10485760 |
plugin.toolkit.log.grpc.reporter.upstream_timeout | 客户端向上游发送数据时将超时多长时间,单位秒 | 30 |
八、SkyWalking 告警功能#
SkyWalking 告警功能是在 6.x 版本新增的,其核心由一组规则驱动,这些规则定义在 config/alarm-settings.yml
文件中。告警规则的定义分为两部分:
- 告警规则:它们定义了应该如何处罚度量告警,应该考虑什么条件
- Webhook(网络钩子):定义警告触发时,哪些服务终端需要被告知
8.1、告警规则#
SkyWalking 的发行版都会默认提供 config/alarm-settings.yml
文件,里面预先定义了一些常用的告警规则。如下:
- 过去 3 分钟内服务平均响应时间超过 1 秒。
- 过去 2 分钟服务成功率低于 80%。
- 过去 3 分钟内服务响应时间超过 1s 的百分比。
- 服务实例在过去 2 分钟内平均响应时间超过 1s, 并且实例名称与正则表达式匹配。
- 过去 2 分钟内端点平均响应时间超过 1 秒。
- 过去 2 分钟内数据库访问平均响应时间超过 1 秒。
- 过去 2 分钟内端点关系平均响应时间超过 1 秒。
这些预定义的告警规则,打开 config/alarm-setting.yml
文件即可看到。
告警规则配置项说明:
Rule name
: 规则名称,也是在告警信息中显示的唯一名称。 必须以 rule 结尾,前缀可自定义Metrics name
: 度量名称,取值为 oal 脚本中的度量名,目前只支持 long、double 和 int 类型。 详见 Official OAL scriptInclude names
: 该规则作用于哪些实体名称,比如服务名,终端名 (可选,默认为全部)Exclude names
: 该规则作不用于哪些实体名称,比如服务名,终端名 (可选,默认为空)Threshold
: 阈值OP
: 操作符,目前支持 >、<、=Period
: 多久告警规则需要被核实一下。 这是一个时间窗口, 与后端部署环境时间相匹配Count
: 在一个 Period 窗口中,如果 values 超过 Threshold 值 (按 op), 达到 Count 值,需要发送警报Silence period
: 在时间 N 中触发报警后,在 TN -> TN + period 这个阶段不告警。默认情况下, 它和 Period 一样,这意味着相同的告警 (在同一个 Metrics name 拥有相同的 id) 在同一个 Period 内只会触发一次message
: 告警消息
例如
rules:
# Rule unique name, must be ended with `_rule`.
service_resp_time_rule:
metrics-name: service_resp_time
op: ">"
threshold: 1000
period: 10
count: 3
silence-period: 5
message: Response time of service {name} is more than 1000ms in 3 minutes of last 10 minutes.
表示三分钟到十分钟之内,如果请求次数大于 5 次,并且有接口请求超过 1 秒,就进行报警
8.2、Webhook(网络钩子)#
Webhook 可以简单理解为是一种 Web 层面的回调机制,通常由一些事件触发,与代码中的事件回调类似,只不过是 Web 层面的。由于是 Web 层面的,所以当事件发生时,回调的不再是代码中的方法或函数,而是服务接口。例如,在告警这个场景,告警就是一个事件。当该事件发生时,SkyWalking 就会主动去调用一个配好的接口,该接口就是所谓的 Webhook。
SkyWalking 的告警消息会通过 HTTP 请求进行发送,请求方法为 POST,Content-Type 为 application/json,其 JSON 数据实基于 List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage> 进行序列化的。JSON 数据示例
[{
"scopeId": 1,
"scope": "SERVICE",
"name": "serviceA",
"id0": "12",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "alarmMessage xxxx",
"startTime": 1560524171000,
"tags": [{
"key": "level",
"value": "WARNING"
}]
}, {
"scopeId": 1,
"scope": "SERVICE",
"name": "serviceB",
"id0": "23",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "alarmMessage yyy",
"startTime": 1560524171000,
"tags": [{
"key": "level",
"value": "CRITICAL"
}]
}]
- scopeId, scope. 所有范围都在 org.apache.skywalking.oap.server.core.source.DefaultScopeDefine 中定义
- name. 目标范围实体名称。请按照实体名称定义
- id0. 范围实体的 ID 与名称匹配。使用关系范围时,它是源实体 ID
- id1. 使用关系范围时,它将是目标实体 ID。否则,它是空的
- ruleName. 您在 中配置的规则名称 alarm-settings.yml
- alarmMessage. 报警消息
- startTime. 告警时间以毫秒为单位,介于当前时间和 UTC 时间 1970 年 1 月 1 日午夜之间
config/alarm-settings.yml
配置中配置钩子
webhooks:
- http://127.0.0.1:8088/alarm/notify
定义 AlarmMessage
import lombok.Data;
//import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
import java.util.List;
@Data
public class SwAlarmMessage {
private int scopeId;
private String scope;
private String name;
private String id0;
private String id1;
private String ruleName;
private String alarmMessage;
private List<Tag> tags;
private long startTime;
private transient int period;
private transient boolean onlyAsCondition;
@Data
public static class Tag {
private String key;
private String value;
}
}
控制层代码
@RestController
@RequiredArgsConstructor
@RequestMapping("/alarm")
public class SwAlarmController {
Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 接收Skywalking服务的告警通知并发送至邮箱
* 必须是post请求
* @param alarmList
*/
@PostMapping("/notify")
public void receive(@RequestBody List<SwAlarmMessage> alarmList){
// SimpleMailMessage mailMessage = new SimpleMailMessage();
// // 发送者邮箱
// mailMessage.setFrom("from");
// // 接收者邮箱
// mailMessage.setTo("to");
// // 主题
// mailMessage.setSubject("主题");
// // 邮件内容
// String content = getContent(alarmList);
// mailMessage.setText("content");
// sender.send(mailMessage);
String content = getContent(alarmList);
log.info("告警邮箱已发送..."+content);
}
private String getContent(List<SwAlarmMessage> alarmList){
StringBuilder sb = new StringBuilder();
alarmList.forEach(message -> sb.append("scopeId: ").append(message.getScopeId())
.append("\nscope: ").append(message.getScope())
.append("\n目标 Scope 的实体类名称: ").append(message.getName())
.append("\nScope 实体类的 ID: ").append(message.getId0())
.append("\nid1: ").append(message.getId1())
.append("\n告警规则名称: ").append(message.getRuleName())
.append("\n告警消息内容: ").append(message.getAlarmMessage())
.append("\n告警时间: ").append(message.getStartTime())
.append("\n标签: ").append(message.getTags())
.append("\n\n-----------------\n\n "));
return sb.toString();
}
}
api 网关配置
spring:
gateway:
# 路由规则
routes:
- id: alarm_route
uri: lb://sw-alarm-server
predicates:
- Path=/alarm/**
请求接口,由于接口响应时间慢,skywalking 发送的告警。
八、Skywalking 高可用#
Sykwalking 集群是集那个 skywalking oap 作为一个服务注册到 nacos 上,只要 skywalking oap 服务没有全部宕机,保证有一个 skywalking oap 在运行,就能进行跟踪
搭建 skywalking oap 集群需要:
- 至少一个 Nacos(也可以是 nacos 集群)
- 至少一个 ElasticSearch/mysql(也可以是 es/mysql 集群)
- 至少 2 个 skywalking oap 服务
- 至少 1 个 UI(也可以是集群,多个,用 Nginx 代理统一入口)
8.1、修改 config/application.yml 文件#
使用 Nacos 作为注册中心,默认为单机模式
8.2、配置 UI 服务 webapp/webapp.yml 文件中的 oap-service,填写多个地址#
8.3、启动服务测试#
启动 Skywalking 服务,指定 SpringBoot 应用的 jvm 参数
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.33.61:11800,192.168.33.62:11800
本作品采用《CC 协议》,转载必须注明作者和本文链接