背景
作为一名有良心的开发选手,既然要做产品,咱们就要把产品做好,让用户有更好的用户体验,好的用户体验体现在哪里呢?
1.精简的业务逻辑
2.表现层(视觉,界面,导航设计):良好的页面展示
3.强力的后台处理能力
4.最大程度解决用户的问题
5.有求必应的运维体系
今天就从第三点“强力的后台处理能力”入手,强力的后台处理能力体现在哪里?无非就是,前端请求后台接口的处理速度,这时候有些老铁可能想起来,F12里面的接口耗时,但是这只是一部分接口的时间,我们怎样来统计整个项目的接口耗时呢?你可能想到:
1.在请求入口做dao操作
2.Aop
今天就用这两种方式来统计整个项目的接口耗时!
在请求入口做dao操作
这个方案我相信大多数开发人员都能想到,话不多说,开干!
我们项目使用的是vert.x框架,有别于现在市面上特别流行的springMVC,但是原理都差不多,只要找到请求的入口就事半功倍了,vert.x框架把前端传过来的接口类,方法,参数,通过反射调用目标方法,我们只要在反射的前后统计一下调用方法的耗时即可!
ExecutorService service= Executors.newFixedThreadPool(3);
long start = System.currentTimeMillis();
//反射调用的方法 这里省略掉了
long costTime = System.currentTimeMillis() - start;
//httpMethod:接口请求方式:post GET
//ip:客户端ip
//url:请求的接口类
//classMethod:请求的接口方法
//params:请求的接口的参数
//costTime:请求的接口耗时
if(costTime >1000){
//异步统计接口耗时信息
service.execute(()->this.insertCostTimeRecord(httpMethod,ip,url,classMethod,params,costTime));
}
其中我是用了使用线程池异步做dao操作,减少统计耗时操作对原有逻辑性能上的影响。costTime >1000,只统计耗时大于1秒的接口,我们的目的是优化,二八原则,你懂得!
接下来,看结果吧,如图:
跟我预先想的一致呀,以后只用看这张表的统计信息就知道是哪个接口慢了,不会再让你为了优化无从下手!
AOP
AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
Aop的好处:
-
集中处理某一关注点 / 横切逻辑
-
可以很方便的添加 / 删除关注点
-
侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码 0 侵入
切面的使用【基于注解】
-
@Aspect => 声明该类为一个注解类
切点注解:
-
@Pointcut => 定义一个切点,可以简化代码
通知注解:
-
@Before => 在切点之前执行代码
-
@After => 在切点之后执行代码
-
@AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装
-
@AfterThrowing => 切点抛出异常后执行
-
@Around => 环绕,在切点前后执行代码
动手写一个请求日志切面
-
使用 @Pointcut 定义切点
@Pointcut("execution(* your_package.controller..*(..))")
public void requestServer() {
}
@Pointcut 定义了一个切点,因为是请求日志切边,因此切点定义的是 Controller 包下的所有类下的方法。定义切点以后在通知注解中直接使用 requestServer 方法名就可以了
-
完整切面代码
@Component
@Aspect
public class RequestLogAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);
ExecutorService service= Executors.newFixedThreadPool(3);
@Pointcut("execution(* your_package.controller..*(..))")
public void requestServer() {
}
@Around("requestServer()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.currentTimeMillis();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Object result = proceedingJoinPoint.proceed();
RequestInfo requestInfo = new RequestInfo();
requestInfo.setIp(request.getRemoteAddr());
requestInfo.setUrl(request.getRequestURL().toString());
requestInfo.setHttpMethod(request.getMethod());
requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
proceedingJoinPoint.getSignature().getName()));
requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
requestInfo.setResult(result);
requestInfo.setTimeCost(System.currentTimeMillis() - start);
LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo));
//异步统计接口耗时信息
service.execute(()->this.insertCostTimeRecord(requestInfo));
return result;
}
/**
* 获取入参
* @param proceedingJoinPoint
*
* @return
* */
private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
Map<String, Object> requestParams = new HashMap<>();
//参数名
String[] paramNames =
((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
//参数值
Object[] paramValues = proceedingJoinPoint.getArgs();
for (int i = 0; i < paramNames.length; i++) {
Object value = paramValues[i];
//如果是文件对象
if (value instanceof MultipartFile) {
MultipartFile file = (MultipartFile) value;
value = file.getOriginalFilename(); //获取文件名
}
requestParams.put(paramNames[i], value);
}
return requestParams;
}
}
就此,使用Aop统计接口耗时开发完成,结果与第一种方式的结果一致。
有了这个功能,你还会为了不知道优化哪个接口而头疼吗?来吧,老铁,给你的项目加上吧!