skywalking原理_Skywalking 从入门到精通

7fa0f31270fb477bc698d58f9e3d3bcf.gif

学习目标

  • 能够知道什么是Skywalking
  • 能够搭建 Skywalking 环境
  • 能够使用 Skywalking 进行 rpc 调用监控
  • 能够使用 Skywalking 进行 MySQL 调用监控
  • 了解 Skywalking 插件
  • 了解 Skywalking agent 和 Open Tracing 原理

17a59b9908079ed0dcd941e69bef4082.png

53ba892f730e6ab8d1e54ecbcea7e01c.png

1. Skywalking 概述

在这一部分我们主要了解2个问题:

  • 什么是 APM 系统
  • 什么是 Skywalking

1.1 什么是 APM 系统

1.1.1 APM 系统概述

APM(Application Performance Management),即应用性能管理系统,是对企业系统即时监控以实现对应用程序性能管理和故障管理的系统化的解决方案。应用性能管理,主要指对企业的关键业务应用进行监控、优化,提高企业应用的可靠性和质量,保证用户得到良好的服务,降低 IT 总拥有成本。

APM 系统可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题。

1.1.2 分布式链路追踪

随着分布式系统和微服务架构的出现,一次用户的请求会经过多个系统,不同服务之间的调用关系十分复杂,任何一个系统出错都可能影响整个请求的处理结果。以往的监控系统往往只能知道单个系统的健康状况、一次请求的成功失败,无法快速定位失败的根本原因。

f467a59c3cfdbd4ef3c4dcf20f55aa1d.png

除此之外,复杂的分布式系统也面临这几个问题:

  • 性能分析:一个服务依赖很多服务,被依赖的服务也依赖了其它服务。如果某个接口耗时突然变长了,那未必是直接调动的下游服务慢了,也可能是下游的下游慢了造成的,如何快速定位耗时变长的根本原因呢?

  • 链路梳理:需求迭代很快,系统之间调用关系变化频繁,靠人工很难梳理清楚系统链路拓扑(系统之外的调用关系)

为了解决这些问题,Google 推出了一个分布式链路追踪系统 Dapper,之后各个互联网公司都参照 Dapper 的思想推出了自己的分布式链路追踪系统,而这些系统就是分布式系统下的 APM 系统。

1.1.3 什么是 OpenTracing

分布式链路追踪最先由 Google 的 Dapper 论文中提出,而 OpenTracing 通过提供与平台无关、厂商无关的 API,使得开发人员能够方便添加(或更换)追踪系统的实现。

下图是一个分布式调用的例子,客户端发起请求,请求首先到达负载均衡器,接着经过认证服务,订单服务,然后请求资源,最后返回结果。

93cb44b7a032df0269c19662d7397127.png

虽然这种图对于看清各组件的组合关系是很有用的,但是存在下面两个问题:

  • 它不能很好显示组件的调用时间,是串行调用还是并行调用,如果展现更复杂的调用关系,会更加复杂,甚至无法画出这样的图。

  • 这种图也无法显示调用间的时间间隔,以及是否通过定时调用来启动调用。

一种更有效的展现一个调用过程的图:

f562d0be504e23ce7aefd5305d997565.png

基于 OpenTracing 我们就可以很轻松的构建出上面这幅图。

1.1.4 主流开源APM

PinPoint

Pinpoint 是由一个韩国团队实现并开源,针对 Java 编写的大规模分布式系统设计,通过 JavaAgent 的机制做字节代码植入,实现加入 traceid 和获取性能数据的目的,对应用代码零侵入。

官方网站:

https://github.com/naver/pinpoint

53213b5ef8314f69d9ad7defabadaa6a.png

缺点:收集数据多,导致整体性能相对比较差。

SkyWalking

Skywalking 是 Apache 基金会下面的一个开源 APM 项目,为微服务架构和云原生架构系统设计。它通过探针自动收集所需的指标,并进行分布式追踪。通过这些调用链路以及指标,Skywalking APM 会感知应用间关系和服务间关系,并进行相应的指标统计。Skywalking 支持链路追踪和监控应用组件,基本涵盖主流框架和容器,如国产 RPC Dubbo和motan等,国际化的 Spring Boot、Spring Cloud。

官方网站:

http://skywalking.apache.org/

Zipkin

Zipkin 是由 Twitter 开源,是分布式链路调用监控系统,聚合各业务系统调用延迟数据,达到链路调用监控追踪。Zipkin 基于 Google 的 Dapper 论文实现,主要完成数据的收集、存储、搜索与界面展示。

官方网站:

https://zipkin.io/

注:Zipkin 自身与 Spring Cloud 有良好的集成,Spring Cloud 也推荐我们使用 Zipkin 作为分布式链路追踪。

CAT

CAT 是由大众点评开源的项目,基于 Java 开发的实时应用监控平台,包括实时应用监控、业务监控,可以提供十几张报表展示。

官方网站:

https://github.com/dianping/cat

缺点:代码具有倾入性,要使用 CAT,必须进行代码的更改,对一些框架的使用,需要进行埋点,埋点之后才能进行数据的上报。

1.2 什么是 Skywalking

1.2.1 Skywalking 概述

根据官方的解释,Skywalking 是一个可观测性分析平台(Observability Analysis Platform,简称 OAP)应用性能管理系统(Application Performance Management 简称 APM)

提供分布式链路追踪、服务网格(Service Mesh)、遥测分析、度量(Metric)、聚合和可视化等一体化解决方案。

几大特点:

  • 多语言自动探针,Java、.Net Core 和 Node.JS
  • 多种监控手段,语言探针和 service mesh
  • 轻量高效。不需要额外搭建大数据平台
  • 模块化架构。UI、存储、集群管理多种机制可选
  • 支持告警
  • 优秀的可视化效果(5.0->6.0)

Skywalking 整体架构如下:

1d506c8a764ff53af29889ce11a2f5cc.png

Skywalking 提供 Tracing 和 Metrics 数据的获取和聚合。

Metric 的特点是,它是可累加的:它们具有原子性,每个都是一个逻辑计量单元,或者一个时间段内的柱状图,例如:队列的当前深度可以被定义为一个计量单元,在写入或读取时被更新统计;输入 HTTP 请求的数量可以被定义为一个计数器,用于简单累加;请求的执行时间可以被定义为一个柱状图,在指定时间片上更新和统计汇总。

Tracing 的最大特点就是,它在单词请求的范围内,处理信息。任何的数据、元数据信息都被绑定到系统中的单个事务上。例如:一次调用远程服务的 RPC 执行过程;一次实际的 SQL 查询语句;一次 HTTP 请求的业务性 ID。

总结:Metric 主要用来进行数据的统计,比如 HTTP 请求数的计算。Tracing 主要包含了某一次请求的链路数据。

详细的内容可以查看 Skywalking 开发者吴晟翻译的文章,Metrics、Tracing 和 Logging 的关系:http://blog.oneapm.com/apm-tech/811.html

整体架构包含如下三个组成部分:

  1. 探针(agent)负责进行数据的收集,包含了 Tracing 和 Metrics 的数据,agent 会被安装到服务所在的服务器上,以方便数据的获取。
  2. 可观测性分析平台 OAP(Observability Analysis Platform),接收探针发送的数据,并在内存中使用分析引擎(Analysis Core)进行数据的整合运算,然后将数据存储到对应的存储介质上,比如 Elasticsearch、MySQL 数据库、H2 数据库等。同时 OAP 还使用查询引擎(Query Core)提供 HTTP 查询接口。
  3. Skywalking 提供单独的 UI 进行数据的查看,此时 UI 会调用 OAP 提供的接口,获取对应的数据,然后进行展示。

1.2.2 Skywalking 优势

Skywalking 相比较其它的分布式链路监控工具,具有以下特点:

  • 社区相当活跃。Skywalking 已经进入 Apache 孵化,目前的 star 数已经超过 11k,最新版本 6.5.0 已经发布。开发者是国人,可以直接和项目发起人交流进行问题的解决。
  • Skywalking 支持 Java、.Net Core 和 Node.JS 语言。相对于其它平台:比如 Pinpoint 支持 Java 和 PHP,具有较大的优势。
  • 探针无倾入性**。**对比 CAT 具有倾入性的探针,优势较大。不修改原有项目一行代码就可以进行集成。
  • 探针性能优秀。有网友对 Pinpoint 和 Skywalking 进行过测试,由于 Pinpoint 收集的数据过多,所以对性能损耗较大,而 Skywalking 探针性能十分出色。
  • 支持组件较多。特别是对 RPC 框架的支持,这是其它框架所不具备的。Skywalking 对 Dubbo、gRPC 等有原生的支持,甚至连小众的 motan 和 sofarpc 都支持。

1.2.3 Skywalking 主要概念介绍

使用如下案例来进行 Skywalking 主要概念的介绍,Skywalking 主要概念包含:

  • 服务(Service)
  • 端点(Endpoint)
  • 实例(Instance)

a0447421aabd89b4e45f07bbd7bd88da.png

上图中,我们编写了用户服务,这是一个 Web 项目,在生产中部署了两个节点:192.168.1.100 和 192.168.1.101。

  • 用户服务就是 Skywalking 的服务(Service),用户服务其实就是一个独立的应用(Application),在 6.0 之后的 Skywalking 将应用更名为服务(Service)。
  • 用户服务对外提供的 HTTP 接口 /usr/queryAll 就是一个端点,端点就是对外提供的接口。
  • 192.168.1.100 和 192.168.1.101 这两个相同服务部署的节点就是实例,实例指同一服务可以部署多个。

1.3 环境搭建

接下来我们在虚拟机 Cent OS 中搭建 Skywalking 的可观测性分析平台 OAP 环境。Skywalking 默认使用 H2 内存中进行数据的存储,我们可以替换存储源为 Elasticsearch 保证其数据的高效及可用性。

具体的安装步骤可以在 Skywalking 的官方 github 上找到:https://github.com/apache/skywalking/blob/master/docs/en/setup/README.md

1、创建目录

mkdir /usr/local/skywalking

建议将虚拟机内存设置为 3G 并且将 CPU 设置成 2 核,防止资源不足。

2、将资源目录中的 elasticsearch 和 skywalking 安装包上传到虚拟机 /usr/local/skywalking 目录下。

elasticsearch-6.4.0.tar.gz --elasticsearch 6.4 的安装包,Skywalking 对 es 版本号有一定要求,最好使用 6.3.2 以上版本,如果是 7.x 版本需要额外进行配置。

apache-skywalking-apm-6.5.0.tar.gz --Skywalking 最新的安装包

3、首先安装 elasticsearch,将压缩包解压。

tar -zxvf ./elasticsearch-.6.4.0.tar.gz
  1. 修改系统中允许应用最多创建多少文件等的限制权限。Linux 默认来说,一般限制应用最多创建的文件是 65535 个。但是 ES 至少需要 65536 的文件创建数的权限。

  2. 修改系统中允许用户启动的进程开启多少个线程。默认的 Linux 限制 root 用户开启的进程可以开启任意数量的线程,其他用户开启的进程可以开启 1024 个线程。必须修改限制数为 4096+。因为 ES 至少需要 4096 的线程池预备。

修改 Linux 系统的限制配置,将文件创建数修改为 65536 个。

vi /etc/security/limits.conf# 新增如下内容在 limit.conf 文件中es soft nofile 65536es hard nofile 65536es soft nproc 4096es hard nproc 4096

修改系统控制权限,Elasticsearch 需要开辟一个 65536 字节以上空间的虚拟内存。Linux 默认不允许任何用户和应用程序直接开辟这么大的虚拟内存。

vi /etc/sysctl.conf# 新增如下内容在 sysctl.conf 文件中,当前用户拥有的内存权限大小vm.max_map_count=262144# 让系统控制权限配置生效sysctl -p

建一个用户,用于 Elasticsearch 启动。

ES 在 5.x 版本之后,强制要求在 linux 中不能使用 root 用户启动 ES 进程。所以必须使用其它用户启动 ES 进程才可以。

# 创建用户useradd es# 修改上述用户的密码passwd es# 修改 elasticsearch 目录的拥有者chown -R es elasticsearch-6.4.0

使用 es 用户启动 elasticsearch

# 切换用户su es# 到 elasticsearch 的 bin 目录下cd bin/# 后台启动./elasticsearch -d

默认 Elasticsearch 是不支持跨域访问的,所以在不修改配置文件的情况下我们只能从虚拟机内部进行访问测试 Elasticsearch 是否安装成功,使用 curl 命令访问 9200 端口:

curl http://localhost:9200

如果显示出如下信息,就证明 Elasticsearch 安装成功:

{  "name" : "NCP0n0f",  "cluster_name" : "elasticsearch",  "cluster_uuid" : "CTHVhOc1R8uMK-jdN7G3sg",  "version" : {    "number" : "6.4.0",    "build_flavor" : "default",    "build_type" : "tar",    "build_hash" : "595516e",    "build_date" : "2018-08-17T23:18:47.308994Z",    "build_snapshot" : false,    "lucene_version" : "7.4.0",    "minimum_wire_compatibility_version" : "5.6.0",    "minimum_index_compatibility_version" : "5.0.0"  },  "tagline" : "You Know, for Search"}

4、安装 Skywalking,分为两个步骤:

  • 安装 Backend 后端服务

  • 安装 UI

首先切回到 root 用户,切换到目录下,解压 Skywalking 压缩包。

# 切换到 root 用户su root# 切换到 Skywalking 目录cd /usr/local/skywalking# 解压压缩包tar -zxvf apache-skywalking-apm-6.4.0.tar.gz

修改 Skywalking 存储的数据源配置:

cd apache-skywalking-apm-binvi config/application.yml

我们可以看到默认配置中,使用了 H2 作为数据源。我们将其全部注释。

storage:#  elasticsearch:#    nameSpace: ${SW_NAMESPACE:""}#    clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}#    protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"}#    trustStorePath: ${SW_SW_STORAGE_ES_SSL_JKS_PATH:"../es_keystore.jks"}#    trustStorePass: ${SW_SW_STORAGE_ES_SSL_JKS_PASS:""}#    user: ${SW_ES_USER:""}#    password: ${SW_ES_PASSWORD:""}#    indexShardsNumber: ${SW_STORAGE_ES_INDEX_SHARDS_NUMBER:2}#    indexReplicasNumber: ${SW_STORAGE_ES_INDEX_REPLICAS_NUMBER:0}#    # Those data TTL settings will override the same settings in core module.#    recordDataTTL: ${SW_STORAGE_ES_RECORD_DATA_TTL:7} # Unit is day#    otherMetricsDataTTL: ${SW_STORAGE_ES_OTHER_METRIC_DATA_TTL:45} # Unit is day#    monthMetricsDataTTL: ${SW_STORAGE_ES_MONTH_METRIC_DATA_TTL:18} # Unit is month#    # Batch process setting, refer to https://www.elastic.co/guide/en/elasticsearch/client/java-api/5.5/java-docs-bulk-processor.html#    bulkActions: ${SW_STORAGE_ES_BULK_ACTIONS:1000} # Execute the bulk every 1000 requests#    flushInterval: ${SW_STORAGE_ES_FLUSH_INTERVAL:10} # flush the bulk every 10 seconds whatever the number of requests#    concurrentRequests: ${SW_STORAGE_ES_CONCURRENT_REQUESTS:2} # the number of concurrent requests#    metadataQueryMaxSize: ${SW_STORAGE_ES_QUERY_MAX_SIZE:5000}#    segmentQueryMaxSize: ${SW_STORAGE_ES_QUERY_SEGMENT_SIZE:200}  h2:    driver: ${SW_STORAGE_H2_DRIVER:org.h2.jdbcx.JdbcDataSource}    url: ${SW_STORAGE_H2_URL:jdbc:h2:mem:skywalking-oap-db}    user: ${SW_STORAGE_H2_USER:sa}    metadataQueryMaxSize: ${SW_STORAGE_H2_QUERY_MAX_SIZE:5000}

修改端口:

cd webappvi webapp.yml

启动 Skywalking:

cd bin# 后台服务启动# ./oapService.sh# UI 服务启动# ./webappService.sh# 两个服务一起启动./startup.sh

提示以下信息说明启动成功:

SkyWalking OAP started successfully!SkyWalking Web Application started successfully!

3b3b9aabe4a649dc55c6400eb079104c.png

2. Skywalking 基础

2.1 agent 的使用

agent 探针可以让我们不修改代码的情况下,对 java 应用上使用到的组件进行动态监控,获取运行数据发送到 OAP 进行统计和存储。agent 探针在 java 中是使用 java agent 技术实现的,不需要更改任何代码,java agent 会通过虚拟机(JVM)接口来运行时更改代码。

Agent 探针支持 JDK1.6-12 的版本,Agent 探针所有的文件在 Skywalking 的 agent 文件夹下。文件目录如下:

-- agent  -- activations      apm-toolkit-log4j-1.x-activation.jar      apm-toolkit-log4j-2.x-activation.jar      apm-toolkit-logback-1.x-activation.jar      ...  //配置文件  -- config      agent.config  //组件的所有插件  -- plugins      apm-dubbo-plugin.jar      apm-feign-default-http-9.x.jar      apm-httpClient-4.x-plugin.jar      ...  //可选插件  -- optional-plugins      apm-gson-2.x-plugin.jar      ...  -- bootstrap-plugins      jdk-http-plugin.jar      ...  -- logs      skywalking-agent.jar

部分插件在使用上会影响整体的性能或者由于版权问题放置于可选插件包中,不会直接加载,如果需要使用,将可选插件中的 jar 包拷贝到 plugins 包下。

由于没有修改 agent 探针中的应用名,所以默认显示的是 Your_ApplicationName。我们修改下应用名称,让它显示的更加正确。编辑 agent 配置文件:

cd /usr/local/skywalking/apache-skywalking-apm-bin/agent/configvi agent.config

我们在配置中找到这么一行:

# The service name in UIagent.service_name=${SW_AGENT_NAME:Your_ApplicationName}

这里的配置含义是可以读取到 SW_AGENT_NAME 配置属性,如果该配置没有指定,那么默认名称为 Your_ApplicationName。这里我们把 Your_ApplicationName 替换成 skywalking_tomcat。

# The service name in UIagent.service_name=${SW_AGENT_NAME:skywalking_tomcat}

然后将 Tomcat 重启:

./shutdown.sh./startup.sh

2.1.1 Linux 下 Tomcat7 和 8 中使用

要使用 Skywalking 监控 Tomcat 中的应用,需要先准备一个 Spring MVC 项目,在资源中已经提供了打包好的文件

hello-mvc.war

以下是该项目的接口代码:

package com.niuzhendong.hello.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("hello")public class HelloController {    @GetMapping("sayHello")    public String sayHello(String name) {        return "hello world, hello "+name;    }}

按资源文件下的 apache-tomcat-8.5.60.tar.gz 文件上传至虚拟机 /usr/local/skywalking 目录下,然后解压:

tar -zxvf apache-tomcat-8.5.60.tar.gz

将 war 包上传至 /usr/local/skywalking/apache-tomcat-8.5.60/webapps/ 下。编辑 /usr/local/skywalking/apache-tomcat-8.5.60/bin/catalina.sh 文件,在文件顶部添加:

CATALINA_OPTS="$CATALINA_OPTS -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar";export CATALINA_OPTS

修改 tomcat 启动端口:

vi conf/server.xml# 修改这一行的端口为 8081<Connector port="8080" protocol=...>

执行 bin 目录下 ./startup.sh 文件启动 tomcat,然后访问地址。

2.1.2 Windows 下 Tomcat7 和 8 中使用(了解)

Windows 下只需要修改 tomcat 目录 /bin/catalina.bat 文件的第一行:

set "CATALINA_OPTS=-javaagent:/path/to/skywalking-agent/skywalking-agent.jar"

2.1.3 Spring Boot 中使用

Skywalking 与 Spring Boot 集成提供了完善的支持。

1、首先我们复制一份 agent,防止与 tomcat 使用的冲突。

cd /usr/local/skywalking/apache-skywalking-apm-bin/cp -r agent agent_bootvi agent_boot/config/agent.config

修改配置中的应用名为:

# The service name in UIagent.service_name=${SW_AGENT_NAME:skywalking_boot}

2、将资源文件夹中 skywalking_springboot.jar 文件上传到 /usr/local/skywalking 目录下。

Controller 层代码如下,提供了一个正常访问的接口和一个异常访问接口:

package com.niuzhendong.hello.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("hello")public class HelloController {    @GetMapping("sayHello")    public String sayHello(String name) {        return "hello world, hello "+name;    }    @GetMapping("exception")    public String exception() {        int i = 1/0;        return "Exception";    }}

使用命令启动 Spring Boot 项目:

java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_boot/skywalking-agent.jar -Dserver.port=9082 -jar hello.jar &

使用 jar 包启动的项目如果需要集成 skywalking,需要添加 -javaagent 参数,参数值为 agent 的 jar 包所在位置。

-Dserver.port 参数用于指定端口号,防止与 tomcat 冲突。

末尾 添加 & 后台运行模式启动 Spring Boot 项目。

此时我们可以访问 http://虚拟机 IP:9092/hello/sayHello 地址来进行访问,访问之后稍等片刻访问 Skywalking 的 UI 页面。

2.2 RocketBot 的使用

Skywalking 的监控 UI 页面称为 RocketBot,我们可以通过 8080 端口进行访问,由于 8080 端口很容易冲突,可以修改 webapp/webapp.yml 来更改端口:

server:  port: 8080

本例中我们更改为 9010 端口防止冲突,访问 http://虚拟机IP:9010/ 打开 RocketBot 的页面。

2.2.1 仪表盘

打开 RocketBot 默认会出现仪表盘页面:

ab18d45d07b4347a18c0c95bfe55a61e.png

2.2.2 拓扑图

3e123329747052bb3372b34a9ddd036c.png

2.2.3 追踪

bf9011d7b61ed1ca7591db5c45a98ac8.png

2.2.4 告警3e2fb15f2ffc9b2194559a6d026644fb.png

3. Skywalking 高级

3.1 RPC 调用监控

Skywalking(6.5.0)支持的 RPC 框架有以下几种:

  • Dubbo 2.5.4 -> 2.6.0
  • Dubbox 2.8.4
  • Apache Dubbo 2.7.0
  • Motan 0.2.x -> 1.1.0
  • gRPC 1.x
  • Apache ServiceComb Java Chassis 0.1 -> 0.5,1.0.x
  • SOFARPC 5.4.0

本节中我们使用 Spring Boot 和 Dubbo 搭建一个简单的服务提供方和服务消费方来测试 Skywalking 对于 RPC 调用的支持。可以使用资源文件夹下已经完成打包的 dubbo_consumer.jar 和 dubbo_provider.jar 来进行测试。

3.1.1 服务提供方

pom 文件:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0modelVersion>  <parent>    <groupId>org.springframework.bootgroupId>    <artifactId>spring-boot-starter-parentartifactId>    <version>2.4.0version>    <relativePath/>   parent>  <groupId>com.niuzhendonggroupId>  <artifactId>dubbo_providerartifactId>  <version>0.0.1-SNAPSHOTversion>  <name>dubbo_providername>  <description>Demo project for Spring Bootdescription>  <properties>    <java.version>1.8java.version>  properties>  <dependencies>    <dependency>      <groupId>org.springframework.bootgroupId>      <artifactId>spring-boot-starter-webartifactId>    dependency>    <dependency>      <groupId>org.springframework.bootgroupId>      <artifactId>spring-boot-starter-testartifactId>      <scope>testscope>    dependency>        <dependency>      <groupId>com.alibaba.bootgroupId>      <artifactId>dubbo-spring-boot-starterartifactId>      <version>0.2.0version>    dependency>  dependencies>  <build>    <plugins>      <plugin>        <groupId>org.springframework.bootgroupId>        <artifactId>spring-boot-maven-pluginartifactId>      plugin>    plugins>  build>project>

这里直接使用了 dubbo-spring-boot-starter 这一 dubbo 与 spring-boot 集成的组件。

官方文档地址:https://github.com/alibaba/dubbo-spring-boot-starter/blob/master/README.md

application.properties:

spring.application.name=dubbo_providerserver.port=9083dubbo.registry.register=falsedubbo.registry.address=127.0.0.1:2181

IHelloService.java:

package com.niuzhendong.api;public interface IHelloService {    String hello();}

HelloServiceImpl.java:

package com.niuzhendong.dubbo_provider.service;import com.alibaba.dubbo.config.annotation.Service;import com.niuzhendong.api.IHelloService;import org.springframework.stereotype.Component;@Service(interfaceClass = IHelloService.class)@Componentpublic class HelloServiceImpl implements IHelloService {    @Override    public String hello() {        return "hello skywalking";    }}

DubboProviderApplication.java:

@SpringBootApplication@EnableDubboConfigpublic class DubboProviderApplication {  public static void main(String[] args) {    SpringApplication.run(DubboProviderApplication.class, args);  }}

3.1.2 服务消费方

pom 文件和 IHelloService 同提供方一致。

TestController.java

package com.niuzhendong.dubbo_consumer.controller;import com.alibaba.dubbo.config.annotation.Reference;import com.niuzhendong.api.IHelloService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class TestController {    @Reference(url = "dubbo://127.0.0.1:20880")    private IHelloService helloService;    @GetMapping("hello")    public String hello() {        return helloService.hello();    }}

DubboConsumerApplication.java:

package com.niuzhendong.dubbo_consumer;import com.alibaba.dubbo.config.spring.context.annotation.EnableDubboConfig;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication@EnableDubboConfigpublic class DubboConsumerApplication {  public static void main(String[] args) {    SpringApplication.run(DubboConsumerApplication.class, args);  }}

3.1.3 部署方式

1、将 dubbo_provider.jar 和 dubbo_consumer.jar 上传至 /usr/local/skywalking 目录下。

2、首先我们复制两份 agent,防止使用的冲突。

cd /usr/local/skywalking/apache-skywalking-apm-bin/cp -r agent agent_dubbo_providercp -r agent agent_dubbo_consumervi agent_dubbo_provider/config/agent.config

修改 agent_dubbo_provider 配置中的应用名为:

# The service name in UIagent.service_name=${SW_AGENT_NAME:dubbo_provider}

接着修改 agent_dubbo_consumer:

vi agent_dubbo_consumer/config/agent.config

修改应用名:

# The service name in UIagent.service_name=${SW_AGENT_NAME:dubbo_consumer}

3、先启动 provider,等待启动成功。

# 切换到目录下cd /usr/local/skywalking# 启动 providerjava -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_dubbo_provider/skywalking-agent.jar -Dserver.port=9086 -jar dubbo_provider.jar &

4b90e10b8c99708517b80c71b1d7215d.png

出现如图所示内容,应用就已经启动成功了。

4、启动 consumer,等待启动成功。

# 切换到目录下cd /usr/local/skywalking# 启动 consumerjava -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_dubbo_consumer/skywalking-agent.jar -Dserver.port=9085 -jar dubbo_consumer.jar &

3.2 MySQL 调用监控

3.2.1 使用 docker 启动 MySQL

虚拟机中已经安装了 docker,我们先将 docker 启动:

systemctl start docker

使用 docker 命令启动 MySQL:

docker run -di --name=skywalking_mysql -p 33306:3306 -e MYSQL_ROOT_PASSWORD=123456 centos/mysql-57-centos7

MYSQL_ROOT_PASSWORD 环境变量指定 root 的密码为 123456

这样就可以在外部访问 MySQL 了。使用工具连接 MySQL,端口为 33306 密码为 123456。创建数据库:

1a62af8b5baea607c6230af0c2317b3c.png

执行建表语句:

CREATE TABLE `t_user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(50) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8

插入几条数据:

insert into `t_user`(`name`) values ('张三'),('李四'),('王五');

3.2.2 Spring Data JDBC 访问 MySQL

pom 文件:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0modelVersion>  <parent>    <groupId>org.springframework.bootgroupId>    <artifactId>spring-boot-starter-parentartifactId>    <version>2.4.0version>    <relativePath/>   parent>    <groupId>com.niuzhendonggroupId>  <artifactId>dubbo_providerartifactId>  <version>0.0.1-SNAPSHOTversion>  <name>dubbo_providername>  <description>Demo project for Spring Bootdescription>  <properties>    <java.version>1.8java.version>  properties>  <dependencies>    <dependency>      <groupId>org.springframework.bootgroupId>      <artifactId>spring-boot-starter-data-jdbcartifactId>    dependency>        <dependency>      <groupId>org.springframework.bootgroupId>      <artifactId>spring-boot-starter-webartifactId>    dependency>    <dependency>      <groupId>org.springframework.bootgroupId>      <artifactId>spring-boot-starter-testartifactId>      <scope>testscope>    dependency>    <dependency>      <groupId>mysqlgroupId>      <artifactId>mysql-connector-javaartifactId>      <version>5.1.46version>    dependency>  dependencies>  <build>    <plugins>      <plugin>        <groupId>org.springframework.bootgroupId>        <artifactId>spring-boot-maven-pluginartifactId>      plugin>    plugins>  build>project>

application.properties:

spring.datasource.url=jdbc:mysql://localhost:33306/skywalkingspring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.username=rootspring.datasource.password=123456server.port=9087

User.java:

@Table("t_user")public class User {  @Id  private Integer id;  private String name;    public Integer getId() {    return id;  }    public void setId(Integer id) {    this.id = id;  }    public String getName() {    return name;  }    public void setName(String name) {    this.name = name;  }    @Override  public String toString() {    return "User{" +            "id=" + id +            ", name='" + name + '\'' +            '}';  }}

UserRepository.java:

public interface UserRepository extends CrudRepository<User, Integer> {}

MysqlController.java:

@RestControllerpublic class MysqlController {  @Autowired  private UserRepository userRepository;    @GetMapping("/users")  public ListfindAll() {    List result = new ArrayList<>();    userRepository.findAll().forEach((user) -> {      result.add(user);    });        return result;  }}

3.2.3 部署方式

1、将 skywalking_mysql.jar 上传至 /usr/local/skywalking 目录下。

2、首先我们复制 agent,防止使用的冲突。

cd /usr/local/skywalking/apache-skywalking-apm-bin/cp -r agent agent_mysqlvi agent_mysql/config/agent.confi

修改 agent_mysql 配置中的应用名为:

# The service name in UIagent.service_name=${SW_AGENT_NAME:skywalking_mysql}

3、启动 skywalking_mysql.jar,等待启动成功。

# 切换到目录下cd /usr/local/skywalking# 启动 providerjava -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_mysql/skywalking-agent.jar -Dserver.port=9087 -jar skywalking_mysql.jar &

3.3 Skywalking 常用插件

3.3.1 配置覆盖

在之前的案例中,我们每次部署应用都需要复制一份 agent,修改其中的服务名称,这样显得非常麻烦。可以使用 Skywalking 提供的配置覆盖功能通过启动命令动态指定服务名,这样 agent 只需要部署一份即可。Skywalking 支持的集中配置方式:

系统配置(System properties)

使用 skywalking. + 配置文件中的配置名作为配置项来进行覆盖。

  • 为什么需要添加前缀?

    agent 的系统配置和环境与目标应用共享,所以加上前缀可以有效的避免冲突。

  • 案例

    通过如下进行 agent.service_name 的覆盖

    -Dskywalking.agent.service_name=skywalking_mysql
探针配置(Agent options)

Add the properties after the agent path in JVM arguments.

-javaagent:/path/to/skywalking-agent.jar=[option1]=[value1],[option2]=[value2]
  • 案例

    通过如下进行 agent.service_name 的覆盖

    -javaagent:/path/to/skywalking-agent.jar=agent.service_name=skywalking_mysql
  • 特殊字符

    如果配置中包含分隔符(, 或者 = ),就必须使用引号包裹起来:

    -javaagent:/path/to/skywalking-agent.jar=agent.ignore_suffix='.jpg,.jpeg'
系统环境变量(System environment variables)
  • 案例

    由于 agent.service_name 配置项如下所示:

    # The service name in UIagent.service_name=${SW_AGENT_NAME:Your_ApplicationName}

    可以在环境变量中设置 SW_AGENT_NAME 的值来指定服务名。

覆盖优先级

探针配置 > 系统配置 > 系统环境变量 > 配置文件中的值

所以我们的启动命令可以修改为:

java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_mysql/skywalking-agent.jar -Dskywalking.agent.service_name=skywalking_mysql -Dserver.port=9087 -jar skywalking_mysql.jar &

或者

java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_mysql/skywalking-agent.jar=agent.service_name=skywalking_mysql -Dserver.port=9087 -jar skywalking_mysql.jar &

3.3.2 获取跟踪 ID

Skywalking 提供我们 Trace 工具包,用于在追踪链路时进行信息的打印或者获取对应的追踪 ID。我们使用 Spring Boot 编写一个案例,也可以直接使用资源下的 skywalking_plugins.jar 进行测试。

pom 文件:

<dependency>  <groupId>org.apache.skywalkinggroupId>  <artifactId>apm-toolkit-traceartifactId>  <version>${skywalking.version}version>dependency>

本案例中使用 6.5.0 的版本号。

PluginController:

@RestControllerpublic class PluginController {  //获取traceId,可以在RocketBot追踪中进行查询  @GetMapping("/getTraceId")  public String getTraceId() {    //使当前链路报错,并且提示报错信息    ActiveSpan.error(new RuntimeException("Test-Error-Throwable"));    //打印info信息    ActiveSpan.info("Test-Info-Msg");    //打印debug信息    ActiveSpan.debug("Test-Debug-Msg");    return TraceContext.traceId();  }}

使用 TraceContext.traceId() 可以打印出当前追踪的 ID,方便在 RocketBot 中进行搜索。

ActiveSpan 提供了三个方法进行信息的打印:

  • error 方法会将本次调用变为失败状态,同时可以打印对应的堆栈信息和错误提示。

  • info 方法打印 info 级别的信息。

  • debug 方法打印 debug 级别的信息。

部署方式

1、将 skywalking_plugins.jar 上传至 /usr/local/skywalking 目录下。

2、启动 skywalkin_plugins 应用,等待启动成功。

java -javagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar -Dskywalking.agent.service_name=skywalking_plugins -Dserver.port=9088 -jar skywalking_plugins.jar &

3、调用接口,接口地址为:http://虚拟机IP地址:9088/getTraceId

3.3.3 过滤指定的端点

在开发过程中,有一些端点(接口)并不需要去进行监控,比如 Swagger 相关的端点。这个时候我们就可以使用 Skywalking 提供的过滤插件来进行过滤。在 skywalking_plugins 中编写两个接口进行测试:

@RestControllerpublic class FilterController {  //此接口可以被追踪  @GetMapping("/include")  public String include() {    return "include";  }    //此接口不可被追踪  @GetMapping("/exclude")  public String exclude() {    return "exclude";  }}
部署方式

1、将 skywalking_plugins.jar 上次至 /usr/local/skywalking 目录下。

2、将 agent 中的 /agent/optional-plugins/apm-trace-ignore-plugin-6.4.0.jar 插件拷贝到 plugins 目录中。

cd /usr/local/skywalking/apache-skywalking-apm-bincp optional-plugins/apm-trace-ignore-plugin-6.4.0.jar plugins/apm-trace-ignore-plugin-6.4.0.jar

3、启动 skywalking_plugins 应用,等待启动成功。

java -javagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar -Dskywalking.agent.service_name=skywalking_plugins -Dskywaking.agent.ignore_path=/exclude -Dserver.port=9089 -jar skywalking_plugins.jar &

这里添加 -Dskywalking.agent.ignore_path=/exclude 参数来标识需要过滤哪些请求,支持 Ant Path 表达式:

/path/*,/path/**,/path/?

  • ?匹配任何单字符

  • *匹配 0 或者任意数量的字符

  • **匹配 0 或者更多的目录

3.4 告警功能

3.4.1 告警功能简介

Skywalking 每隔一段时间根据收集到的链路的数据和配置的告警规则(如服务响应时间、服务响应时间百分比)等,判断如果达到阈值则发送响应的告警信息。发送告警信息时通过调用 webhook 接口完成,具体的 webhook 接口可以使用者自行定义,从而开发者可以在指定的 webhook 接口中编写各种告警方式,比如邮件、短信等。告警的信息也可以在 RocketBot 中查看到。

以下是默认的告警规则配置,位于 skywalking 安装目录下的 config 文件夹下 alarm-settings.yml 文件中:

# Sample alarm rules.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.  service_sla_rule:    # Metrics value need to be long, double or int    metrics-name: service_sla    op: "    threshold: 8000    # The length of time to evaluate the metrics    period: 10    # How many times after the metrics match the condition, will trigger alarm    count: 2    # How many times of checks, the alarm keeps silence after alarm triggered, default as same as period.    silence-period: 3    message: Successful rate of service {name} is lower than 80% in 2 minutes of last 10 minutes  service_p90_sla_rule:    # Metrics value need to be long, double or int    metrics-name: service_p90    op: ">"    threshold: 1000    period: 10    count: 3    silence-period: 5    message: 90% response time of service {name} is more than 1000ms in 3 minutes of last 10 minutes  service_instance_resp_time_rule:    metrics-name: service_instance_resp_time    op: ">"    threshold: 1000    period: 10    count: 2    silence-period: 5    message: Response time of service instance {name} is more than 1000ms in 2 minutes of last 10 minutes#  Active endpoint related metrics alarm will cost more memory than service and service instance metrics alarm.#  Because the number of endpoint is much more than service and instance.##  endpoint_avg_rule:#    metrics-name: endpoint_avg#    op: ">"#    threshold: 1000#    period: 10#    count: 2#    silence-period: 5#    message: Response time of endpoint {name} is more than 1000ms in 2 minutes of last 10 minuteswebhooks:#  - http://127.0.0.1/notify/#  - http://127.0.0.1/go-wechat/

以上文件定义了默认的 4 种规则:

  1. 最近 3 分钟内服务的平均响应时间超过 1 秒

  2. 最近 2 分钟服务成功率低于 80%

  3. 最近 3 分钟 90% 服务响应时间超过 1 秒

  4. 最近 2 分钟服务实例的平均响应时间超过 1 秒

规则中的参数属性如下:

属性含义
metrics-nameoal 脚本中的度量名称
threshold阈值,与 metrics-name 和下面的符号相匹配
op比较操作符,可以设定 >,<
period多久检查一次当前的指标数据是否符合告警
count达到多少次后,发送告警
silence-period在多久之内,忽略相同的告警消息
message告警消息内容
include-names本规则告警生效的服务列表

webhooks 可以配置告警产生时的调用地址。

3.4.2 告警功能测试代码

编写告警功能接口来进行测试,创建 skywalking_alarm 项目。可以直接使用资源下的 skywalking_alarm.jar 进行测试。

AlarmController:

package com.niuzhendong.skywalking_alarm.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class AlarmController {    /**     * 每次调用,睡眠1.5秒,模拟超时的报警     *     * @return     */    @GetMapping("/timeout")    public String timeout() {        try {            Thread.sleep(1500);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "timeout";    }}

该接口主要用于模拟超时,多次调用之后就可以生成告警信息。

AlarmMessage:

package com.niuzhendong.skywalking_alarm.pojo;public class AlarmMessage {    private int scopeId;    private String name;    private int id0;    private int id1;    private String alarmMessage;    private long startTime;    public int getScopeId() {        return scopeId;    }    public void setScopeId(int scopeId) {        this.scopeId = scopeId;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getId0() {        return id0;    }    public void setId0(int id0) {        this.id0 = id0;    }    public int getId1() {        return id1;    }    public void setId1(int id1) {        this.id1 = id1;    }    public String getAlarmMessage() {        return alarmMessage;    }    public void setAlarmMessage(String alarmMessage) {        this.alarmMessage = alarmMessage;    }    public long getStartTime() {        return startTime;    }    public void setStartTime(long startTime) {        this.startTime = startTime;    }    @Override    public String toString() {        return "AlarmMessage{" +                "scopeId=" + scopeId +                ", name='" + name + '\'' +                ", id0=" + id0 +                ", id1=" + id1 +                ", alarmMessage='" + alarmMessage + '\'' +                ", startTime=" + startTime +                '}';    }}

实体类用于接收告警信息。

Webhooks:

package com.niuzhendong.skywalking_alarm.controller;import com.niuzhendong.skywalking_alarm.pojo.AlarmMessage;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;@RestControllerpublic class Webhooks {    private List lastList = new ArrayList<>();    /**     * 产生告警时调用的接口     *     * @param alarmMessageList     */    @PostMapping("webhook")    public void webhook(@RequestBody List alarmMessageList) {        this.lastList = alarmMessageList;    }    /**     * 展示告警的信息     *     * @return     */    @GetMapping("/show")    public List show() {        return lastList;    }}
产生告警时会调用 webhook 接口,该接口必须是 POST 类型,同时接口参数使用 RequestBody。参数格式为:
[{  "scopeId": 1, "scope": "SERVICE", "name": "serviceA", "id0": 12, "id1": 0, "ruleName": "service_resp_time_rule", "alarmMessage": "alarmMessage xxx", "startTime": 1560524171000  },{  "scopeId": 1, "scope": "SERVICE", "name": "serviceB", "id0": 23, "id1": 0, "ruleName": "service_resp_time_rule", "alarmMessage": "alarmMessage yyy", "startTime": 1560524171000  },]

3.4.3 部署测试

首先需要修改告警规则配置文件,将 webhook 地址修改为:

webhooks:  - http://127.0.0.1:9090/webhook

然后重启 skywalking。

  1. 将 skywalking_alarm.jar 上传到 /usr/local/skywalking 目录下。

  2. 启动 skywalking_alarm 应用,等待启动成功。

    java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar -Dskywalking.agent.service_name=skywalking_alarm -Dserver.port=9090 -jar skywalking_alarm.jar
  3. 不停调用接口,接口地址为:http://虚拟机IP:9090/timeout

  4. 直到出现告警:

    fca4b7a76593c57bdd2f05afc14f5030.png

  5. 查看告警信息接口:http://虚拟机IP:9090/show

    a65089bd5d6f2b30b6526db8212ab7b9.png

    从上图中可以看到,我们已经获取到了告警相关的信息。在生产中使用可以在 webhook 接口中对接短信、邮件等平台,当告警出现时能迅速发送信息给对应的处理人员,提高故障处理的速度。

4. Skywalking 原理

4.1 java agent 原理

上文中我们知道,要使用 Skywalking 去监控服务,需要在其 VM 参数中添加“-javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar”。这里就使用到了 java agent 技术。

Java agent 是什么?

Java agent 是 java 命令的一个参数。参数 javaagent 可以用于指定一个 jar 包。

  1. 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。
  2. Premain-Class 指定的那个类必须实现 premain() 方法。

当 Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行 -javaagent 所指定 jar 包内 Premain-Class 这个类的 premain 方法。

如何使用 java agent?

使用 java agent 需要几个步骤:

  1. 定义一个 MANIFEST.MF 文件,必须包含 Premain-Class 选项,通常也会加入 Can-Redefine-Classes 和 Can-Retransform-Classes 选项。
  2. 创建一个 Premain-Class 指定的类,类中包含 premain 方法,方法逻辑由用户自己确定。
  3. 将 premain 的类和 MANIFEST.MF 文件打成 jar 包。
  4. 使用参数 -javaagent:jar 包路径启动要代理的方法。

4.1.1 搭建 java agent 工程

使用 maven 创建 java_agent_demo 工程:

edc3f7ad5933aec99d68935f0043c4c0.png

在 java 文件夹下新建 PreMainAgent 类:

import java.lang.instrument.Instrumentation;public class PreMainAgent {    /**     * 在这个 premain 函数中,开发者可以进行对类的各种操作。     * 1、agentArgs 是 premain 函数得到的程序参数,随同"-javaagent"一起传入。与 main 函数不同的是,     * 这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。     * 2、Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。     * java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,     * 集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。     *     * @param agentArgs     * @param inst     */    public static void premain(String agentArgs, Instrumentation inst) {        System.out.println("=========premain方法执行1=========");        System.out.println(agentArgs);    }    /**     * 如果不存在 premain(String agentArgs, Instrumentation inst)     * 则会执行 premain(String agentArgs)     *     * @param agentArgs     */    public static void premain(String agentArgs) {        System.out.println("=========premain方法执行2=========");        System.out.println(agentArgs);    }}

类中提供两个静态方法,方法名均为 premain,不能拼错。

在 pom 文件中添加打包插件:

<?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.0modelVersion>    <groupId>org.niuzhendonggroupId>    <artifactId>java_agent_demoartifactId>    <version>1.0-SNAPSHOTversion>    <build>        <plugins>            <plugin>                <groupId>org.apache.maven.pluginsgroupId>                <artifactId>maven-assembly-pluginartifactId>                <executions>                    <execution>                        <id>make-assemblyid>                                                <phase>packagephase>                        <goals>                                                        <goal>singlegoal>                        goals>                    execution>                executions>                <configuration>                    <appendAssemblyId>falseappendAssemblyId>                    <descriptorRefs>                        <descriptorRef>jar-with-dependenciesdescriptorRef>                    descriptorRefs>                    <archive>                                                <manifest>                            <addClasspath>trueaddClasspath>                        manifest>                        <manifestEntries>                            <Premain-Class>PreMainAgentPremain-Class>                            <Agent-Class>PreMainAgentAgent-Class>                            <Can-Redefine-Classes>trueCan-Redefine-Classes>                            <Can-Retransform-Classes>trueCan-Retransform-Classes>                        manifestEntries>                    archive>                configuration>            plugin>        plugins>    build>project>

4.1.2 搭建主工程

使用 maven 创建 java_agent_user 工程:

fe3c8242f4fee8b612cc52276fade8d5.png

在 java 文件夹下新建 Main 类:

public class Main {    public static void main(String[] args) {        System.out.println("========main方法执行========");    }}

添加 VM options:

-javaagent:/Users/hoge/Downloads/java_agent_demo/target/java_agent_demo-1.0-SNAPSHOT.jar=test

执行结果:

=========premain方法执行1=========test========main方法执行========Process finished with exit code 0

4.1.3 统计方法调用时间

Skywalking 中对每个调用的时长都进行了统计,这一小节中我们会使用 Byte Buddy 和 Java agent 技术来统计方法的调用市场。

Byte Buddy 是开源的、基于 Apache 2.0 许可证的库,它致力于解决字节码操作和 instrumentation API 的复杂性。

Byte Buddy 所声称的目标是将显式的字节码操作隐藏在一个类型安全的领域特定语言背后。通过使用 Byte Buddy,任何熟悉 Java 编程语言的人都有望非常容易地进行字节码操作。Byte Buddy 提供了额外的 API 来生成 Java agent,可以轻松的增强我们已有的代码。

添加依赖:

<dependencies>    <dependency>        <groupId>net.bytebuddygroupId>        <artifactId>byte-buddyartifactId>        <version>1.10.18version>    dependency>    <dependency>        <groupId>net.bytebuddygroupId>        <artifactId>byte-buddy-agentartifactId>        <version>1.10.18version>    dependency>dependencies>

修改 PreMainAgent 代码:

import net.bytebuddy.agent.builder.AgentBuilder;import net.bytebuddy.description.method.MethodDescription;import net.bytebuddy.description.type.TypeDescription;import net.bytebuddy.dynamic.DynamicType;import net.bytebuddy.implementation.MethodDelegation;import net.bytebuddy.matcher.ElementMatchers;import net.bytebuddy.utility.JavaModule;import java.lang.instrument.Instrumentation;public class PreMainAgent {    /**     * 在这个 premain 函数中,开发者可以进行对类的各种操作。     * 1、agentArgs 是 premain 函数得到的程序参数,随同"-javaagent"一起传入。与 main 函数不同的是,     * 这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。     * 2、Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。     * java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,     * 集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。     *     * @param agentArgs     * @param inst     *///    public static void premain(String agentArgs, Instrumentation inst) {//        System.out.println("=========premain方法执行1=========");//        System.out.println(agentArgs);//    }    /**     * 如果不存在 premain(String agentArgs, Instrumentation inst)     * 则会执行 premain(String agentArgs)     *     * @param agentArgs     */    public static void premain(String agentArgs) {        System.out.println("=========premain方法执行2=========");        System.out.println(agentArgs);    }    public static void premain(String agentArgs, Instrumentation inst) {        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {            public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {                // method 指定哪些方法需要被拦截,ElementMatchers.any 指定了所有的方法                // 声明 interceptor 拦截器                return builder.method(ElementMatchers.<MethodDescription>any())                        .intercept(MethodDelegation.to(MyInterceptor.class));            }        };        // type 指定了 agent 拦截的包名,以 com.agent 作为前缀        // 指定了 transformer        // 将配置安装到 Instrumentation        new AgentBuilder.Default().type(ElementMatchers.<TypeDescription>nameStartsWith("com.agent")).transform(transformer).installOn(inst);    }}

MyInterceptor:

import net.bytebuddy.implementation.bind.annotation.Origin;import net.bytebuddy.implementation.bind.annotation.RuntimeType;import net.bytebuddy.implementation.bind.annotation.SuperCall;import java.lang.reflect.Method;import java.util.concurrent.Callable;public class MyInterceptor {    @RuntimeType    public static Object intercept(@Origin Method method, @SuperCall Callable> callable) throws Exception {        Long start = System.currentTimeMillis();        try {            return callable.call();        } finally {            System.out.println(method.getName() + ":" + (System.currentTimeMillis() - start) + "ms");        }    }}

修改 Main:

package com.agent;public class Main {    public static void main(String[] args) throws InterruptedException {        System.out.println("========main方法执行========");        Thread.sleep(1000L);    }}

4.2 Open Tracing 介绍

之前的课程中已经简单介绍过 Open Tracing 一些基础概念,OpenTracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。OpenTracing 中最核心的概念就是 Trace。

4.2.1 Trace 的概念

在广义上,一个 trace 代表了一个事务或者流程在(分布式)系统中的执行过程。在 OpenTracing 的标准中,trace 是多个 span 组成的一个有向无环图(DAG),每一个 span 代表 trace 中被命名并计时的连续性的执行片段。

e574e3a17a73866a37b68834b98a30e6.png

例如客户端发起的一次请求,就可以认为是一个 Trace。将上面的图通过 OpenTracing 的语义修改完之后做可视化,得到下面的图:

5552e20430dcccfcf433e1b1aa46f11b.png

图中每一个色块其实就是一个 span。

4.2.2 Span 的概念

一个 Span 代表系统中具有开始时间和执行时长的逻辑运行单元。Span 之间通过嵌套或者顺序排列建立逻辑因果关系。

Span 里面的信息包括:操作的名字,开始时间和结束时间,可以附带多个 key:value 构成的 Tags(key 必须是 String,value 可以是 String、bool 或者数字),还可以附带 Logs 信息(不一定所有的实现都支持)也是 key:value 形式。

下面例子是一个 Trace,里面有 8 个 Span:

8b040a65e6859fed418561c60c3f72d7.png

一个 Span 可以和一个或者多个 Span 间存在因果关系。OpenTracing 定义了两种关系:childof 和 followsFrom。这两种引用类型代表了子节点和父节点间的直接因果关系。未来,OpenTracing 将支持非因果关系的 Span 引用关系。(例如:多个 Span 被批量处理,Span 在同一个队列中,等等)

ChildOf 很好理解,就是父亲 Span 依赖另一个孩子 Span。比如函数调用,被调者是调用者的孩子,比如说 RPC 调用,服务端那边的 Span,就是 ChildOf 客户端的。很多并发的调用,然后将结果聚合起来的操作,就构成了 ChildOf 关系。

如果父亲 Span 并不依赖于孩子 Span 的返回结果,这时可以说它构成 FollowsFrom 关系。

6f3da8c30fb75f139612583043fddf85.png

如图所示,左边的每一条追踪代表一个 Trace,而右边时序图中每一个节点就是一个 Span。

4.2.3 Log 的概念

每个 Span 可以进行多次 Logs 操作,每一个 Logs 操作,都需要一个带时间戳的时间名称,以及可选的任意大小的存储结构。

如下图是一个异常的 Log:

530aa673dc870dbd768f9e625307964d.png

如下图是两个正常信息的 Log,它们都带有时间戳和对应的事件名称、消息内容。

70d2c79ea72f38329f4c78f6b7c9260d.png

4.2.4 Tags 的概念

每个 Span 可以有多个键值对(key:value)形式的 TagsTags 是没有时间戳的,支持简单的对 Span 进行注解和补充。

如下图就是一个 Tags 的详细信息,其中记录了数据库访问的 SQL 语句等内容。

67210321968980780ab2977cb4c02256.png

四个概念关系图:

99e9a8fc8a0882ba8d4dd45141b9eb22.png

7db5e99930c729414972bd6460f5d16f.png7db5e99930c729414972bd6460f5d16f.png

各位程序员大佬们,请让我听到你们的声音!不管你是前端开发、后端研发、测试开发、移动端开发、全栈工程师、运维工程师、网络工程师、架构师、研发经理还是其他职位,不管你在做 Android 开发 、iOS 开发、U3D 、COCOS2DX、 自动化测试、功能测试、性能测试、白盒测试、灰盒测试、黑盒测试、ETL、数据仓库、数据开发、数据挖掘、数据分析、数据架构、算法研究、精准推荐、分布式、系统集成、地图引擎、人工智能、大数据、深度学习、机器学习、图像处理、图像识别、语音识别、语音学习、机器视觉、自然语言处理、视频开发、区块链还是其它技术支持。欢迎在评论中踊跃发表意见!

7a141cba3cbae8b875a3aacbb519cc92.pngd53fe4e6db6821779c3ad95b1fb0ac3d.png

扫码关注我们

极牛科技公众号

微信号 : jnkjnl

博客:niuzhendong.com

Github:github.com/niuzhendonglm

5b996cf14bf8cd66e25be2b9e3750d7a.gif

点个赞再走嘛!9132db4221027ba12189376f0a0583df.gif


版权声明:本文为weixin_39603265原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。