当前位置:   article > 正文

Spring-Boot+AOP+统计单次请求方法的执行次数和耗时情况_runtimemxbean.getname()耗时长

runtimemxbean.getname()耗时长

参考文章:Spring-Boot+AOP+统计单次请求方法的执行次数和耗时情况

SpringBoot(审计) 统计接口调用次数及成功率

ControllerInterceptor 

  1. package com.fiend.ou.cdp.monitorcollect.aspect;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.*;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.stereotype.Component;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. /**
  11. * @author langpf
  12. */
  13. @Aspect
  14. @Component
  15. public class WebQueryAespect {
  16. private final Logger log = LoggerFactory.getLogger(getClass());
  17. //ThreadLocal 维护变量 避免同步
  18. //ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
  19. ThreadLocal<Long> startTime = new ThreadLocal<>();// 开始时间
  20. /**
  21. * map1存放方法被调用的次数O
  22. */
  23. ThreadLocal<Map<String, Long>> methodCalledNumMap = new ThreadLocal<>();
  24. /**
  25. * map2存放方法总耗时
  26. */
  27. ThreadLocal<Map<String, Long>> methodCalledTimeMap = new ThreadLocal<>();
  28. /**
  29. * 定义一个切入点. 解释下:
  30. * <p>
  31. * ~ 第一个 * 代表任意修饰符及任意返回值. ~ 第二个 * 定义在web包或者子包 ~ 第三个 * 任意方法 ~ .. 匹配任意数量的参数.
  32. */
  33. static final String pCutStr = "execution(public * com.fiend.*..*(..))";
  34. @Pointcut(value = pCutStr)
  35. public void logPointcut() {
  36. }
  37. /**
  38. * Aop:环绕通知 切整个包下面的所有涉及到调用的方法的信息
  39. * @param joinPoint jp
  40. * @return o
  41. * @throws Throwable t
  42. */
  43. @Around("logPointcut()")
  44. public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
  45. //初始化 一次
  46. if (methodCalledNumMap.get() == null) {
  47. methodCalledNumMap.set(new HashMap<>());
  48. }
  49. if (methodCalledTimeMap.get() == null) {
  50. methodCalledTimeMap.set(new HashMap<>());
  51. }
  52. long start = System.currentTimeMillis();
  53. try {
  54. Object result = joinPoint.proceed();
  55. if (result == null) {
  56. //如果切到了 没有返回类型的void方法,这里直接返回
  57. return null;
  58. }
  59. long end = System.currentTimeMillis();
  60. log.info("===================");
  61. String targetClassName = joinPoint.getSignature().getDeclaringTypeName();
  62. String methodName = joinPoint.getSignature().getName();
  63. Object[] args = joinPoint.getArgs();// 参数
  64. int argsSize = args.length;
  65. String argsTypes = "";
  66. String typeStr = joinPoint.getSignature().getDeclaringType().toString().split(" ")[0];
  67. String returnType = joinPoint.getSignature().toString().split(" ")[0];
  68. log.info("类/接口:" + targetClassName + "(" + typeStr + ")");
  69. log.info("方法:" + methodName);
  70. log.info("参数个数:" + argsSize);
  71. log.info("返回类型:" + returnType);
  72. if (argsSize > 0) {
  73. // 拿到参数的类型
  74. for (Object object : args) {
  75. argsTypes += object.getClass().getTypeName().toString() + " ";
  76. }
  77. log.info("参数类型:" + argsTypes);
  78. }
  79. Long total = end - start;
  80. log.info("耗时: " + total + " ms!");
  81. if (methodCalledNumMap.get().containsKey(methodName)) {
  82. Long count = methodCalledNumMap.get().get(methodName);
  83. methodCalledNumMap.get().remove(methodName);//先移除,在增加
  84. methodCalledNumMap.get().put(methodName, count + 1);
  85. count = methodCalledTimeMap.get().get(methodName);
  86. methodCalledTimeMap.get().remove(methodName);
  87. methodCalledTimeMap.get().put(methodName, count + total);
  88. } else {
  89. methodCalledNumMap.get().put(methodName, 1L);
  90. methodCalledTimeMap.get().put(methodName, total);
  91. }
  92. return result;
  93. } catch (Throwable e) {
  94. long end = System.currentTimeMillis();
  95. log.info("====around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : "
  96. + e.getMessage());
  97. throw e;
  98. }
  99. }
  100. //对Controller下面的方法执行前进行切入,初始化开始时间
  101. @Before(value = "execution(public * com.fiend.*.controller.*.*(..))")
  102. public void beforeMethod(JoinPoint jp) {
  103. startTime.set(System.currentTimeMillis());
  104. }
  105. //对Controller下面的方法执行后进行切入,统计方法执行的次数和耗时情况
  106. //注意,这里的执行方法统计的数据不止包含Controller下面的方法,也包括环绕切入的所有方法的统计信息
  107. @AfterReturning(value = "execution(* com.fiend.*.controller.*.*(..))")
  108. public void afterMethod(JoinPoint jp) {
  109. long end = System.currentTimeMillis();
  110. long total = end - startTime.get();
  111. String methodName = jp.getSignature().getName();
  112. log.info("连接点方法为:" + methodName + ",执行总耗时为:" + total + "ms");
  113. //重新new一个map
  114. Map<String, Long> map = new HashMap<>();
  115. //从map2中将最后的 连接点方法给移除了,替换成最终的,避免连接点方法多次进行叠加计算
  116. //由于map2受ThreadLocal的保护,这里不支持remove,因此,需要单开一个map进行数据交接
  117. for (Map.Entry<String, Long> entry : methodCalledTimeMap.get().entrySet()) {
  118. if (entry.getKey().equals(methodName)) {
  119. map.put(methodName, total);
  120. } else {
  121. map.put(entry.getKey(), entry.getValue());
  122. }
  123. }
  124. for (Map.Entry<String, Long> entry : methodCalledNumMap.get().entrySet()) {
  125. for (Map.Entry<String, Long> entry2 : map.entrySet()) {
  126. if (entry.getKey().equals(entry2.getKey())) {
  127. System.err.println(entry.getKey() + ",被调用次数:" + entry.getValue() + ",综合耗时:" + entry2.getValue() + "ms");
  128. }
  129. }
  130. }
  131. }
  132. }

2. 统计接口调用次数及成功率

介绍:
  很多时候会需要提供一些统计记录的,比如某个服务一个月的被调用量、接口的调用次数、成功调用次数等等。
优点:
  使用AOP+Hendler对业务逻辑代码无侵入,完全解耦。通过spring boot自带的健康检查接口(/health)方便、安全。
注意:
  数据没有被持久化,只保存在内存中,重启后数据将被重置。可按需自己实现 
代码:
  AOP:在AOP中调用Handler

2.1 ControllerAdvice

  1. @Component
  2. @Aspect
  3. public class ControllerAdvice {
  4. private static ILogger log = LoggerFactory.getLogger(ControllerAdvice.class);
  5. @Around("execution(public * *..*controller.*.*(..))")
  6. public Object handle(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
  7. Object result;
  8. try {
  9. Function<ProceedingJoinPoint, AbstractControllerHandler> build = AbstractControllerHandler.getBuild();
  10. if (null == build) {
  11. AbstractControllerHandler.registerBuildFunction(DefaultControllerHandler::new);
  12. }
  13. build = AbstractControllerHandler.getBuild();
  14. AbstractControllerHandler controllerHandler = build.apply(proceedingJoinPoint);
  15. if (null == controllerHandler) {
  16. log.warn(String.format("The method(%s) do not be handle by controller handler.", proceedingJoinPoint.getSignature().getName()));
  17. result = proceedingJoinPoint.proceed();
  18. } else {
  19. result = controllerHandler.handle();
  20. }
  21. } catch (Throwable throwable) {
  22. RuntimeHealthIndicator.failedRequestCount++;
  23. log.error(new Exception(throwable), "Unknown exception- -!");
  24. throw throwable;
  25. }
  26. return result;
  27. }
  28. }

2.2 Handler:执行记录的逻辑

2.2.1 抽象类:AbstractControllerHandler 

  1. public abstract class AbstractControllerHandler {
  2. private static ILogger log = LoggerFactory.getLogger(AbstractControllerHandler.class);
  3. private static Function<ProceedingJoinPoint, AbstractControllerHandler> build;
  4. public static Function<ProceedingJoinPoint, AbstractControllerHandler> getBuild() {
  5. return build;
  6. }
  7. public static void registerBuildFunction(Function<ProceedingJoinPoint, AbstractControllerHandler> build) {
  8. Assert.isNotNull(build, "build");
  9. AbstractControllerHandler.build = build;
  10. }
  11. protected ProceedingJoinPoint proceedingJoinPoint;
  12. protected HttpServletRequest httpServletRequest;
  13. protected String methodName;
  14. protected String uri;
  15. protected String requestBody;
  16. protected String ip;
  17. protected Method method;
  18. protected boolean inDataMasking;
  19. protected boolean outDataMasking;
  20. public AbstractControllerHandler(ProceedingJoinPoint proceedingJoinPoint) {
  21. Assert.isNotNull(proceedingJoinPoint, "proceedingJoinPoint");
  22. this.proceedingJoinPoint = proceedingJoinPoint;
  23. Signature signature = this.proceedingJoinPoint.getSignature();
  24. this.httpServletRequest = this.getHttpServletRequest(this.proceedingJoinPoint.getArgs());
  25. this.methodName = signature.getName();
  26. this.uri = null == this.httpServletRequest ? null : this.httpServletRequest.getRequestURI();
  27. this.requestBody = this.formatParameters(this.proceedingJoinPoint.getArgs());
  28. this.ip = null == this.httpServletRequest ? "" : CommonHelper.getIp(this.httpServletRequest);
  29. this.inDataMasking = false;
  30. this.outDataMasking = false;
  31. if (signature instanceof MethodSignature) {
  32. MethodSignature methodSignature = (MethodSignature) signature;
  33. try {
  34. this.method = proceedingJoinPoint.getTarget().getClass().getMethod(this.methodName, methodSignature.getParameterTypes());
  35. if (null != this.method) {
  36. LogDataMasking dataMasking = this.method.getDeclaredAnnotation(LogDataMasking.class);
  37. if (null != dataMasking) {
  38. this.inDataMasking = dataMasking.in();
  39. this.outDataMasking = dataMasking.out();
  40. }
  41. }
  42. } catch (NoSuchMethodException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. }
  47. public abstract Object handle() throws Throwable;
  48. protected void logIn() {
  49. String requestBody = this.requestBody;
  50. if (this.inDataMasking) {
  51. requestBody = "Data Masking";
  52. }
  53. log.info(String.format("Start-[%s][%s][%s][body: %s]", this.ip, this.uri, this.methodName, requestBody));
  54. }
  55. protected void logOut(long elapsedMilliseconds, boolean success, String responseBody) {
  56. if (success) {
  57. if (this.outDataMasking) {
  58. responseBody = "Data Masking";
  59. }
  60. log.info(
  61. String.format(
  62. "Success(%s)-[%s][%s][%s][response body: %s]",
  63. elapsedMilliseconds,
  64. this.ip,
  65. this.uri,
  66. this.methodName,
  67. responseBody));
  68. } else {
  69. log.warn(
  70. String.format(
  71. "Failed(%s)-[%s][%s][%s][request body: %s][response body: %s]",
  72. elapsedMilliseconds,
  73. this.ip,
  74. this.uri,
  75. this.methodName,
  76. this.requestBody,
  77. responseBody));
  78. }
  79. }
  80. protected HttpServletRequest getHttpServletRequest(Object[] parameters) {
  81. try {
  82. if (null != parameters) {
  83. for (Object parameter : parameters) {
  84. if (parameter instanceof HttpServletRequest) {
  85. return (HttpServletRequest) parameter;
  86. }
  87. }
  88. }
  89. return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  90. } catch (Exception e) {
  91. log.error(e);
  92. return null;
  93. }
  94. }
  95. protected String formatParameters(Object[] parameters) {
  96. if (null == parameters) {
  97. return null;
  98. } else {
  99. StringBuilder stringBuilder = new StringBuilder();
  100. for (int i = 0; i < parameters.length; i++) {
  101. Object parameter = parameters[i];
  102. if (parameter instanceof HttpServletRequest || parameter instanceof HttpServletResponse) {
  103. continue;
  104. }
  105. stringBuilder.append(String.format("[%s]: %s.", i, JSON.toJSONString(parameter)));
  106. }
  107. return stringBuilder.toString();
  108. }
  109. }

2.2.2 实现类:

  1. public class DefaultControllerHandler extends AbstractControllerHandler {
  2. private static ILogger log = LoggerFactory.getLogger(DefaultControllerHandler.class);
  3. private static int currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1;
  4. public DefaultControllerHandler(ProceedingJoinPoint proceedingJoinPoint) {
  5. super(proceedingJoinPoint);
  6. }
  7. @Override
  8. public Object handle() throws Throwable {
  9. long timestamp = System.currentTimeMillis();
  10. this.logIn();
  11. ResponseDto responseDto;
  12. boolean success = false;
  13. try {
  14. Object result = proceedingJoinPoint.proceed();
  15. if (result instanceof ResponseDto) {
  16. responseDto = (ResponseDto) result;
  17. } else {
  18. responseDto = ResponseDto.success(result);
  19. }
  20. success = true;
  21. RuntimeHealthIndicator.successRequestCount++;
  22. } catch (BusinessException e) {
  23. // RuntimeHealthIndicator.failedRequestCount++;
  24. if (this.isDebugLogLevel()) {
  25. log.error(e);
  26. }
  27. responseDto = new ResponseDto<>(e.getCode(), e.getMessage(), null);
  28. } catch (Exception e) {
  29. RuntimeHealthIndicator.failedRequestCount++;
  30. if (this.isDebugLogLevel()) {
  31. log.error(e);
  32. }
  33. responseDto = ResponseDto.failed(ExceptionDefinitions.ServerError, e.getMessage(), null);
  34. } finally {
  35. Calendar cale = Calendar.getInstance();
  36. if (currentMonth != (cale.get(Calendar.MONTH) + 1)) {
  37. String recodeKey = String.format("%d年%d月",
  38. cale.get(Calendar.YEAR), cale.get(Calendar.MONTH) + 1);
  39. String recodeValue = "successCount:" + RuntimeHealthIndicator.successRequestCount +
  40. " failedCount:" + RuntimeHealthIndicator.failedRequestCount;
  41. RuntimeHealthIndicator.historyRequestRecode.put(recodeKey, recodeValue);
  42. RuntimeHealthIndicator.successRequestCount = 0;
  43. RuntimeHealthIndicator.failedRequestCount = 0;
  44. currentMonth = cale.get(Calendar.MONTH);
  45. }
  46. }
  47. long duration = System.currentTimeMillis() - timestamp;
  48. RuntimeHealthIndicator.markRestApiInvoked(this.methodName, (int) duration);
  49. this.logOut(duration, success, JSON.toJSONString(responseDto));
  50. return responseDto;
  51. }
  52. public boolean isDebugLogLevel() {
  53. return log.isEnabled(LogLevel.DEBUG);
  54. }
  55. }

2.3 Health接口

  1. @Component
  2. public class RuntimeHealthIndicator extends AbstractHealthIndicator {
  3. private static ILogger log = LoggerFactory.getLogger(ApplicationInstanceManager.class);
  4. private static Map<String, RestApiInvokeStatus> restApiInvokeStatuses = new HashMap<>();
  5. public static long failedRequestCount = 0;
  6. public static long successRequestCount = 0;
  7. public static Map<String, Object> historyRequestRecode;
  8. private Map<String, Object> details;
  9. public RuntimeHealthIndicator() {
  10. this.details = new HashMap<>();
  11. RuntimeHealthIndicator.historyRequestRecode = new HashMap<>();
  12. this.details.put("startTime", new Date(ManagementFactory.getRuntimeMXBean().getStartTime()));
  13. this.details.put("path", RuntimeHealthIndicator.class.getClassLoader().getResource("").getPath());
  14. this.details.put("osName", System.getProperty("os.name"));
  15. this.details.put("osVersion", System.getProperty("os.version"));
  16. this.details.put("javaVersion", System.getProperty("java.version"));
  17. try {
  18. this.details.put("ip", ZGHelper.getIpV4());
  19. } catch (SocketException e) {
  20. log.error(e, "Failed to get Ipv4.");
  21. }
  22. }
  23. @Override
  24. protected void doHealthCheck(Health.Builder builder) throws Exception {
  25. ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
  26. while (null != threadGroup.getParent()) {
  27. threadGroup = threadGroup.getParent();
  28. }
  29. this.details.put("threadCount", threadGroup.activeCount());
  30. OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
  31. this.details.put("cpuUsageRate", operatingSystemMXBean.getSystemCpuLoad());
  32. this.details.put(
  33. "memoryUsageRate",
  34. (float) (operatingSystemMXBean.getTotalPhysicalMemorySize() - operatingSystemMXBean.getFreePhysicalMemorySize()) / (float) operatingSystemMXBean.getTotalPhysicalMemorySize());
  35. this.details.put("failedRequestCount", RuntimeHealthIndicator.failedRequestCount);
  36. this.details.put("successRequestCount", RuntimeHealthIndicator.successRequestCount);
  37. this.details.put("restApiInvokeStatuses", RuntimeHealthIndicator.restApiInvokeStatuses);
  38. this.details.put("historyRequestRecode",RuntimeHealthIndicator.historyRequestRecode);
  39. for (Map.Entry<String, Object> detail : this.details.entrySet()) {
  40. builder.withDetail(detail.getKey(), detail.getValue());
  41. }
  42. builder.up();
  43. }
  44. public static void markRestApiInvoked(String name, int duration) {
  45. if (StringUtils.isBlank(name)) {
  46. return;
  47. }
  48. if (!RuntimeHealthIndicator.restApiInvokeStatuses.containsKey(name)) {
  49. RuntimeHealthIndicator.restApiInvokeStatuses.put(name, new RestApiInvokeStatus(name));
  50. }
  51. RestApiInvokeStatus restApiInvokeStatus = RuntimeHealthIndicator.restApiInvokeStatuses.get(name);
  52. restApiInvokeStatus.setDuration(duration);
  53. }
  54. }

2.4 工具类

  1. public class RestApiInvokeStatus {
  2. private String name;
  3. private Date startDate;
  4. private Date latestDate;
  5. private long times;
  6. private float averageDuration;
  7. private int minDuration;
  8. private int maxDuration;
  9. private int[] durations;
  10. public String getName() {
  11. return name;
  12. }
  13. public Date getStartDate() {
  14. return startDate;
  15. }
  16. public Date getLatestDate() {
  17. return latestDate;
  18. }
  19. public long getTimes() {
  20. return times;
  21. }
  22. public int getMinDuration() {
  23. return minDuration;
  24. }
  25. public int getMaxDuration() {
  26. return maxDuration;
  27. }
  28. public RestApiInvokeStatus(String name) {
  29. Assert.isNotBlank(name, "name");
  30. this.name = name;
  31. this.durations = new int[1000];
  32. this.minDuration = Integer.MAX_VALUE;
  33. this.maxDuration = Integer.MIN_VALUE;
  34. Date now = new Date();
  35. this.startDate = now;
  36. this.latestDate = now;
  37. }
  38. public void setDuration(int duration) {
  39. this.durations[(int) (this.times % this.durations.length)] = duration;
  40. this.maxDuration = this.maxDuration > duration ? this.maxDuration : duration;
  41. this.minDuration = this.minDuration < duration ? this.minDuration : duration;
  42. this.latestDate = new Date();
  43. this.times++;
  44. }
  45. public float getAverageDuration() {
  46. long length = this.times < this.durations.length ? this.times : this.durations.length;
  47. int count = 0;
  48. for (int i = 0; i < length; i++) {
  49. count += this.durations[i];
  50. }
  51. this.averageDuration = (float) count / (float) length;
  52. return this.averageDuration;
  53. }
  54. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/372317
推荐阅读
相关标签
  

闽ICP备14008679号