链路追踪Skywalking实践


链路追踪介绍

对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会遇到下面一些问题,比如:

  1. 如何串联整个调用链路,快速定位问题?

  2. 如何缕清各个微服务之间的依赖关系?

  3. 如何进行各个微服务接口的性能分折?

  4. 如何跟踪整个业务流程的调用处理顺序?

1、Skywalking简介

1.1 Skywalking是什么

skywalking是一个国产开源框架,2015年由吴晟开源 , 2017年加入Apache孵化器。skywalking是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它是一款优秀的 APM(Application Performance Management)工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等。

官网:http://skywalking.apache.org/

下载:http://skywalking.apache.org/downloads/

Github:https://github.com/apache/skywalking

文档: https://skywalking.apache.org/docs/main/v8.4.0/readme/

中文文档: https://skyapm.github.io/document-cn-translation-of-skywalking/

1.2 链路追踪框架对比

  1. Zipkin是Twitter开源的调用链分析工具,目前基于springcloud sleuth得到了广泛的使用,特点是轻量,使用部署简单。

    1. Pinpoint是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能强大,接入端无代码侵入。
  2. SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。

  3. CAT是大众点评开源的基于编码和配置的调用链分析,应用监控分析,日志采集,监控报警等一系列的监控平台工具。

cat zipkin pinpoint skywalking
依赖 Java 6,7,8 Maven 3.2.3+ mysql5.6 Linux 2.6以及之上(2.6内核才可以支持epoll) Java 6,7,8 Maven3.2+ rabbitMQ Java 6,7,8 maven3+ Hbase0.94+ Java 6,7,8 maven3.0+ nodejs zookeeper elasticsearch
实现方式 代码埋点(拦截器,注解,过滤器等) 拦截请求,发送(HTTP,mq)数据至zipkin服务 java探针,字节码增强 java探针,字节码增强
存储选择 mysql , hdfs in-memory , mysql , Cassandra , Elasticsearch HBase elasticsearch , H2
通信方式 http , MQ thrift GRPC
MQ监控 不支持 不支持 不支持 支持(RocketMQ,kafka)
全局调用统计 支持 不支持 支持 支持
trace查询 不支持 支持 不支持 支持
报警 支持 不支持 支持 支持
JVM监控 不支持 不支持 支持 支持
调用链路可视化
优点 功能完善。 spring-cloud-sleuth可以很好的集成zipkin , 代码无侵入,集成非常简单 , 社区更加活跃。 对外提供有query接口,更加容易二次开发 完全无侵入, 仅需修改启动方式,界面完善,功能细致。 完全无侵入,界面完善,支持应用拓扑图及单个调用链查询。 功能比较完善(zipkin + pinpoint)
缺点 代码侵入性较强,需要埋点 文档比较混乱,文档与发布版本的符合性较低,需要依赖点评私服 (或者需要把他私服上的jar手动下载下来,然后上传到我们的私服上去)。 默认使用的是http请求向zipkin上报信息,耗性能。 跟sleuth结合可以使用rabbitMQ的方式异步来做,增加了复杂度,需要引入rabbitMQ 。 数据分析比较简单。 不支持查询单个调用链, 对外表现的是整个应用的调用生态。 二次开发难度较高 3.2版本之前BUG较多 ,网上反映兼容性较差 . 3.2新版本的反映情况较少 依赖较多。
文档 网上资料较少,仅官网提供的文档,比较乱 文档完善 文档完善 文档完善

1.3 Skywalking主要功能特性

1、 多种监控手段,可以通过语言探针和service mesh获得监控的数据;

2、 支持多种语言自动探针,包括 Java,.NET Core 和 Node.JS;

3、 轻量高效,无需大数据平台和大量的服务器资源;4、模块化,UI、存储、集群管理都有多种机制可选;

5、 支持告警;

6、 优秀的可视化解决方案;

2、SkyWalking 环境搭建部署

  • skywalking agent和业务系统绑定在一起,负责收集各种监控数据
  • Skywalking oapservice是负责处理监控数据的,比如接受skywalking agent的监控数据,并存储在数据库中; 接受skywalking webapp的前端请求,从数据库查询数据,并返回数据给前端。
  • Skywalking oapservice通常以集 群的形式存在。 skywalking webapp,前端界面,用于展示数据。 用于存储监控数据的数据库,比如mysql、elasticsearch等。

2.1 下载 SkyWalking

下载:http://skywalking.apache.org/downloads/

目录结构:

  • webapp :UI前端(web 监控页面)的jar包和配置文件;

  • oap-libs :后台应用的jar包,以及它的依赖jar包,里边有一个server-starter-*.jar就是启动程序;

  • config:启动后台应用程序的配置文件,是使用的各种配置

  • bin:各种启动脚本,一般使用脚本startup.*来启动web页面和对应的后台应用;

  • oapService.*:默认使用的后台程序的启动脚本;(使用的是默认模式启动,还支持其他模式,各模式区别见启动模式)

  • oapServicelnit.*:使用init模式启动;在此模式下,OAP服务器启动以执行初始化工作,然后退出

  • oapServiceNoInit.*:使用no init模式启动;在此模式下,OAP服务器不进行初始化。

  • webappService.*:UI前端的启动脚本;

  • startup.*:组合脚本,同时启动oapService.*、 webappService.*脚本;

  • agent:

  • skywalking-agent.jar:代理服务jar包

  • config:代理服务启动时使用的配置文件

  • plugins:包含多个插件,代理服务启动时会加载改目录下的所有插件(实际是各种jar包)

  • optional-plugins:可选插件,当需要支持某种功能时,比如SpringCloud Gateway,则需要把对应的jar包拷贝到 plugins目录下;

2.2 搭建SkyWalking OAP 服务

启动脚本bin/startup.sh

日志信息存储在logs目录

启动成功后会启动两个服务,一个是skywalking-oap-server,一个是skywalking-web-ui : 8868

skywalking-oap-server服务启动后会暴露11800 和 12800 两个端口,分别为收集监控数据的端口11800和接受前端请 求的端口12800,修改端口可以修改config/applicaiton.yml

skywalking-web-ui服务会占用 8080 端口, 修改端口可以修改webapp/webapp.yml

server.port:SkyWalking UI服务端口,默认是8080;

collector.ribbon.listOfServers:SkyWalking OAP服务地址数组,SkyWalking UI界面的数据是通过请求SkyWalking OAP服务来获得;

访问:http://192.168.3.100:8080/

页面的右下角可以中英文切换,可以切换选择要展示的时间区间的跟踪数据。

2.3 SkyWalking中三个概念

服务(Service) :表示对请求提供相同行为的一系列或一组工作负载,在使用Agent时,可以定义服务的名字;

服务实例(Service Instance) :上述的一组工作负载中的每一个工作负载称为一个实例, 一个服务实例实际就是操作系统上 的一个真实进程;

端点(Endpoint) :对于特定服务所接收的请求路径, 如HTTP的URI路径和gRPC服务的类名 + 方法签名;

2.4 Skywalking UI介绍

[SkyWalking UI介绍 参考此文](http://note.youdao.com/noteshare? id=2ffc04d844595930dc3f42490b2f1a58&sub=4B6EDF5E5E894A72A3F96166197AB389)

3、 SkyWalking 接入微服务

3.1 linux环境—通过jar包方式接入

准备一个springboot程序,打成可执行jar包,写一个shell脚本,在启动项目的Shell脚本上,通过 -javaagent 参数进行 配置SkyWalking Agent来跟踪微服务;

startup.sh脚本:

#!/bin/sh
# SkyWalking Agent配置
export SW_AGENT_NAME=springboot‐skywalking‐demo #Agent名字,一般使用`spring.application.name`
export SW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800 #配置 Collector 地址。 
export SW_AGENT_SPAN_LIMIT=2000 #配置链路的最大Span数量,默认为 300。 
export JAVA_AGENT=‐javaagent:/usr/local/soft/apache‐skywalking‐apm‐bin‐es7/agent/skywalking‐agent.jar 
java $JAVA_AGENT ‐jar springboot‐skywalking‐demo‐0.0.1‐SNAPSHOT.jar #jar启动 

启动日志

等同于

java ‐javaagent:/usr/local/soft/apache‐skywalking‐apm‐bin‐es7/agent/skywalking‐agent.jar
‐DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
‐DSW_AGENT_NAME=springboot‐skywalking‐demo ‐jar springboot‐skywalking‐demo‐0.0.1‐SNAPSHOT.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_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800}

我们也可以使用skywalking.+配置文件中的配置名作为系统配置项来进行覆盖。 javaagent参数配置方式优先级更高

测试: 访问你的微服务

3.2 windos环境—在IDEA中使用Skywalking

在运行的程序配置jvm参数,如下图所示:

# skywalking‐agent.jar的本地磁盘的路径
‐javaagent:D:\apache\apache‐skywalking‐apm‐es7‐8.4.0\apache‐skywalking‐apm‐bin‐es7\agent\skywalking‐agent.jar
# 在skywalking上显示的服务名
‐DSW_AGENT_NAME=springboot‐skywalking‐demo
# skywalking的collector服务的IP及端口
‐DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.3.100:11800

-DSW_AGENT_COLLECTOR_BACKEND_SERVICES 可以指定远程地址, 但是-javaagent必须绑定你本机物理路径的skywalking-agent.jar

3.3 Skywalking跨多个微服务跟踪

Skywalking跨多个微服务跟踪,只需要每个微服务启动时添加javaagent参数即可。

注意:此处存在bug,跟踪链路不显示gateway 拷贝agent/optional-plugins目录下的gateway插件到agent/plugins目录,redis也是

4、 Skywalking持久化跟踪数据

默认使用的H2数据库存储

config/application.yml

4.1 基于mysql持久化:

  1. 修改config目录下的application.yml,使用mysql作为持久化存储的仓库

  2. 修改mysql连接配置

    storage: 
    #选择使用mysql 默认使用h2,不会持久化,重启skyWalking之前的数据会丢失 
    selector: ${SW_STORAGE:mysql} 
    #使用mysql作为持久化存储的仓库
    mysql: 
    properties: 
    #数据库连接地址
    jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://1ocalhost:3306/swtest"} 
    #用户名 
    dataSource.user: ${SW_DATA_SOURCE_USER:root} 
    #密码 
    dataSource.password: ${SW_DATA_SOURCE_PASSWORD:root} 

    注意:需要添加mysql数据驱动包,因为在lib目录下是没有mysql数据驱动包的,所以修改完配置启动是会报错,启动失 败的。

  3. 添加mysql数据驱动包到oap-libs目录下

  4. 启动Skywalking,查看swtest数据库,可以看到生成了很多表。 说明启动成功了,打开配置对应的地址http://192.168.3.100:8080/,可以看到skywalking的web界面。

  5. 测试:重启skywalking,验证跟踪数据会不会丢失

5、 自定义SkyWalking链路追踪

如果我们希望对项目中的业务方法,实现链路追踪,方便我们排查问题,可以使用如下的代码

引入依赖

<!--	skywalking自定义监控链路,注意版本要对应	-->
		<dependency>
			<groupId>org.apache.skywalking</groupId>
			<artifactId>server-storage-plugin</artifactId>
			<version>8.5.0</version>   
		</dependency>

5.1 @Trace将方法加入追踪链路

如果一个业务方法想在ui界面的跟踪链路上显示出来,只需要在业务方法上加上@Trace注解即可

5.2 加入@Tags或@Tag

我们还可以为追踪链路增加其他额外的信息,比如记录参数和返回信息。实现方式:在方法上增加@Tag或者@Tags。

@Tag 注解中 key = 方法名 ; value = returnedObj 返回值 arg[0] 参数

注:使用这两个注解之前方法上一定要有@Trace注解否则会失效

@Trace
@Tag(key = "list", value = "returnedObj")
public List<User> list(){
	return userMapper.list();
}

@Trace
@Tags({@Tag(key = "param", value = "arg[0]"),
@Tag(key = "user", value = "returnedObj")})
public User getById(Integer id){
	return userMapper.getById(id);
}

5.3 性能分析

skywalking的性能分析,在根据服务名称、端点名称,以及相应的规则建立了任务列表后,在调用了此任务列表的端点 后。skywalking会自动记录,剖析当前端口,生成剖析结果,具体流程如图:

按照项目信息填写以下信息即可

6、 Skywalking集成日志框架

[logback官方配置](skywalking/Application-toolkit-logback-1.x.md at v8.5.0 · apache/skywalking · GitHub)

[log4j官方配置](Dependency the toolkit, such as using maven or gradle | Apache SkyWalking)

[log4j2j官方配置](Dependency the toolkit, such as using maven or gradle | Apache SkyWalking)

引入依赖

<!‐‐ apm‐toolkit‐logback‐1.x 注意版本‐‐>
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm‐toolkit‐logback‐1.x</artifactId>
    <version>8.5.0</version>
</dependency>

添加logback-spring.xml文件,并配置 %tid 占位符

<?xml version="1.0" encoding="UTF‐8"?>
<configuration>
<!‐‐ 引入 Spring Boot 默认的 logback XML 配置文件 ‐‐>
<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>

<!‐‐ 设置 Appender ‐‐>
<root level="INFO">
<appender‐ref ref="console"/>
</root>

</configuration>

Skywalking通过grpc上报日志 (需要v8.4.0+)

gRPC报告程序可以将收集到的日志转发到SkyWalking OAP服务器上

logback-spring.xml中添加

<!‐‐ v8.4.0提供 ‐‐>
<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="grpc‐log" />
</root>

打开agent/config/agent.config配置文件,添加如下配置信息:

plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:192.168.3.100}
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.log.grpc.reporter.server_host 指定要向其报告日志数据的grpc服务器的主机 127.0.0.1
plugin.toolkit.log.grpc.reporter.server_port 指定要向其报告日志数据的grpc服务器的端口 11800
plugin.toolkit.log.grpc.reporter.max_message_s ize 指定grpc客户端要报告的日志数据的最大大小 10485760
plugin.toolkit.log.grpc.reporter.upstream_time out 客户端向上游发送数据时将超时多长时间。单位是秒 30

[agent配置信息详解](Setup java agent | Apache SkyWalking)

Skywalking UI效果

解决方法

7、SkyWalking 告警功能

SkyWalking 告警功能是在6.x版本新增的,其核心由一组规则驱动,这些规则定义在config/alarm-settings.yml文件中。 告警规则的定 义分为两部分:

  1. 告警规则:它们定义了应该如何触发度量警报,应该考虑什么条件。
  2. Webhook(网络钩子):定义当警告触发时,哪些服务终端需要被告知

告警规则

SkyWalking 的发行版都会默认提供config/alarm-settings.yml文件,里面预先定义了一些常用的告警规则。如下:

  1. 过去 3 分钟内服务平均响应时间超过 1 秒。
  2. 过去 2 分钟服务成功率低于80%。
  3. 过去 3 分钟内服务响应时间超过 1s 的百分比
  4. 服务实例在过去 2 分钟内平均响应时间超过 1s,并且实例名称与正则表达式匹配。
  5. 过去 2 分钟内端点平均响应时间超过 1 秒。
  6. 过去 2 分钟内数据库访问平均响应时间超过 1 秒。
  7. 过去 2 分钟内端点关系平均响应时间超过 1 秒。

这些预定义的告警规则,打开config/alarm-settings.yml文件即可看到

告警规则配置项的说明:

  • Rule name:规则名称,也是在告警信息中显示的唯一名称。必须以_rule结尾,前缀可自定义
  • Metrics name:度量名称,取值为oal脚本中的度量名,目前只支持long、double和int类型。详见Official OAL script
  • Include names:该规则作用于哪些实体名称,比如服务名,终端名(可选,默认为全部)
  • Exclude names:该规则作不用于哪些实体名称,比如服务名,终端名(可选,默认为空)
  • Threshold:阈值
  • OP: 操作符,目前支持 >、<、=
  • Period:多久告警规则需要被核实一下。这是一个时间窗口,与后端部署环境时间相匹配
  • Count:在一个Period窗口中,如果values超过Threshold值(按op),达到Count值,需要发送警报
  • Silence period:在时间N中触发报警后,在TN -> TN + period这个阶段不告警。 默认情况下,它和Period一样,这意味着 相同的告警(在同一个Metrics name拥有相同的Id)在同一个Period内只会触发一次
  • message:告警消息 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
 }, {
 "scopeId": 1,
 "scope": "SERVICE",
 "name": "serviceB",
 "id0": "23",
 "id1": "",
 "ruleName": "service_resp_time_rule",
 "alarmMessage": "alarmMessage yyy",
 "startTime": 1560524171000
}]

字段说明:

  • scopeId、scope:所有可用的 Scope 详见 org.apache.skywalking.oap.server.core.source.DefaultScopeDefine
  • name:目标 Scope 的实体名称
  • id0:Scope 实体的 ID
  • id1:保留字段,目前暂未使用
  • ruleName:告警规则名称
  • alarmMessage:告警消息内容
  • startTime:告警时间,格式为时间戳

邮件告警功能实践

根据以上两个小节的介绍,可以得知:SkyWalking是不支持直接向邮箱、短信等服务发送告警信息的,SkyWalking只会在发生告警时将 告警信息发送至配置好的Webhook接口。

但我们总不能人工盯着该接口的日志信息来得知服务是否发生了告警,因此我们需要在该接口里实现发送邮件或短信等功能,从而达到个 性化的告警通知。

接下来开始动手实践,这里基于Spring Boot进行实现。首先是添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐mail</artifactId>
</dependency>

配置邮箱服务:

server:
 port: 9134
#邮箱配置
spring:
 mail:
 host: smtp.163.com
#发送者邮箱账号
 username: 你的邮箱@163.com
#发送者密钥
 password: 你的邮箱服务密钥
 default‐encoding: utf‐8
 port: 465 #端口号465或587
 protocol: smtp
 properties:
 mail:
 debug:
 false
 smtp:
 socketFactory:
 class: javax.net.ssl.SSLSocketFactory

根据SkyWalking发送的JSON数据定义一个DTO,用于接口接收数据:

@Data
public class SwAlarmDTO {
 private Integer scopeId;
 private String scope;
 private String name;
 private Integer id0;
 private Integer id1;
 private String ruleName;
 private String alarmMessage;
 private Long startTime;
}

接着定义一个接口,实现接收SkyWalking的告警通知,并将数据发送至邮箱:

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/alarm")
public class SwAlarmController {

    private final JavaMailSender sender;

    @Value("${spring.mail.username}")
    private String from;

    /**
     * 接收skywalking服务的告警通知并发送至邮箱
     */
    @PostMapping("/receive")
    public void receive(@RequestBody List<SwAlarmDTO> alarmList) {
        SimpleMailMessage message = new SimpleMailMessage();
        // 发送者邮箱
        message.setFrom(from);
        // 接收者邮箱
        message.setTo(from);
        // 主题
        message.setSubject("告警邮件");
        String content = getContent(alarmList);
        // 邮件内容
        message.setText(content);
        sender.send(message);
        log.info("告警邮件已发送...");
    }

    private String getContent(List<SwAlarmDTO> alarmList) {
        StringBuilder sb = new StringBuilder();
            for (SwAlarmDTO dto : alarmList) {
                sb.append("scopeId: ").append(dto.getScopeId())
                  .append("\nscope: ").append(dto.getScope())
                  .append("\n目标 Scope 的实体名称: ").append(dto.getName())
                  .append("\nScope 实体的 ID: ").append(dto.getId0())
                  .append("\nid1: ").append(dto.getId1())
                  .append("\n告警规则名称: ").append(dto.getRuleName())
                  .append("\n告警消息内容: ").append(dto.getAlarmMessage())
                  .append("\n告警时间: ").append(dto.getStartTime())
                  .append("\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐\n\n");
             }
         return sb.toString();
     }
 }

最后将该接口配置到SkyWalking中,Webhook的配置位于config/alarm-settings.yml文件的末尾,格式为http://{ip}:port}/{uri}。

如下示例:

vim config/alarm‐settings.yml

webhooks:
‐ http://127.0.0.1:9134/alarm/receive

文章作者: wmg
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wmg !
  目录