背景
最近公司的社区相关的服务需要优化,由于对业务不熟悉,只能借助监控从一些慢接口开始尝试探索慢的原因。由于社区相关的功能务是公司小程序流量入口,所以相应的服务访问量还是比较高的。针对这类高访问的项目,任何不留神的地方都可能会引起连锁反应导致瓶颈,本次是针对此次排查提供一些我探索的方法。
慢原因
由于我们生产环境接入了阿里云的ARMS监控,所以排查效率会特别高。先列举一些比较常见的慢原因:
- 下游接口调用慢
- 缓存获取慢
- 慢SQL
我觉得更多的是前期的表设计存在很大的问题,导致太多的表关联查询效率很差。
- 获取连接数慢
连接慢的排查之路
我们使用的是阿里的druid的连接池,所以一开始并没有觉得有什么大问题。
从ARMS中找了一个比较有代表性的图:
一共耗时12秒,一开始只觉得应该和并发请求有关,因为只有比较少量的请求会出现该情况。
这是一个查询接口,而且承载了该服务大量的请求。
通过分析链路发现了两个比较明显的问题:
- 下游的接口慢
- SQL比较耗时
经过一番优化之后,效果显著,但是还是存在几秒获取连接的情况。
这是啥情况?仔细盯着链路图,发呆了几秒,不对呀!查询接口咋还出现了commit提交事务的命令呢?
由于这个接口查询本地表的SQL并不慢,而且并没有修改数据的动作存在,转而又去看了相应的代码。方法上面也没有加上@Transaction
呀?咋就把事务开了呢!思来想去,泥马不会在类上加了吧! 一看果然!!!
@Service
@Transactional(rollbackFor = Exception.class)
public class XxxxService extends AbstractUserService {
}
怕了怕了,这种写法会导致接口内部的方法可能都会默认开启事务,一旦事务内部有出现下游调用或者慢操作,就会出现长事务。
此时感觉自己深陷沼泽。。。赶紧写了个工具扫描所有service类上加了@Transaction注解的功能。。逐一修复!!!
第二天瞄了一眼,嗯,还是有提升,但是还是有几百毫秒的获取连接情况!
天哪~
又不情不愿的盯着ARMS的各种功能看【不得不说ARMS还是挺全面的】
终于在线程监控中发现端倪:
看到这个
at com.alibaba.druid.pool.DruidAbstractDataSource.testConnectionInternal (DruidAbstractDataSource.java:1407)
心里顿时感觉应该和配置有关,通过看源码发现是到了testOnBorrow满足的逻辑,然后去看了服务相关的配置:
spring.datasource.druid.testOnBorrow=true
嗯嗯嗯,去官网上看了相关的解释;
配置 | 默认值 | 说明 |
---|---|---|
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 |
timeBetweenEvictionRunsMillis | 1分钟(1.0.14) | 有两个含义:1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
- druid github 配置参考
- druid github 通用配置
想想也是,如果每次获取连接都去校验是否有效,这不纯纯浪费么。通常一般99%的校验都是浪费,倒不如指定的超时时间去校验一次,当然各有取舍,极端情况可能会出现连接时效的问题,但影响应该也不大。
另外再补充一个阿里云的jedis的最佳实践吧
还有一个扫描@Transaction注解的方法:
import cn.hutool.core.lang.ClassScanner;
import cn.hutool.core.lang.Filter;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class TransactionPonintCase {
public static void main(String[] args) {
List<Class> clazzList= new ArrayList<>();
List<String> methodList= new ArrayList<>();
Set<Class<?>> classes = ClassScanner.scanAllPackage("你要扫描的包", new Filter<Class<?>>() {
@Override
public boolean accept(Class<?> aClass) {
boolean annotationPresent = aClass.isAnnotationPresent(Transactional.class);
if(annotationPresent){
clazzList.add(aClass);
return true;
}
for (Method declaredMethod : aClass.getDeclaredMethods()) {
if(declaredMethod.isAnnotationPresent(Transactional.class)){
methodList.add(declaredMethod.getDeclaringClass().getName()+"."+declaredMethod.getName());
}
}
return annotationPresent;
}
});
System.out.println(">>>>>>>>>>>>>>>>> 类级别注解 <<<<<<<<<<<<<<<<<<<<<");
for (Class<?> aClass : classes) {
System.out.println(aClass.getName());
}
System.out.println(">>>>>>>>>>>>>>>>> 方法級別注解 <<<<<<<<<<<<<<<<<<<<<");
for (String name : methodList) {
System.out.println(name);
}
}
}