使用AOP拦截器获取一次请求流经方法的调用次数和调用耗时
Posted lovesqcc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用AOP拦截器获取一次请求流经方法的调用次数和调用耗时相关的知识,希望对你有一定的参考价值。
引语
作为工程师,不能仅仅满足于实现了现有的功能逻辑,还必须深入认识系统。一次请求,流经了哪些方法,调用了多少次DB操作,多少次API操作,多少次IO操作,多少CPU操作,各耗时多少 ? 开发者必须知道这些运行时数据,才能对系统的运行有更深入的理解,更好滴提升系统的性能和稳定性。
完成一次订单导出任务,实际上是一个比较复杂的过程:需要访问ES 来查询订单,调用 API 及访问 Hbase 获取订单详情数据,写入和上传报表文件,更新数据库,上报日志数据等;在大流量导出的情形下,采用批量并发策略,多线程来获取订单详情数据, IO 访问就更加难以掌控了。
本文主要介绍使用AOP拦截器来获取一次请求流经方法的调用次数和调用耗时。
总体思路
使用AOP思想来解决。增加一个注解,然后增加一个AOP拦截器 methodAspect ,在携带注解的方法执行前后,记录方法的调用次数及耗时。
methodAspect 内部含有两个变量 methodCount, methodCost ,都采用了 ConcurrentHashMap 。这是因为方法执行时,可能是多线程写入这两个变量。
使用:
(1) 将需要记录次数和耗时的方法加上 MethodMeasureAnnotation 即可;
(2) 将 MethodMeasureAspect 作为组件注入到 ServiceAspect 中,并在 ServiceAspect 中打印 MethodMeasureAspect 的内容。
源代码
MethodMeasureAspect.java
package zzz.study.aop;
import zzz.study.util.MapUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 记录一次请求中,流经的所有方法的调用耗时及次数
*
*/
@Component
@Aspect
public class MethodMeasureAspect {
private static final Logger logger = LoggerFactory.getLogger(MethodMeasureAspect.class);
private Map<String, Integer> methodCount = new ConcurrentHashMap();
private Map<String, List<Integer>> methodCost = new ConcurrentHashMap();
@SuppressWarnings(value = "unchecked")
@Around("@annotation(zzz.study.aop.MethodMeasureAnnotation)")
public Object process(ProceedingJoinPoint joinPoint) {
Object obj = null;
String methodName = getMethodName(joinPoint);
long startTime = System.currentTimeMillis();
try {
obj = joinPoint.proceed();
} catch (Throwable t) {
logger.error(t.getMessage(), t);
} finally {
long costTime = System.currentTimeMillis() - startTime;
logger.info("method={}, cost_time={}", methodName, costTime);
methodCount.put(methodName, methodCount.getOrDefault(methodName, 0) + 1);
List<Integer> costList = methodCost.getOrDefault(methodName, new ArrayList<>());
costList.add((int)costTime);
methodCost.put(methodName, costList);
}
return obj;
}
public String getMethodName(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
return method.getName();
}
public String toString() {
StringBuilder sb = new StringBuilder("MethodCount:
");
Map<String,Integer> sorted = MapUtil.sortByValue(methodCount);
sorted.forEach(
(method, count) -> {
sb.append("method=" + method + "," + "count=" + count+'
');
}
);
sb.append('
');
sb.append("MethodCosts:
");
methodCost.forEach(
(method, costList) -> {
String info = String.format("method=%s, cost=%s, stat=%s", method, costList,
costList.stream().collect(Collectors.summarizingInt(x->x)));
sb.append(info+'
');
}
);
return sb.toString();
}
}
MethodMeasureAnnotation.java
package zzz.study.aop;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 记录方法调用
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MethodMeasureAnnotation {
}
MapUtil.java
public class MapUtil {
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
Map<K, V> result = new LinkedHashMap<>();
Stream<Map.Entry<K, V>> st = map.entrySet().stream();
st.sorted(Comparator.comparing(e -> e.getValue())).forEach(e -> result.put(e.getKey(), e.getValue()));
return result;
}
}
以上是关于使用AOP拦截器获取一次请求流经方法的调用次数和调用耗时的主要内容,如果未能解决你的问题,请参考以下文章