前后分离springbootJava web

如何获取一次请求流经方法的调用次数和调用耗时

2020-03-30  本文已影响0人  不怕天黑_0819
如题,在日常开发中,经常会遇到一些问题的技术选型和方案制定。这个文集的目的就是提供一些问题的技术思考,便于我们在遇到问题的时候,可以更快的应答。当然,有些方案会随着时间变得不再合理,这个时候,也需要我们不断学习新的技术来优化我们的方案。
首先,看到这个问题的时候,我们需要明确我们的目的是什么,只有知道我们的目的,才能更好的去实现。
针对如何获取一次请求流经方法的调用次数和调用耗时,我们可能是想去做统计,也可能是想进行性能优化。
比如针对性能优化,使用AOP的代码开发就过于复杂,开发成本高。但是通过加日志打印调用时间需要上线。这个时候我们可以使用一些调优的工具,比如Arthas。本文我们主要还是介绍使用AOP来解决,关于Arthas如何进行问题定位和性能调优,我们后续在其他文章中介绍。

总体思路

过程:
(1) 将需要记录次数和耗时的方法加上 MethodMeasureAnnotation ;
(2) 在 MethodMeasureAspect 的 @PointCut 部分写上需要拦截通知的方法的表达式;
(3) 将 MethodMeasureAspect 作为组件注入到 ServiceAspect 中,并在 ServiceAspect 中打印 MethodMeasureAspect 的内容。

AOP基本概念

哪些场景下,AOP不会生效

如果类 C 有三个公共方法,a,b,c ; a 调用 b ,b 调用 c 。会发现 b,c 是不会执行切面逻辑的。这是因为Spring的AOP主要基于动态代理机制。当调用 a 时,会调用代理的 a 方法,也就进入到切面逻辑,但是当 a 调用 b 时, b 是直接在目标对象上执行,而不是在代理对象上执行,因此,b 是不会进入到切面逻辑的。

代码实现

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.IntSummaryStatistics;
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 className = joinPoint.getTarget().getClass().getSimpleName();
    String methodName = className + "_" + 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:\n");
    Map<String,Integer> sorted =  MapUtil.sortByValue(methodCount);
    sorted.forEach(
        (method, count) -> {
          sb.append("method=" + method + ", " + "count=" + count+'\n');
        }
    );
    sb.append('\n');
    sb.append("MethodCosts:\n");
    methodCost.forEach(
        (method, costList) -> {
          IntSummaryStatistics stat = costList.stream().collect(Collectors.summarizingInt(x->x));
          String info = String.format("method=%s, sum=%d, avg=%d, max=%d, min=%d, count=%d", method,
                                      (int)stat.getSum(), (int)stat.getAverage(), stat.getMax(), stat.getMin(), (int)stat.getCount());
          sb.append(info+'\n');
        }
    );

    sb.append('\n');
    sb.append("DetailOfMethodCosts:\n");
    methodCost.forEach(
        (method, costList) -> {
          String info = String.format("method=%s, cost=%s", method, costList);
          sb.append(info+'\n');
        }
    );
    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;
  }

}

参考连接:https://www.cnblogs.com/lovesqcc/p/9380167.html

上一篇下一篇

猜你喜欢

热点阅读