通过AOP技术统计应用接口耗时情况
通过AOP技术统计应用接口耗时情况
需求:统计项目每个接口调用记录处理耗时(毫秒),并按分钟为单位,记录请求次数、失败次数、累计处理耗时、最大处理耗时
1、自定义注解
通过注解控制哪个接口的请求耗时信息需要被统计
package com.povison.common.annotation;
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;
/**
* 统计请求耗时
*
* @author Y-wee
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestTime {
}
2、定义VO
package com.povison.common.domain.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* 请求监控
*
* @author Y-wee
*/
@Getter
@Setter
@ToString
public class RequestMonitoringVo {
/**
* 请求时间
*/
private String time;
/**
* 接口请求的 URL
*/
private String requestURL;
/**
* 接口请求成功的次数
*/
private Long successNum;
/**
* 接口请求失败的次数
*/
private Long failNum;
/**
* 接口请求成功的累计耗时
*/
private Long successTime;
/**
* 接口请求失败的累计耗时
*/
private Long failTime;
/**
* 接口请求成功最大耗时
*/
private Long SuccessMaxTime;
}
3、定义计数器实现耗时信息统计
将接口耗时信息存储在缓存中并设置过期时间,可以根据需要持久化存储
package com.povison.common.util;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.povison.common.domain.vo.RequestMonitoringVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 记录接口访问成功/失败的计数及耗时
*
* @author Y-wee
*/
@Slf4j
public class RequestCounter {
/**
* Guava Cache 缓存接口访问记录
*/
private static Cache<String, ConcurrentHashMap<RequestCounter, RequestMonitoringVo>> cache = CacheBuilder.newBuilder().expireAfterWrite(60 * 15, TimeUnit.SECONDS).build();
/**
* 获取接口计数统计 Map
*
* @param key
* @return
*/
public static ConcurrentHashMap<RequestCounter, RequestMonitoringVo> getMapCounter(String key) {
ConcurrentHashMap<RequestCounter, RequestMonitoringVo> map = cache.getIfPresent(key);
if (CollectionUtils.isEmpty(map)) {
map = new ConcurrentHashMap<>(16);
RequestCounter counter = new RequestCounter();
RequestMonitoringVo monitoringVo = new RequestMonitoringVo();
String[] split = key.split("--");
monitoringVo.setTime(split[0]);
monitoringVo.setRequestURL(split[1]);
monitoringVo.setSuccessNum(0L);
monitoringVo.setFailNum(0L);
monitoringVo.setSuccessTime(0L);
monitoringVo.setSuccessMaxTime(0L);
monitoringVo.setFailTime(0L);
map.put(counter, monitoringVo);
cache.put(key, map);
}
return map;
}
/**
* 增加访问接口调用成功的次数
*
* @param concurrentHashMap
* @return
*/
public static void increaseSucceed(ConcurrentHashMap<RequestCounter, RequestMonitoringVo> concurrentHashMap) {
RequestMonitoringVo monitoringVo = null;
for (RequestMonitoringVo m : concurrentHashMap.values()) {
monitoringVo = m;
}
Lock lock = new ReentrantLock();
lock.lock();
try {
monitoringVo.setSuccessNum(monitoringVo.getSuccessNum() + 1);
} catch (Exception e) {
log.error("---increaseSucceed,exception: ", e);
} finally {
lock.unlock();
}
}
/**
* 更新调用成功的耗时及调用成功最大耗时
*
* @param key
* @param successTime
*/
public static void setSuccessAndSuccessMaxTime(String key, Long successTime) {
ConcurrentHashMap<RequestCounter, RequestMonitoringVo> map = getMapCounter(key);
RequestMonitoringVo monitoringVo = null;
for (RequestMonitoringVo m : map.values()) {
monitoringVo = m;
}
Lock lock = new ReentrantLock();
lock.lock();
try {
Long successMaxTime = monitoringVo.getSuccessMaxTime();
successMaxTime = successMaxTime > successTime ? successMaxTime : successTime;
monitoringVo.setSuccessMaxTime(successMaxTime);
monitoringVo.setSuccessTime(monitoringVo.getSuccessTime() + successTime);
} catch (Exception e) {
log.error("---setSuccessfulTime,exception: ", e);
} finally {
lock.unlock();
}
}
/**
* 增加访问接口调用失败的次数
*
* @param concurrentHashMap
*/
public static void increaseFail(ConcurrentHashMap<RequestCounter, RequestMonitoringVo> concurrentHashMap) {
RequestMonitoringVo monitoringVo = null;
for (RequestMonitoringVo m : concurrentHashMap.values()) {
monitoringVo = m;
}
Lock lock = new ReentrantLock();
lock.lock();
try {
monitoringVo.setFailNum(monitoringVo.getFailNum() + 1);
} catch (Exception e) {
log.error("---increaseFail,exception: ", e);
} finally {
lock.unlock();
}
}
/**
* 更新调用失败的耗时
*
* @param key
* @param failTime
*/
public static void setFailTime(String key, Long failTime) {
ConcurrentHashMap<RequestCounter, RequestMonitoringVo> map = getMapCounter(key);
RequestMonitoringVo monitoringVo = null;
for (RequestMonitoringVo m : map.values()) {
monitoringVo = m;
}
Lock lock = new ReentrantLock();
lock.lock();
try {
monitoringVo.setFailTime(monitoringVo.getFailTime() + failTime);
} catch (Exception e) {
log.error("---increaseFail,exception: ", e);
} finally {
lock.unlock();
}
}
/**
* 获取时间戳,并以5分钟为节点
*
* @return
*/
public static String getTimeStamp() {
Long millis = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
SimpleDateFormat sdfMinute = new SimpleDateFormat("mm");
// 时间戳转换成时间字符串
String date = sdf.format(new Date(Long.parseLong(String.valueOf(millis))));
// 时间戳转换成时间字符串
String minute = sdfMinute.format(new Date(Long.parseLong(String.valueOf(millis))));
date += ":" + (Integer.valueOf(minute) - Integer.valueOf(minute) % 5);
return date;
}
/**
* 获取请求的接口 URL
*
* @param request
* @return
*/
public static String getRequestUrl(HttpServletRequest request) {
return request.getRequestURI();
}
/**
* 获取接口监控信息
*
* @return
*/
public static CopyOnWriteArrayList<RequestMonitoringVo> getCacheMap() {
CopyOnWriteArrayList<RequestMonitoringVo> list = new CopyOnWriteArrayList<>();
ConcurrentMap<String, ConcurrentHashMap<RequestCounter, RequestMonitoringVo>> map = cache.asMap();
for (ConcurrentHashMap<RequestCounter, RequestMonitoringVo> value : map.values()) {
for (RequestMonitoringVo monitoringVo : value.values()) {
list.add(monitoringVo);
}
}
return list;
}
}
4、定义切面进行耗时统计
package com.povison.common.aspect;
import com.povison.common.util.RequestCounter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ExecutionException;
/**
* 统计请求耗时切面
*
* @author Y-wee
*/
@Component
@Aspect
@Slf4j
public class RequestTimeAspect {
private static ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 切入点: 通过自定义注解标记需要监控的接口
*/
@Pointcut("@annotation(com.povison.common.annotation.RequestTime)")
public void annotationPointCut() {
}
/**
* 默认切入点: com.povison.srm.controller 包下所有接口
*/
@Pointcut("execution(* com.povison.srm.controller.*.*(..))")
public void defaultPointCut() {
}
/**
* 前置通知
*
* @param joinPoint
* @throws Throwable
*/
@Before("annotationPointCut()||defaultPointCut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
// 获取目标方法类名方法名
log.info("---doBefore,请求方法: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
}
/**
* 后置通知
*
* @param joinPoint
* @param returnVal
* @throws ExecutionException
*/
@Async
@AfterReturning(returning = "returnVal", pointcut = "annotationPointCut()||defaultPointCut()")
public void doAfterReturning(JoinPoint joinPoint, Object returnVal) throws ExecutionException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 接口统计信息容器的 key
String key = RequestCounter.getTimeStamp() + "--" + RequestCounter.getRequestUrl(request);
RequestCounter.increaseSucceed(RequestCounter.getMapCounter(key));
// 耗时计算
Long succeedTime = System.currentTimeMillis() - startTime.get();
log.info("---doAfterReturning,访问成功,URI: {},耗费时间: {} ms", request.getRequestURI(), succeedTime);
RequestCounter.setSuccessAndSuccessMaxTime(key, succeedTime);
startTime.remove();
}
/**
* 异常通知
*
* @param joinPoint
* @throws ExecutionException
*/
@AfterThrowing(pointcut = "annotationPointCut()||defaultPointCut()")
public void doAfterThrowing(JoinPoint joinPoint) throws ExecutionException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 接口统计信息容器的 key
String key = RequestCounter.getTimeStamp() + "--" + RequestCounter.getRequestUrl(request);
RequestCounter.increaseFail(RequestCounter.getMapCounter(key));
// 耗时计算
Long failTime = System.currentTimeMillis() - startTime.get();
log.info("访问失败,URI: {}, 耗费时间: {} ms", request.getRequestURI(), failTime);
RequestCounter.setFailTime(key, failTime);
startTime.remove();
}
}
5、定义接口获取统计的耗时信息
package com.povison.common.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.povison.common.domain.vo.RequestMonitoringVo;
import com.povison.common.util.RequestCounter;
import org.jeecg.common.api.vo.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 请求监控
*
* @author Y-wee
*/
@RestController
@RequestMapping("/monitor")
public class RequestMonitorController {
/**
* 监控列表
*
* @return
*/
@GetMapping("/page")
public Result<Page<RequestMonitoringVo>> get() {
CopyOnWriteArrayList<RequestMonitoringVo> list = RequestCounter.getCacheMap();
Page<RequestMonitoringVo> page = new Page<>();
page.setRecords(list);
page.setTotal(list.size());
return Result.OK(page);
}
}
记得快乐