借助 AOP 为 Java Web 应用记录性能数据
作为开发者,应用的性能始终是我们最感兴趣的话题之一。然而,不是所有的开发者都对自己维护的应用的性能有所了解,更别说快速定位性能瓶颈并实施解决方案了。
今年北京 Velocity 的赞助商大多从事 APM 领域,提供性能剖析、可视化甚至优化的解决方案。这些厂商的产品看起来能够很好地帮助中小企业的开发者解决应用性能上的缺陷,但是这些产品几乎都有着一个致命的缺陷:极强的侵入性。
开发者需要在业务生产代码中嵌入 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。
捕获调用树就没有什么常见的封装了。一种推荐的做法,是在一次调用中,给每个要剖析的代码块一个唯一的标记,这个标记要能够体现代码块之间的嵌套、顺序等关系。
举个栗子,我们有如下调用关系。
func1 +- func2 | +- func3 | /- func4 /- 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 的包。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>2.5.6</version>
</dependency>
如果需要代码能够运行,还需要引入 cglib 的依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
方法拦截器的参考实现如下,使用 try finally 这样的 code pattern 去保证 Profiler 被正确使用。
public class Interceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Class clazz = invocation.getMethod().getDeclaringClass();
String method = invocation.getMethod().getName();
String mark = clazz.getCanonicalName() + "#" + method;
Profiler.enter(mark);
try {
return invocation.proceed();
} finally {
String log = Profiler.exit();
if (log != null) {
System.out.println(log);
}
}
}
}
借助 AOP 为 Java Web 应用记录性能数据的更多相关文章
- java getway springcloud 记录请求数据
java getway springcloud 记录请求数据,兼容post请求无参数和response返回无参数 方式1 import com.icar.web.icarutils.util.Clie ...
- JAVA WEB ------ 文件下载及导出数据到office Execl表格
文件下载需要五步: 1.设置文件ContentType类型 // 设置文件ContentType类型,这样设置,会自动判断下载文件类型 response.setContentType("mu ...
- Java WEB中的HttpServletResponse数据传递
1.什么是HttpServletResponse 2.使用HttpServletResponse向浏览器发送数据及相关实例. 实例1:实现文件下载功能 实例2:实现验证码注册 实例3:实现页面3秒后跳 ...
- java web简单的对数据库存数据
1.建立一个表,分别有Coursename,teachername,Place,Id;这些数据跟sql语句中的相对应 2.在src包目录下创建这些类 3.在WebContent目录下创建jsp,Mai ...
- Java Web 高性能开发,第 2 部分: 前端的高性能
Web 发展的速度让许多人叹为观止,层出不穷的组件.技术,只需要合理的组合.恰当的设置,就可以让 Web 程序性能不断飞跃.Web 的思想是通用的,它们也可以运用到 Java Web.这一系列的文章, ...
- Java Web实现用户登录功能
java web 学习记录一下 mvc结构实现mysql 连接 什么是mvc MVC是模型(model).视图(view).控制(controller)这三个单词上的首字母组成.它是一种应用模型,它的 ...
- Java Web文件上传原理分析(不借助开源fileupload上传jar包)
Java Web文件上传原理分析(不借助开源fileupload上传jar包) 博客分类: Java Web 最近在面试IBM时,面试官突然问到:如果让你自己实现一个文件上传,你的代码要如何写,不 ...
- JAVA WEB快速入门之通过一个简单的Spring项目了解Spring的核心(AOP、IOC)
接上篇<JAVA WEB快速入门之从编写一个JSP WEB网站了解JSP WEB网站的基本结构.调试.部署>,通过一个简单的JSP WEB网站了解了JAVA WEB相关的知识,比如:Ser ...
- 记录一个奇怪的异常,无法还原此异常。 普通的Maven Java Web 项目
项目 : 普通的Maven Java Web 项目 操作记录: 使用 Maven 构建项目,指令 tomcat7:run 无异常 但使用 eclipse 的 tomcat 运行项目,报此异常. 后面从 ...
随机推荐
- mini2440 MJPG_STREAMER 产生问题
usb摄像头芯片是中芯微的zc0301pl, http://www.vimicro.com.cn/product/pdf/ZC301PL-1107-V10-EN.pdf [root@FriendlyA ...
- linux 信号处理
查看信号 kill -l 信号实际就是一个进程发送给另一个进程的消息
- HTML5基础知识(一)---标签
在HTML5中,Web页面中重新调整了页面规划,这其中新引入了几个新标记. 我们将创建一个简单的Web页面,该页面包含一个Header区.一个Navigation区.一个Article区(包含三个部分 ...
- PHP正则表达式 验证电子邮件地址
我们最经常遇到的验证,就是电子邮件地址验证.网站上常见.各种网页脚本也都常用“正则表达式”(regular expression)对我们输入的电子邮件地址进行验证,判断是否合法.有的还能分解出用户名和 ...
- thinkphp给图片打水印不清晰
项目中打印条形码的函数,从thinkphp自带的water函数修改而来的. 贴上代码: /** * water2 * 改写thinkphp的water函数更强健的函数,增加了写入位置参数 去掉了alp ...
- kindeditor编辑器代码过滤解决方法.
很多朋友在使用Kindeditor编辑器的时候都会遇到这样一个问题,如:给A标签加上title属性过后,浏览的时候,却神奇般地发现title属性没有了.再次切换html源代码的时候,返现编辑器将tit ...
- Boost.Build 简明教程
Boost.Build 简明教程 目录1. 介绍2. 构建过程3. 基本任务4. 项目管理5. 最佳实践6. 规则参考7. 特征参考 介绍 编译器和平台无关编译系统Boost.Build是一个高级编译 ...
- Net Core Docker
Net Core Docker轻量级的web框架 .net core现在已经有了大的发展,虽然笔者现在已经从事python开发,但是一直在关注.net的发展,在逛博客园的时候,发现有大家都会提到N ...
- iOS Copy 和 MutableCopy的区别 深浅拷贝的区别-供参考
概述 对于系统的非容器类对象,对一不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝).如果是对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的. 对于 ...
- caffe之(二)pooling层
在caffe中,网络的结构由prototxt文件中给出,由一些列的Layer(层)组成,常用的层如:数据加载层.卷积操作层.pooling层.非线性变换层.内积运算层.归一化层.损失计算层等:本篇主要 ...