使用注解记录方法执行耗时

介绍

各个系统都在追求性能,要做压力测试。

压力测试目的是要找到热点,性能瓶颈,然后解决它或者优化它,这边暂不讨论怎么解决和优化性能问题。如果不做大规模的性能测试,是否有其他方式在开发,单元测试和集成测试的时候就发现一些性能问题呢?

我个人比较喜欢在编码的各个阶段都去注意性能问题,并不希望在把问题留到大规模性能测试之后再发现。那样费时费力,在解决性能瓶颈和发现下一个性能瓶颈的过程中来来回回,压力测试的同学在叹气,开发的同学在抓头。

不说废话了。进入正题。假设我们能够通过DB查询出系统中各个方法的执行时间,那运用各种group byorder by。就能轻松知道当前系统的状态,有可能产生的瓶颈。不仅如此,还能清楚知道每一笔交易,对各个方法的执行次数。是否有大量不必要的循环等等。参考下表。

CREATE TABLE
    service_method_record
    (
        id bigint NOT NULL AUTO_INCREMENT,
        access_jnl CHAR(32), 					--流水号
        start_time TIMESTAMP NULL,		--开始时间
        end_time TIMESTAMP NULL,			--结束时间
        method_name VARCHAR(100),			--方法名称
        use_time INT,									--用时多久,毫秒
        env_name VARCHAR(20),					--环境名字,DEV,UAT,版本机等
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

系统中方法太多,还有各种工具方法,根本没必要统计。如果在某个方法上给个注解@LogMethodTime,这样就表示要统计这个方法的调用时长。岂不是美滋滋。

使用Aspect做面向切面,拦截方法并且在方法入口和出口做时间统计操作。

看下示意图:

记录方式

1.可以记录到日志中,这样性能消耗比较小

2.可以记录到DB中,但是性能消耗比较大,建议使用消息队列做异步记录。

记录到日志中

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogMethodTime {
}
复制代码
@Aspect
public class LogMethodTimeAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogMethodTimeAspect.class);

    @Value("${common.logSwitch}")
    private boolean logSwitch;// 开关,true:打开,false:关闭

    @Pointcut("@annotation(LogMethodTime)")
    public void pointcutName() {
    }

    @Around("pointcutName()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        if (!logSwitch) {
            return pjp.proceed();
        }

        // 获取方法名(类全路径+.+方法名)
        String classFullName = pjp.getSignature().getDeclaringTypeName();
        String className = classFullName.substring(classFullName.lastIndexOf(".") + 1);
        // 比如:com.xxx.XxxService.xxxMethod
        String name = className + "." + pjp.getSignature().getName();

        long start = System.currentTimeMillis();
        // 记录开始时间
        LOGGER.debug("-------------------start method:" + name + "-------------------");
        Object result = pjp.proceed();
        // 记录结束时间和用时
        LOGGER.debug("-------------------end method:" + name + ", use time " + (System.currentTimeMillis() - start) + "-------------------");
        return result;
    }
}
复制代码

两个东西

① 注解定义

② Aspect的实现(对Aspect不熟悉可以参考相关文档,这边不做详细赘述)

AspectSpring中的配置就不细说了。

####记录到DB中

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogMethodTime2DB {
}
复制代码
@Aspect
public class LogMethodTime2DBAspect {

    @Autowired
    private ServiceMethodRecordService serviceMethodRecordService;

    @Value("${common.logSwitch}")
    private boolean logSwitch;// 开关,true:打开,false:关闭

    @Pointcut("@annotation(LogMethodTime2DB)")
    public void pointcutName() {
    }

    @Around("pointcutName()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        if (!logSwitch) {
            return pjp.proceed();
        }

        // 获取方法名(类全路径+.+方法名)
        String classFullName = pjp.getSignature().getDeclaringTypeName();
        String className = classFullName.substring(classFullName.lastIndexOf(".") + 1);
        // 比如:com.xxx.XxxService.xxxMethod
        String name = className + "." + pjp.getSignature().getName();

        // 这边用了消息队服务去做初始化
        JSONObject recordObj = serviceMethodRecordService.init(name, System.currentTimeMillis());
        Object result = pjp.proceed();
        // 这边用了消息队服务去做update
        serviceMethodRecordService.update(recordObj.getString("id"), recordObj.getLongValue("startTime"), System.currentTimeMillis());
        return result;
    }
}
复制代码

上面这个记录DB的案例中,我用消息队列服务。先做初始化,调用完实际invoke方法后,在发一条update的消息。

注意:这前后两条消息务必是顺序的。保证消息有序的方式属于各个消息队列的事情,比如kafka,只要保证两条有前后顺序的消息在分配到同一个partition中就是有序的。

使用

把上述的一些类,注解,各种都配置好后,使用起来就舒服了。在想要监控的方法体上加上注解即可。

@LogMethodTime
public void method() {
	// do something
}
复制代码
@LogMethodTime2DB
public void method() {
	// do something
}
复制代码

如果某天想把所有的这些注解都去掉。那就统一搜索下,统一删除下。几分钟就搞定了。但我觉得没必要,既然加了开关了,删除它干嘛?把开关设为false即可。

总结

这样的小技巧,能在开发,单元测试,集成测试的过程中就发现很多问题,没有必要留到后面性能测试。实现中务必加上配置开关,默认可以是false,这东西最好不要在生产跑。

猜你喜欢

转载自juejin.im/post/5eaeb4b7f265da7bc60e00bf