借助 AOP 为 Java Web 应用记录性能数据

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
日志服务 SLS,月写入数据量 50GB 1个月
应用实时监控服务-用户体验监控,每月100OCU免费额度
简介:

作为开发者,应用的性能始终是我们最感兴趣的话题之一。然而,不是所有的开发者都对自己维护的应用的性能有所了解,更别说快速定位性能瓶颈并实施解决方案了。

今年北京 Velocity 的赞助商大多从事 APM 领域,提供性能剖析、可视化甚至优化的解决方案。这些厂商的产品看起来能够很好地帮助中小企业的开发者解决应用性能上的缺陷,但是这些产品几乎都有着一个致命的缺陷:极强的侵入性。

借助 AOP 为 Java Web 应用记录性能数据

开发者需要在业务生产代码中嵌入 APM 厂商提供的埋点代码,才能够使用 APM 厂商提供的 Saas 服务。在瞬息万变的技术大潮中,这种代码级别的侵入和绑定,总是让开发者忧心忡忡。如果我作为架构师,在自建 APM 还是使用 Saas APM 上,我也会谨慎考虑。

然而无论自建 APM 还是使用 Saas 服务,其底层模型无非就是海量日志的实时处理,数据来源就是应用产生的性能日志了。

If we have data, let’s look at data. If all we have are opinions, let’s go with mine.

Jim Barksdale

这是一个数据为王的时代,夸张一点说,数据可以指导一切!

言归正传,如果我们不希望使用 APM 尝试提供的强侵入的服务,我们就只能自建服务了,比如以 AOP 的方式采集线程内调用树以及调用开销并输出日志,然后使用 ELK(Elasticsearch, Logstash, and Kibana) 去采集日志并提供搜索、可视化等功能。如果采集的日志仅作为离线计算使用,可以直接用 Flume 把日志写入 HDFS。

随着系统流量越来越大,上述的方案渐渐就扛不住了,然后就需要自己实现高性能的日志采集 Agent,把采集到的日志一股脑写入 Kafuka 之类的能扛大量堆积消息的 MQ 里面,然后使用 Storm/JStorm 做实时的流式计算。

前些日子我简单搞了一个基于 AOP 来抓取调用树和开销的尝试,感觉有点意思,分享一下。

抓取调用树和时间开销

在 Java 里面获取代码块的时间开销最常见的手段就是 System.currentTimeMillis()。Apache 和 Guava 等流行类库都有对获取时间开销这一功能的封装类 StopWatch。

捕获调用树就没有什么常见的封装了。一种推荐的做法,是在一次调用中,给每个要剖析的代码块一个唯一的标记,这个标记要能够体现代码块之间的嵌套、顺序等关系。

举个栗子,我们有如下调用关系。

 
 
  1. func1 
  2. +- func2 
  3. |  +- func3 
  4. |  /- func4 
  5. /- func5 

为了体现调用之间的嵌套和顺序,我们给 func1 标记 0,给 func2 标记 0.1,给 func3 标记 0.1.1,给 func4 标记 0.1.2,给 func5 标记 0.2。如此一来,我们便能够轻易地根据标记重建出调用树。

我们可以把调用树的抓取和记录每个代码块的时间开销的功能以线程安全的手法封装起来,给这个封装起一个类似于 Profiler 的名字。Profiler 提供 2 个静态方法,enter 在进入代码块之前调用,exit 在代码块结束之后调用。

在实现 Profiler 的时候,需要给每个线程维护一个调用栈,以及剖析结果列表。基本上可以实现为 enter 压栈,exit 退栈并把结果放入结果列表,当调用栈退空后,输出完整的剖析结果。

AOP 与方法拦截器

Profiler 有一个需要严格执行的约定,就是 enter 和 exit 必须成对调用,就像 C++ 里面 new 和 delete 必须成对出现一样,否则内存会被直接打爆,远不是内存泄露这么简单。

这种约定如果写到业务代码中,会死的很难看,各种 try finally 硬生生的把业务逻辑打断,本来业务代码就已经很恶心了,这么一搞简直没法维护。

所以我们需要一种比较科学的方式,以无入侵的方式实现对 Profiler 的正确调用。AOP 是一种合适的工具。

这里以 Spring AOP 为例,实现一个简单的例子。

首先引入 Spring AOP 的依赖,或者包含 org.aopalliance.intercept.MethodInterceptor 的包。

 
 
  1. <dependency> 
  2.     <groupId>org.springframework</groupId> 
  3.     <artifactId>spring-aop</artifactId> 
  4.     <version>2.5.6</version> 
  5. </dependency> 

如果需要代码能够运行,还需要引入 cglib 的依赖。

 
 
  1. <dependency> 
  2.     <groupId>cglib</groupId> 
  3.     <artifactId>cglib-nodep</artifactId> 
  4.     <version>2.2</version> 
  5. </dependency> 

方法拦截器的参考实现如下,使用 try finally 这样的 code pattern 去保证 Profiler 被正确使用。

 
 
  1. public class Interceptor implements MethodInterceptor { 
  2.     @Override 
  3.     public Object invoke(MethodInvocation invocation) throws Throwable { 
  4.         Class clazz = invocation.getMethod().getDeclaringClass(); 
  5.         String method = invocation.getMethod().getName(); 
  6.         String mark = clazz.getCanonicalName() + "#" + method; 
  7.         Profiler.enter(mark); 
  8.         try { 
  9.             return invocation.proceed(); 
  10.         } finally { 
  11.             String log = Profiler.exit(); 
  12.             if (log != null) { 
  13.                 System.out.println(log); 
  14.             } 
  15.         } 
  16.     } 



来源:51CTO

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
目录
打赏
0
0
0
0
16429
分享
相关文章
java常用数据判空、比较和类型转换
本文介绍了Java开发中常见的数据处理技巧,包括数据判空、数据比较和类型转换。详细讲解了字符串、Integer、对象、List、Map、Set及数组的判空方法,推荐使用工具类如StringUtils、Objects等。同时,讨论了基本数据类型与引用数据类型的比较方法,以及自动类型转换和强制类型转换的规则。最后,提供了数值类型与字符串互相转换的具体示例。
137 3
Java爬虫获取微店快递费用item_fee API接口数据实现
本文介绍如何使用Java开发爬虫程序,通过微店API接口获取商品快递费用(item_fee)数据。主要内容包括:微店API接口的使用方法、Java爬虫技术背景、需求分析和技术选型。具体实现步骤为:发送HTTP请求获取数据、解析JSON格式的响应并提取快递费用信息,最后将结果存储到本地文件中。文中还提供了完整的代码示例,并提醒开发者注意授权令牌、接口频率限制及数据合法性等问题。
|
24天前
|
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
65 21
使用Web浏览器访问UE应用的最佳实践
在3D/XR应用开发中,尤其是基于UE(虚幻引擎)开发的高精度场景,传统终端因硬件局限难以流畅运行高帧率、复杂效果的三维应用。实时云渲染技术,将渲染任务转移至云端服务器,降低终端硬件要求,确保用户获得流畅体验。具备弹性扩展、优化传输协议、跨平台支持和安全性等优势,适用于多种终端和场景,特别集成像素流送技术,帮助UE开发者实现低代码上云操作,简化部署流程,保留UE引擎的强大开发能力,确保画面精美且终端轻量化。
102 17
使用Web浏览器访问UE应用的最佳实践
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
15 0
基于Java的Hadoop文件处理系统:高效分布式数据解析与存储
本文介绍了如何借鉴Hadoop的设计思想,使用Java实现其核心功能MapReduce,解决海量数据处理问题。通过类比图书馆管理系统,详细解释了Hadoop的两大组件:HDFS(分布式文件系统)和MapReduce(分布式计算模型)。具体实现了单词统计任务,并扩展支持CSV和JSON格式的数据解析。为了提升性能,引入了Combiner减少中间数据传输,以及自定义Partitioner解决数据倾斜问题。最后总结了Hadoop在大数据处理中的重要性,鼓励Java开发者学习Hadoop以拓展技术边界。
57 7
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
82 1
|
1月前
|
java怎么统计每个项目下的每个类别的数据
通过本文,我们详细介绍了如何在Java中统计每个项目下的每个类别的数据,包括数据模型设计、数据存储和统计方法。通过定义 `Category`和 `Project`类,并使用 `ProjectManager`类进行管理,可以轻松实现项目和类别的数据统计。希望本文能够帮助您理解和实现类似的统计需求。
109 17
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
100 10
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等