在日常项目中,我们常常会遇到线上性能问题,尤其在微服务的场景下,调用链错综复杂,如何才能快速的定位和解决问题,然后享受美好的夏日时光。枯藤老树昏鸦,空调WiFi西瓜,葛优同款沙发,夕阳西下,我就往上一趴。岂不美哉?
SkyWalking是一个观察性分析平台和应用性能管理系统(APM)。由华为吴晟等人开发,目前已经是Apache顶级项目。SkyWalking提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。能非常方便的定位线上性能问题。
本文将基于SkyWalking 7.0.0,开发Firefly服务端、客户端的agent插件为例,与大家分享SkyWalking的插件开发流程。
SkyWalking整体架构主要分三层,探针、后端和界面。
在应用端收集性能度量数据并发送给后端。SkyWalking支持三种探针:
接受探针发送过来的数据,进行度量分析,调用链分析和存储。后端主要分为两部分:
这三个模块的交互流程:
各种编程语言开发的应用(JVM、.Net、Go、Python等)都可以使用SkyWalking探针来收集数据。平时我们用的最多的是基于JVM的技术栈(Java、Scala、Kotlin),对于JVM技术栈,使用agent plugin的方式来收集性能数据有如下优势:
SkyWalking本身已经提供了足够多agent plugins,支持了JVM技术栈常用的开源框架和库(Spring Cloud、各种网络框架、Dubbo、各种数据库等),但是JVM的生态系统非常的庞大和活跃,各种开源框架和库层出不穷,成熟框架的版本更新也非常快,SkyWalking本身不一定能够及时的追踪这些新的框架或者新版本的库,所以有时候需要根据具体的项目或者工具来做定制的plugin开发。
SkyWalking提供的完整的plugin开发和自动测试框架,开发一个新的plugin需要下载SkyWalking源码进行构建。构建步骤如下:
源码构建成功之后,就可以开始插件开发了。首先我们在源代码的apm-sdk-plugin目录下建立自己的plugin module,目录结构如下:
[skywalking]
|-[apm-sniffer]
|-[apm-sdk-plugin] |-[firefly-5.x-plugins] |-[firefly-5.x-net-http-client-plugin] |-pom.xml |-[firefly-5.x-net-http-server-plugin] |-pom.xml |-pom.xml
Agent plugin是使用ByteBuddy做字节码增强,类似于AOP,相当于给目标类增加了一个代理。SkyWalking封装了相关的操作,形成了自己的开发框架。
上述代码定义了一个针对firefly http server的拦截器,用来收集firefly http server的性能数据。
表示需要拦截的类,这里拦截的是AsyncHttpServerConnectionListener,因为firefly http server接收到的所有请求都会进入这个listener,只需要对这个类做一个代理就可以拿到所有请求的性能数据了。这里除了可以按类名去拦截,还可以按照Annotation、前缀等方式去拦截目标类,具体可以参考ClassMatch接口的子类:
InstanceMethodsInterceptPoint用来定义,这个类中对哪些方法进行拦截,这里直接按方法名拦截onHttpRequestComplete方法。除了按方法名拦截,SkyWalking还封装了各种方式匹配要拦截方法,这里就不在赘述,可以参考ElementMatchers类相关源码或者文档。
第57行getMethodsInterceptor定义了拦截器的实现类的类名。一会儿我们就要实现这个类来记录性能数据。
在实现拦截器之前我们需要了解分布式追踪系统中的一些关键数据结构。
上图展示了一个简单的场景,server3通过远程调用server2返回结果给用户。右侧图表中的每一行称为一个跨度(Span),在拦截器中,我们就需要构造对应的Span发送给SkyWalking后端,那么SkyWalking的后端就能够根据Span的相关信息做调用链和度量分析。
Span是分布式追踪的主要构造块,表示⼀个独⽴的⼯作单元,它包含如下状态记录:
用yml表示span的结构如下:
其中spanType分三种:
Span的Context记录分两种:
了解了Span的基本概念,我们就可以开始实现刚才定义的方法拦截器AsyncHttpServerConnectionListenerInterceptor了。
首先看一下我们准备去拦截的目标方法onHttpRequestComplete的代码:
Firefly 5.x是一个基于Coroutine的网络框架,所有的http request都会进入这个callback然后找到相关的router来处理request,router的handler会运行在一个coroutine上,为了保持和java的兼容性,异步结果通过CompletableFuture返回。
AsyncHttpServerConnectionListenerInterceptor的实现:
beforeMethod在进入onHttpRequesComplete方法之前执行。
afterMethod方法在onHttpRequestComplete方法执行之后执行。这里可以拿到该方法的返回值,CompletableFuture,然后在future执行完成之后调用span.asyncFinish()来结束当前span并把span的相关数据发送给SkyWalking的后端OAP平台。
handleMethodException用来处理目标方法抛异常的情况,这里直接记录一下错误日志。
至此Firefly 5.x http server的plugin就开发完成了,http client的插件也类似,就不在赘述,区别就在于client的方法拦截器中是先创建ExitSpan,然后吧ContextCarrier中的信息存储到http request中发送给server,流程和server是正好相反的。
开发完所有代码之后,还需要在ComponentsDefine类和component-libraries.yml配置文件中增加新plugin的id、名称等信息的配置。
由于我们在下载源码后,已经对SkyWalking做过一次全量构建,开发新的插件之后,就不需要对整个项目进行构建,这里可以只构建agent模块即可。运行命令:mvn clean package -Pagent,dist -DskipTests=true
这里总结一下插件的开发流程如下:
插件代码构建完成之后,我们可以在实际的代码中加载一下新开发的插件看看运行是否正常。
构建完成的SkyWalking目录结构如下:
运行bin/startup.sh启动SkyWalking的后端服务和UI界面。看logs目录中的日志启动成功后,打开浏览器 http://localhost:8080 就可以访问SkyWalking的UI界面了
Server3应用代码如下:
启动的时候要在启动参数,配置agent路径、服务名称、SkyWalking后端地址等。配置如下:
配置完成后用IDEA运行server3
同样的方法配置server2并启动。
浏览器访问 http://localhost:7997/coroutine/hello ,浏览器显示
Server 3 -> server2 coroutine
这个时候我们就可以通过SkyWalking看到刚才请求的调用栈了。
同时也可以在拓补图中看到服务间的调用关系。
这样就说明插件工作正常了。
刚刚我们已经成功的构建并通过启动实际的应用运行了新开发的SkyWalking plugin。这种手工启动应用来进行插件的测试效率较低,SkyWalking自身已经提供了一套全自动的插件测试框架,来将刚才的构建、运行应用、发起测试请求、观察测试结果等步骤自动化运行。
自动测试框架在源码的test目录下,目录结构如下:
[skywalking]
|-[plugin]
|-[scenarios] |-generator.sh |-run.sh |-pom.xml
运行generator.sh命令,根据提示输入scenarios名称、类型等信息。完成后,测试脚手架会放到scenarios目录下,脚手架目录结构如下。
[plugin-scenario]
|- [bin]
|- startup.sh
|- [config]
|- expectedData.yaml
|- [src]
|- [main]
|- ...
|- [resource]
|- log4j2.xml
|- pom.xml
|- configuration.yaml
|- support-version.list
创建完脚手架之后需要在configuration.yaml中对测试场景进行一些配置。
把刚才运行的server3和server2的代码移植到测试场景中即可。代码如下:
在expectedData.yaml中,我们可以配置一些断言来测试plugin向后端发送的Span数据是否正确来测试plugin的功能。
断言配置中,数字类型的字段可以用 nq、eq、ge、gt等表达式,字符串类型的字段可以用not null、null、eq等表达式。
SkyWalking的插件自动测试框架会把我们测试应用打包成docker镜像然后运行,所以在运行测试场景之前需要在本机启动docker,然后运行:
./test/pugin/run.sh -f ${scenario_name}
就可以运行刚才开发的测试场景了。
在错综复杂的微服务架构环境下,SkyWalking可以对整个应用的各项性能指标以及调用链进行追踪和分析,能够帮助我们快速的定位和发现性能瓶颈。本文分享了SkyWalking插件开发的完整步骤和流程,希望对大家有所帮助。