Profiler是一个时间统计程序,他通过在程序中埋点,将埋点时间记录入线程变量中以实现革离,最后dump出结果,得出埋点时间树。
在消息中心项目中主要采用两种埋点方式,一种是业务程序中直接埋入Profiler代码,另一种是利用Spring方法拦截器,将Profiler代码埋入拦截器中。两种方法各有优劣,所以在项目中两种方式综合应用,相互补充。
优点是在业务代码中埋点可以设置任意粒度,能够显示程序中微小范围内的时间变化。缺点是Profiler耦合进了业务代码,埋点分布于业务程序的各处,不便于阅读和修改,同时由于其要带try{}finally{}块,将业务代码分割的支离破碎,造成业务代码的断层。用例如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 |
public
void sendWithChannels(final
MessageTaskDO messageTask, final
MessageTypeDO messageType,final
UserInfoDO userInfo, final
int chs) throws
MessageException{ int
channelInt = chs; UserInfoFuture userInfoFuture = null; Profiler.start("TagTask isFinish"); try{ if
(messageTask.getTaskPreStatus() == MessageTaskDO.FALURE_NOTSEND_RETRY && messageStatusDAO.isFinish(messageTask.getTaskID(), userInfo.getTargetID())){ return; } Profiler.enter("Task Filter"); try{ channelInt = getChannelsAfterFilter(messageType, userInfo, channelInt); }finally{ Profiler.release(); } Profiler.enter("Task Fill UserInfo"); try{ channelInt = getChannelsAfterUInfoFilled(userInfo, userInfoFuture, channelInt); }finally{ Profiler.release(); } List<Channel> channels = null; Map<Channel, Integer> channelTemplateId = null; Map<String, String> mapContext = null; Profiler.enter("Task Fill Context"); try{ channels = Channel.parseChannels(channelInt); }finally{ Profiler.release(); } Profiler.enter("Task Send with Channels"); try{ for
(final
Channel channel : channels){ final
boolean couldSend = true; String sendToAddress = ""; if
(couldSend){ Profiler.enter("send of Channel choose"); if
(channel == Channel.WANGWANG){ sendToAddress = userInfo.getUserNick(); }else{ logger.error("MessageTask:"
+ messageTask.getTaskID() + " is not channel"); continue; } final
Integer templetId = channelTemplateId.get(channel); final
String replyTo = "taobao@taobao.com"; Profiler.release(); } } }finally{ Profiler.release(); } }finally{ Profiler.release(); final
String detail = Profiler.dump("Detail: ", " "); logger.warn(String.format("调用服务:%s的方法%s耗时:%dms,超过预期\n%s\n", new
Object[] {invocation.getThis().getClass().getName(), invocation.getMethod().getName(),duration, detail })); Profiler.reset(); } } |
通过上面代码可以看出,直接埋点虽然可以控制到很小的粒度,但埋点代码混杂于业务代码,将业务代码分割的支离破碎,是一种很不优雅的用法。
优点是和业务代码解耦,要统计哪个方法的耗时只需要修改拦截器配置文件和拦截器中的埋点代码即可,和业务代码完全无关。缺点是埋点粒度只能到方法这个级别,方法内部的耗时统计就无法完成,同时只能拦截Spring Bean的方法,Bean内部间方法调用无法拦截,只能拦截外部调用Bean的方法。用例如下:
xml配置如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 |
<!-- 自动代理所有的advisor --><bean id="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <property name="proxyTargetClass"
value="true"/></bean><bean id="interceptorAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 业务实现方法名匹配 --> <property name="patterns"> <list> <value>com.taobao.messenger.dal.*DAO.*</value> <value>com.taobao.messenger.service.*</value> </list> </property> <property name="advice"> <ref bean="interceptorAdvice"
/> </property></bean><bean id="interceptorAdvice"
class="com.taobao.messenger.proxy.ProfilerInterceptor"></bean> |
java代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 |
public
Object invoke(final
MethodInvocation invocation) throws
Throwable { final
String key = invocation.getThis().getClass().getName() +MessageConstants.DOT_SPLIT+invocation.getMethod().getName(); boolean
resetFlag = false; if(Profiler.getEntry() == null
|| (key.equals(TagTaskDispatcher.class.getName() +MessageConstants.DOT_SPLIT+"sendMessage"))){ Profiler.start(key); resetFlag = true; }else{ Profiler.enter(key); } Object result = null; try{ result = invocation.proceed(); } finally{ Profiler.release(); if(resetFlag || (key.equals(TagTaskDispatcher.class.getName() +MessageConstants.DOT_SPLIT+"sendMessage"))){ try{if(threshold.containsKey(key)){ final
Long duration = Profiler.getDuration(); if(duration>threshold.get(key)){ final
String detail = Profiler.dump("Detail: ", " "); logger.warn(String.format("调用服务:%s的方法%s耗时:%dms,超过预期\n%s\n", new
Object[] { invocation.getThis().getClass().getName(), invocation.getMethod().getName(),duration, detail })); } } } finally{ Profiler.reset(); }} } return
result; } |
通过上面代码可以看出,Profiler代码和业务代码从表象上看完全无关,从结构上和管理上都比较不错。
使用Profile后产生的结果树如下:
【转】Profiler使用方法,布布扣,bubuko.com
原文:http://www.cnblogs.com/bing198333/p/3627284.html