如何记录应用的接口访问信息(调用次数,最长时间、最短时间、平均时间等等)

来源:互联网 发布:7号淘宝店外设网站 编辑:程序博客网 时间:2024/06/05 15:44

背景:

有时候为了对系统进行优化,我们需要找出系统中访问时间长的那些方法,本文就来演示下,如何实现这个功能。最终实现的效果是访问一个url,列出当前系统中所有api接口的访问信息,包括:接口的调用次数、正常调用次数、异常调用次数、接口的平均访问时间、最大访问时间、最小访问时间,如图所示:


思路:

(1)定义一个拦截器,记录方法的开始时间和结束时间

(2)定义一个全局的异常处理器,记录防范访问发生异常

(3)定义ThreadLocal,保存方法的访问信息。

(4)为了访问多线程并发访问影响记录的准确性,用队列把计算串行化。

(5)定义结果输出界面

下面基于SpringBoot来实现一下。

(1)定义拦截器

@Servicepublic class AccessInterceptor implements HandlerInterceptor  {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if(handler instanceof HandlerMethod){HandlerMethod hm = (HandlerMethod)handler;AccessInfo mi = new AccessInfo();mi.setHm(hm);mi.setStartAt(System.currentTimeMillis());mi.setUri(request.getRequestURI());AccessHolder.accessStart(mi);//记录方法开始}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView model)throws Exception {//可以向view中写入数据}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)throws Exception {if(handler instanceof HandlerMethod){AccessHolder.accessEnd();//记录方法结束}}}

(2)定义异常处理器

@ControllerAdvice@ResponseBodypublic class GlobalExceptionHandler {    @ExceptionHandler(value=Exception.class)      public Result<String> allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception{          AccessHolder.accessError();//记录方法异常        if(exception instanceof BizException){        exception.printStackTrace();        BizException biz = (BizException)exception;        return Result.error(biz.getCodeMsg());        }else {        exception.printStackTrace();        return Result.error(CodeMsg.SERVER_ERROR);        }    }  }

(3)定义ThreadLocal

@Servicepublic class AccessHolder {private static ThreadLocal<AccessInfo> accessHolder = new ThreadLocal<AccessInfo>();//记录单次访问信息private static ConcurrentHashMap<Class<?>, ControllerAccessInfo> map = new ConcurrentHashMap<Class<?>, ControllerAccessInfo>();//记录所有的访问信息private static WorkingService<AccessRequest> workingService = new WorkingService<AccessRequest>();//工作队列,串行化static {workingService.start();}public static void accessStart(AccessInfo mai) {accessHolder.set(mai);}public static AccessInfo getAccessInfo() {return accessHolder.get();}public static void accessError() {AccessInfo ai = getAccessInfo() ;if(ai == null) {return;}ai.setOccurError(true);}public static void accessEnd() {AccessInfo ai = getAccessInfo();if(ai == null) {return;}ai.setEndAt(System.currentTimeMillis());accessHolder.set(null);workingService.execute(new AccessRequest(ai), new LazyExecutable<AccessRequest>() {@Overridepublic void lazyExecute(AccessRequest request) {addAccessInfo(request.getAi());}});}private static void addAccessInfo(AccessInfo ai) {HandlerMethod hm = ai.getHm();Class<?> controllerClazz = hm.getBeanType();if(!AccessAble.class.isAssignableFrom(controllerClazz)) {return;}Method method = hm.getMethod();long startAt = ai.getStartAt();long endAt = ai.getEndAt();boolean occurError = ai.isOccurError();long useTime = endAt - startAt;String uri = ai.getUri();ControllerAccessInfo cai = map.get(controllerClazz);if(cai == null) {cai = new ControllerAccessInfo();cai.setControllerClazz(controllerClazz);map.put(controllerClazz, cai);}List<MethodAccessInfo> mais = cai.getMethodInfos();if(mais == null) {mais = new ArrayList<MethodAccessInfo>();cai.setMethodInfos(mais);}MethodAccessInfo mai = getMethodAccessInfo(mais, method);if(mai == null) {mai = new MethodAccessInfo();mai.setMethod(method.getName());mai.setUri(uri);mai.setInvokeCount(1);if(occurError) {mai.setErrorCount(1);}else {mai.setSuccessCount(1);}mai.setMinMillSecond(useTime);mai.setMaxMillSecond(useTime);mai.setAvgMillSecond(useTime);mais.add(mai);}else {if(useTime < mai.getMinMillSecond()) {mai.setMinMillSecond(useTime);}if(useTime > mai.getMaxMillSecond()) {mai.setMaxMillSecond(useTime);}mai.setInvokeCount(mai.getInvokeCount() + 1);if(occurError) {mai.setErrorCount(mai.getErrorCount()+1);}else {mai.setSuccessCount(mai.getSuccessCount()+1);}mai.setAvgMillSecond((mai.getAvgMillSecond()+useTime)/2);}}private static MethodAccessInfo getMethodAccessInfo(List<MethodAccessInfo> mais, Method method) {for(MethodAccessInfo mai : mais) {if(method.getName().equals(mai.getMethod())) {return mai;}}return null;}public static Map<String, Object> getAllAccessInfo() {Map<String, Object> result = new HashMap<String, Object>();for(Map.Entry<Class<?>, ControllerAccessInfo> entry : map.entrySet()) {ControllerAccessInfo cai = entry.getValue();result.put(cai.getControllerClazz().getSimpleName(), cai.getMethodInfos());}return result;}}

(4)利用SpringBoot的EndPoint把结果输出出去

public class AccessEndPoint implements Endpoint<Map<String, Object>> {    public String getId() {        return "access";    }        public boolean isEnabled() {        return true;    }        public boolean isSensitive() {        return false;    }        public Map<String, Object> invoke() {    return AccessHolder.getAllAccessInfo();    }}
@Configurationpublic class EndPointAutoConfig {    @Bean    public AccessEndPoint myEndPoint() {        return new AccessEndPoint();    }}
完整的代码见:https://github.com/xjs1919/access/


阅读全文
0 0
原创粉丝点击