前言
应用系统在建设和维护过程中,通常会由于业务量增长或版本迭代而产生性能问题。这些问题会对用户操作,业务开展造成较大的影响。在整个性能问题的解决过程中,定位问题原因可以说是最关键的一个环节,如果问题定位不够准确,会造成后续性能优化无法达到预期效果。本文主要介绍了使用elastic-apm组件中的各种特性,帮助开发人员快速准确定位应用系统性能问题。
常见的应用系统性能问题
应用系统无论是单体架构还是分布式架构,耗时主要都发生在以下范围内:
I/O等待和CPU计算
I/O等待包括了数据库I/O,客户端服务端的网络I/O,磁盘I/O,服务间远程调用I/O,消息中间件读写I/O等
CPU计算主要是应用程序执行cpu指令的耗时,如果应用程序是密集计算型,那么这一块的场景会比较多
对于我们常见的CRUD系统来说, I/O上的耗时是远大于CPU计算耗时
下图是一个分布式系统容易出现性能问题的各个环节
应用系统对外提供各类rest api,用户通过客户端或者程序接口对应用系统进行访问,用户请求通过网络运营商到应用系统机房网关接入,最后到达应用层,应用层的服务之间存在互相远程调用情况,应用系统会对数据层进行读写操作,会通过消息队列进行异步消息的读写,业务请求会通过网关返回给用户。
应用系统自身可能也会存在各种定时批处理任务,事件触发的计算任务等
本文中我们对性能问题定位进行了简化,不考虑运营商和网关产生的性能问题,仅针对应用系统本身。从应用架构中可以发现性能问题存在于应用系统内部函数调用和各类服务间远程调用,数据层读写,消息队列读写中。
elastic-apm是什么
ElasticAPM是基于Elastic Stack构建的应用程序性能监视系统,它可以嵌入应用进程内部,实时采集各种运行指标并传输到elasticsearch平台进行聚合统计,通过kibana界面端展示应用系统性能处理能力。以java的应用系统举例,elastic-apm(后续简称apm)能收集的指标主要包括下列:
1.http请求响应耗时
2.函数间调用耗时
3.SQL执行耗时
4.缓存查询耗时
5.程序异常抛出
6.进程CPU,内存使用情况
7.JVM堆内存,非堆内存使用情况
8.进程内部线程数使用情况
9.JVM垃圾回收情况
10.消息队列读写数据处理耗时
apm会对收集到的数据进行图形化展示,把出现性能瓶颈的地方标记出来 ,开发人员定位这些性能问题可以非常便捷,不用再像传统形式那样通过日志记录耗时来一步一步排查。
elastic-apm架构
elastic-apm包括以下几个组件:
•Apm agent,嵌入应用进程的指标收集器,它会负责收集应用进程内部的运行情况,并把这些数据打包发送给apm Server进程,apm agent可以通过多种形式和JAVA应用进程融合
•Apm server,负责接收apm agent,并把数据转发到Elasticsearch, 可以在配置文件中做一些数据过滤,数据包装等工作
•Elasticsearch,ES分布式存储,存储应用程序的运行数据指标
•Kibana APM UI,监控面板,图形化展示apm收集的数据指标,标记性能瓶颈,方便开发人员 快速定位性能问题
先搭建相关环境,安装运行elasticsearch,kibana,apm server
elasticsearch的后台启动,通过下面的命令实现
./bin/elasticsearch -d -p pid //后台启动
运行后可以通过jps -l验证进程是否在运行
可以通过浏览器查看elasticsearch进程运行情况是否异常
后台运行kibana
nohup ./kiabana &
运行apm-Server进程,默认情况下 apm-Server会去尝试连接localhost下面的9200端口,也就是elasticsearch的默认端口
nohup ./apm-server -e &
apm-server运行后,可以在kibana中查看apm-Server运行状态是否正常, 在kibana中apm功能项中确认操作
接下来就是使用apm-agent采集应用进程的数据,并在kibana中展示了,在此之前我们需要运行一个java的进程,为了简单起见,我们可以跑一个springboot的后台进程,容器是tomcat,监听端口是8080
apm-agent目前可以通过两个方式注入应用进程的jvm中,第一种用法是在java -jar命令行中植入对应的jar,如下所示
java -javaagent:/Users/jamwang/Downloads/elastic-apm-agent-1.18.0.RC1.jar -Delastic.apm.service_name=my-cool-service -Delastic.apm.application_packages=org.example,org.another.example -Delastic.apm.server_urls=http://localhost:8200 -jar apm-test-1.2.0.jar
这个含义是在运行apm-test-1.2.0.jar时,同时加载elastic-apm-agent-1.18.0.RC1.jar,并且设置好apm-Server的地址和端口,设置好应用的名字
第二种用法是在应用程序的pom中引入依赖包
<dependency>
<groupId>co.elastic.apm</groupId>
<artifactId>apm-agent-attach</artifactId>
<version>1.18.0.RC1</version>
</dependency>
并且在应用程序中加入一句代码,这样程序在运行时就会加载apm-agent的包并且采集信息发送到apm-server
public static void main(String[] args) {
ElasticApmAttacher.attach();
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
springApplication.run(args);}
带有 apm-agent jar包的应用程序运行起来后会去连接apm-Server的8200端口,并持续将运行情况发送给apm-server,在kibana中能查看到应用程序运行时数据,包括cpu和内存使用率,线程使用情况,堆栈信息,GC信息等
restful API性能问题定位
微服务系统和web系统通常对外都会暴露restful api,使用apm可以快速定位请求响应较慢的restful api。我们通过模拟一个api的请求耗时,看看apm是如何采集展示这些restful api的性能问题
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String index() throws Exception {
Thread.sleep(2000);//模拟处理时间
return "Hello World";
}
尝试请求两次这个api,然后在kibana中可以看到下列信息
apm准确描述了api的类名和入口函数名,统计了相关耗时,点击进入可以查看相关明细
为了更好的模拟生产环境性能问题,这里把例子改了一下,有20%的请求会出现性能问题,再通过测试工具访问100次,看看kibana中的restful api性能信息展示
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String index() throws Exception {
if(Math.random()*10<2){
Thread.sleep(2000);//模拟处理时间
}
return "Hello World";
}
上图表明,有20%左右的请求的耗时是在2秒,这个分布是符合我们程序的预期的
通过以上例子可以论证,apm能快速定位应用系统的restful api性能问题
消息队列性能问题
应用系统在消息队列消费场景下也容易出现性能问题,导致的现象主要是队列消息堆积
测试程序是一个消费kafka的应用程序 ,每读取一条消息后,在消费处理函数中sleep一段时间,模拟消息处理的耗时,程序使用单线程消息处理模式 ,一条消息处理完才会去kafka读取下一条消息
上图可以看到,apm准确定位了消费者在哪个topic下存在性能问题
sql性能问题
应用访问数据库时,执行的sql也会被apm抓取到,并且统计执行时间,下面是一个抓取sql执行耗时的例子
上图会标明在整个api处理过程中,每个sql的执行情况,方便直接定位性能较差的sql
redis缓存访问性能问题定位
程序访问redis或者其他网络缓存也会被apm抓取到
函数调用性能问题
应用的函数内部调用耗时,是不会被自动记录的,这时候需要加入apm的注解来监控特定的函数耗时
程序的调用通常如上图所示,如果想知道上面各个函数是否存在性能问题,需要通过apm注解来解决
@CaptureSpan
private void caculate1() throws InterruptedException { Thread.sleep(1000);}
添加了@CaptureSpan注解的函数的调用耗时会被统计到对应的transaction中
未捕获的异常
java的程序会在某些出错情况下抛出异常,应用程序可以选择捕获异常进行处理或者向上继续抛出。如果应用程序没有捕获异常,最终就会继续抛出到JVM中。jvm会对未捕获异常进行console打印,应用程序在启动时如果未重定向console打印到文件的话, 这部分的异常就会丢失掉。较严重的异常会导致jvm崩溃,如果这些异常没有打印出来,对jvm崩溃的问题排查就会比较困难。
apm会自动监控jvm中出现的未捕获异常,并在kibana中展示出来 ,可以比较方便的定位这些漏捕获或未捕获的异常
jvm性能问题
java系统在进行full GC的时候会把内部工作线程都挂起来等待垃圾回收结束后再工作,频繁的gc操作会使系统出现性能问题。
这里我们模拟一个java程序产生频繁gc,然后再看看apm中这部分数据是如何展示的
首先我们写一个模拟垃圾回收的java程序,这个程序定时往一个队列里放string,又定时从队列中取出string,这样会造成jvm中大量的堆内存使用,从而引发频繁的GC操作。
@Scheduled(fixedDelay = 1000)
public void run(){
System.out.println("run Scheduled");
for(int i=0;i<100000;++i){
queue.add(new String("hello")); }
}
@Scheduled(fixedDelay = 1000)
public void run2(){
System.out.println("delete Scheduled");
for(int i=0;i<100000;++i){ queue.poll(); }
}
在apm的JVM指标中可以看到应用频繁的进行GC操作 ,且能统计出每次的GC耗时。这个时候可以登录到服务器上进行验证,通过jstat –gcutil pid查看应用的gc情况,可以重点关注年轻代,年老代的百分占比,gc的次数和耗时。如果发现gc耗时和次数较大,多数情况下会出现gc引发的性能问题。
分布式调用链性能问题
微服务架构中,一个业务请求链上会调用很多其他服务的接口,这些接口调用通常都是远程同步调用,同时这些远程服务内部可能也会继续调用其他接口,这样 就形成了一个远程调用链的过程。在这个过程中,某一个环节出现了性能问题会导致整个业务请求变慢,一旦调用链过长,定位性能问题就变得十分困难。
如果应用服务都使用apm来采集性能指标 ,可以直接在统计分析中看到单个服务请求中的子服务请求耗时在整个调用中的占比 ,很容易定位微服务调用链中的性能问题,如下图展示了A服务调用B服务,B服务内部又再次调用A服务的整个调用链耗时。
总结
elastic-apm提供了代码植入,jar包加载,函数注解等方式,采集应用系统运行时信息,存储在elasticsearch分布式数据库中,通过kibana提供友好的界面展示,方便开发和运维人员及时了解应用系统的处理性能和相关性能瓶颈,帮助开发人员快速定位应用性能瓶颈,包括定位各类I/O操作 ,函数内部调用耗时,慢SQL问题,GC性能问题,分布式调用链性能等。
网友评论