Agent和业务程序运行在一起,采集链路及其它数据,通过gRPC发送给OAP(部分Agent采用http+json的方式);OAP还原链路(图中的Tracing),并分析产生一些指标(图中的Metric),最终存储到Storage中。本文从源码角度来串联一下这整个流程(基于目前最新的Skywalking 8.0.1)。
本地调试必须先从源码编译Skywalking,有两种方式,一种是从GitHub拉取代码,一种是从Apache Skywalking的release页面下载代码。区别在于GitHub上面的代码是使用git module管理的,拉取下来需要执行一系列操作,最主要的是没有科学上网的话,速度比较慢。Release页面下载的是已经把依赖关系全部整理好的代码,整个源码包不到3MB,还有很多国内镜像地址,所以下载非常快。两种我都使用过,我的建议是:如果你想看历史提交记录或者想持续跟上游版本的话,就选用从GitHub拉取代码的方式;如果你想方便或者从GitHub clone超级慢的话,建议直接从Release处下载。不管哪种,编译以及导入IDEA或Eclipse官方文档写的都比较详细,我就不做翻译了,基本都是命令操作,英文不好也看得懂(just copy-and-paste~~):How to build.
源码编译成功以后(务必保证编译成功),就可以准备进行调试了。
这里通过一个简单的Spring MVC程序来演示如何调试Agent和OAP。
在Skywalking项目下增加一个简单的Spring MVC模块(注意这里一定要以Skywalking项目module的方式添加),这里我创建了一个名叫simple-springmvc的module,增加了一个简单的Controller:/hello/{name}
。如下图:
然后在这个这个MVC程序的VM option中增加如下配置:
-javaagent:{源码根目录}/skywalking-agent/skywalking-agent.jar
-Dskywalking.agent.service_name=simple-springmvc
注意,-javaagent后面那个skywalking-agent.jar路径换成你自己的路径。源码如果编译成功的话,源码根目录下面会出现这个skywalking-agent目录,并且里面会有这个skywalking-agent.jar。如下图:
这样调试用的示例程序以及Skywalking的agent注入也配置好了。先别启动,接下来还需要启动OAP。
如果想先只调试agent的话,可以单独下载一个Skywalking的二进制(编译完以后,根目录下的dist目录也有二进制安装包),本地启动(参考我之前的文章)即可。第一次调试的话,我建议agent和OAP单独调试,因为两者有一些公用代码,在一个工程里面启动的话,容易造成混淆。分开调试的话就本地单独起一个Skywalking就行,这里讲直接在项目里面启动一个OAP的方式。
启动OAP非常简单,OAP的代码是源码根目录下的oap-server,入口函数是org.apache.skywalking.oap.server.starter
包下面的OAPServerStartUp类。直接启动即可。
需要注意的是这样只启动了OAP,为了方便查看还原的链路(不启动也不影响调试,不看Web的直接跳过),我们再手动启动一个Web UI。直接在Skywalking安装目录下面(注意是二进制安装目录,不是源码目录)的webapp目录下执行:java -jar skywalking-webapp.jar
即可。默认访问地址为http://127.0.0.1:8080/。
OAP和UI(optional)启动好以后,就可以开始调试了。
启动调试之前,我先简单介绍一下数据流向以及一些关键的函数,方便提前打断点。整个数据流如下图:
这里我们先创建了一个Spring MVC程序simple-springmvc,并且配置了javaagent,这样Skywalking agent就会以字节码注入的方式运行在simple-springmvc里面。当我们使用curl命令发送请求时,就会产生链路数据。需要注意的是,Skywalking默认已经实现了Spring MVC的插件{源码根目录}/skywalking-agent/plugins/apm-springmvc-annotation-commons-8.0.1.jar
,对应的源码是{源码根目录}/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons
。它的增强函数就是在这个模块下的AbstractMethodInterceptor
类中实现的,给这个类的beforeMethod
方法打个断点(为了节省篇幅,省略了一些不重要的代码),就可以观察数据agent增强流程:
package org.apache.skywalking.apm.plugin.spring.mvc.commons.interceptor;
public abstract class AbstractMethodInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
// 给下面这行打个断点
Boolean forwardRequestFlag = (Boolean) ContextManager.getRuntimeContext().get(FORWARD_REQUEST_FLAG);
/**
* Spring MVC plugin do nothing if current request is forward request.
* Ref: https://github.com/apache/skywalking/pull/1325
*/
if (forwardRequestFlag != null && forwardRequestFlag) {
return;
}
// 以下省略
}
// 以下省略
}
然后启动了OAP,后端存储使用了默认内建的内存数据库H2。为了方便查看链路,可以选择性启动一个UI。Agent和OAP之间是通过gRPC来发送链路信息的。Agent端维护了一个队列(默认5个channel,每个channel大小为300)和一个线程池(默认1个线程,后面称为发送线程),链路数据采集后主线程(即业务线程)会写入这个队列,如果队列满了,主线程会直接把把数据丢掉(丢的时候会以debug级别打印日志)。发送线程会从队列取数据通过gRPC发送给后端OAP,OAP经过处理后写入存储。为了看得清楚,我把涉及的框架类画到了下面的图里面(格式是:{类名}#{方法名}({方法中调用的重要函数}
):
这里只列举了核心函数,每个函数内部的方法就不赘述了。需要说明的就是Skywalking代码的模块化还是做得很不错,大家跟踪代码的时候可以关注一下功能所属的模块,更有利于学习整个项目或者进行二次开发。
给这些核心方法,打上断点,以Debug模式启动oap-server和simple-springmvc,然后用curl发一个请求,就可以愉快的调试了。
Just read the source code, good luck !
]]>APM是什么?APM全称Application Performance Monitor,即应用性能监控(也有翻译成Application Performance Management,应用性能管理的),所有和应用性能相关的指标、管理相关的事情都属于它的范畴。Skywalking就是一个具体的APM软件,也是Apache下面的一个顶级项目,而且是由中国人发起。随着分布式和微服务架构的普及,一个系统所包含的服务、节点越来越多,APM的需求越来越明显。这里就拿一个具体的例子来看APM有什么用处吧。
现在我们有如下一个基于Spring Cloud的微服务系统:
这个系统是一个非常简化的微服务架构了,它包含了5个服务:
这个系统的代码可以从这里下载:github: skywalking-demo。现在呢,这个系统跑起来以后客户反馈了两个问题:
现在该怎么定位这2个问题呢?如果没有APM,最直观的方式从调用发起端(Web)一个个往后排查。如果服务少,机器少这个方式还行,但如果有很多服务,每个服务还有多个实例,那可能就有几十个甚至成百上千个服务实例,一个一个排查估计客户没发飙之前运维同学已经离职了。
这个时候,是该让APM上场了。本文介绍Skywalking。
本文目标是先把整个系统运行起来,具体Skywalking的细节,后面文章再讲述。Skywalking架构图如下(图片来自官方):
可以看到主要由四部分组成:
先从官网下载安装包:下载页面,目前最新的版本是8.0.1。下载后解压即可完成安装。
然后在解压后的目录执行bin/startup.sh
即可启动(Windows下执行startup.bat
),这样默认启动了两个模块:
浏览器访问http://127.0.0.1:8080/,如果页面正常,说明Skywalking已经部署好了。下面看如何借助Skywalking来定位上面客户反馈的两个问题。
首先得在服务里面集成Skywalking的Agent。Java的Agent是通过字节码注入实现的。所以启动时在虚拟机参数中配置javaagent即可。下载前面说的代码,然后编辑每个服务启动时的虚拟机参数,如图:
-javaagent:/Users/allan/portable-software/apache-skywalking-apm-bin-8.0.1/agent/skywalking-agent.jar
:将这个里面的路径改为你自己Skywalking的安装部署路径,所有服务都一样。-Dskywalking.agent.service_name=eureka-server
:这个是指定服务的名字。注意不同的服务修改成不同的名字。这些配置好以后,按如下顺序依次启动4个服务(不按照这个顺序启动也可以,只是可能会报些错误以及服务发现可能慢一些,但不影响功能):
http://127.0.0.1:8761/
,则表示启动成功。/products/{id}
,这个接口内部会有一个数据库访问操作,数据库默认使用的是H2./users/{id}
,这个接口内部会使用RestTemplate调用product-service的/products/{id}
接口。zuul-server:监听端口为8000。默认配置了两个路由:
zuul:
routes:
user-service:
path: /user-service/**
serviceId: USER-SERVICE
product-service:
path: /product-service/**
serviceId: product-service
配置的含义是将/user-service/
开头的请求转发给USER-SERVICE(不区分大小写)服务,转发的时候去掉前面的/user-service
。比如 /user-service/users/1
转发到USER-SERVICE时会变为/users/1
。product-service部分配置一样。
都启动后,在浏览器能访问http://127.0.0.1:8761/
,看下3个服务是否已经在Eureka中注册成功(Eureka自身不注册)。如果成功了,我们使用curl模拟web发送如下请求(这里我用的是HTTPie这个工具,比curl使用简单,且输出自动高亮且格式化了。如果你没有装,直接将下面的http命令替换成curl即可,或者使用postman之类的工具都OK):
# curl http://localhost:8000/user-service/users/1
http http://localhost:8000/user-service/users/1
# 上面的命令多执行几次,保证出现过以下两种结果
# 结果1:即客户反映的有调用失败的情况
{
"error": "Internal Server Error",
"message": "500 : [<Map><timestamp>2020-07-09T09:17:36.024+0000</timestamp><status>500</status><error>Internal Server Error</error><message>something error in product service</message><path>/products/1</path></Map>]",
"path": "/users/1",
"status": 500,
"timestamp": "2020-07-09T09:17:36.065+0000"
}
# 结果2:结果正常,但明显比较慢。
{
"products": [
"audi",
"benz",
"volvo",
"honda"
],
"user_id": "1"
}
我们发现多次执行请求(curl命令),的确有时会失败(结果1);不失败的时候,一次请求挺慢的。和客户反馈的是一致的。这个时候,打开Skywalking的Web页面(http://127.0.0.1:8080/)来看看为什么吧(Web上面的详细功能本文就不介绍了,后面文章介绍):
先看trace菜单,也就是调用的链路数据(如果你看不到数据,先点击一下那个Clear,然后选一下时间范围,再点击Search),就能出现链路图了。
先在左侧找一个没有标红的链路,右侧选择“Table”视图,如下:
可以看到,整个请求耗时9057ms,product-service的一个/products/{id}
接口就占了9029ms,这样我们就知道调用慢到底是哪个服务导致的了。而且可以看到的是,里面的数据库操作并没有占用太多时间。在去检查/products/{id}
到底做了什么事情,耗费了这么多时间之前,我们再找一个标红的链路看看。
标红的链路表示请求出错了,对应刚才发请求时返回的结果1。我们随便找一个出错的,点击右侧链路中最下面的那个请求(即图中的/products/{id}
,根据颜色可以看出来这个接口属于product-service),右侧会弹出一个详细内容框,这里有一些基本的元数据信息,最主要的是对于出错的,展示出了调用栈信息:
从调用栈可以看出,错误发生在ProductServiceApplication.java文件的第30行。
至此,我们已经知道了偶尔出错和请求慢的罪魁祸首都是product-service提供的/products/{id}
接口导致的。然后赶紧看下那里的代码到底在搞什么:
@SpringBootApplication
@RestController
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
@Autowired
ProductRepository productRepo;
@GetMapping("/products/{id}")
public Object getProductsById(@PathVariable String id) throws Exception {
// 产品经理让加的,说方便后面让客户给钱升级版本
int trap = (int) (Math.random() * 10);
if (trap % 2 == 0) {
throw new Exception("something error in product service");
}
Thread.sleep(trap * 1000);
productRepo.findByDescriptionLike("%world%");
List<String> productList = new ArrayList<>(8);
productRepo.findAll().forEach(product -> productList.add(product.getName()));
return productList;
}
}
第30行代码是throw new Exception("something error in product service");
。看了代码上下文我们才发现:原来罪魁祸首都是产品经理:让在代码里面加了一个随机的出错,即使不出错,也要sleep。怪不得请求偶尔会出错或者请求比较慢呢。原来都是产品经理的锅,是时候杀个产品经理来祭天了。
至此,可以看到,APM提供的链路分析对于定位请求出错以及耗时方面是非常方便的。当然APM除了链路跟踪这个功能之外,一般还会提供很多其它跟性能相关的数据。比如Skywalking还提供了指标分析(Dashboard)、拓扑图(Topology)、告警(Alarm)、性能剖析(Profile)功能。下面截几张图看看,后面文章专门介绍。
Dashboard的APM页面:
Dashboard的数据库页面:
如果你的图那里没有看到数据的话,去Skywalking安装目录下,修改config/application.yml文件,找到下面的行,将default后面的值改为1(只有超过这个值的SQL语句才会被认为是慢SQL),如下图:
slowDBAccessThreshold: ${SW_SLOW_DB_THRESHOLD:default:1,mongodb:100}
然后重启Skywalking,重新发几次请求,再查看这个页面。
拓扑图页面:
可以根据服务调用自动还原应用拓扑图,那个连线会根据请求关系动态流动,截图体现不出来。可以看到,这个拓扑图和前面画的那个架构图是一致的。
告警页面:
如果你之前没有接触过APM,我想看到这些,应该是有被惊艳到的。的确,APM是应用性能监控或管理的大杀器。除了Skywalking,还有一些开源和商用的APM。网上的文章很多,有兴趣的可以自行Google。
下篇文章介绍一下Skywalking的整体架构。
]]>