• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
Y-wee
博客园    首页    新随笔    联系   管理     

通过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);
    }

}
记得快乐
posted @ 2023-06-02 16:08  Y-wee  阅读(469)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3