20230407 记一次接口速度调优过程
1、问题场景:
有一个历史综合信息查询接口,响应速度很慢,已经到了快10s才会响应,导致前端界面加载过慢,必须得优化。
2、问题排查与解决:
列表查询的业务比较明确,即查询一个主体的不同的附属信息,需要查询mongo、mysql以及rpc远程接口调用,才能获取到全部信息;
分析业务代码后,发现在查询到基础数据后,会进行for循环调用查询mongo、mysql、以及调用远程rpc接口,这都有可能降低响应速度;
排查后大概以下业务可能会导致响应慢:
1、循环调用rpc接口查询了主体日志信息,且该rpc接口中查询了mysql,然后累加获取到mysql数据;
2、循环查询mongo,且使用了类似sql查询,limit1的操作
针对以上问题进行优化:
1、rpc接口改为一次查询多条数据,并在获取结果时使用sql对数据进行聚合统计;此处修改后,响应速度平均在4s左右
降了一大半;
2、循环查询mongo,且使用了类似sql查询,limit1的操作,此处改为使用mongo聚合查询,类似sql中的group by+sum
此处修改后,速度优化了300ms左右,接口响应速度平均大概在3.7s左右
目前根据业务分析不出来哪里还可能需要优化了,开始上科技:arthas
```trace -E class_path method|method2|.... ```
1、先直接用arthas的trace命令追了接口,发现追踪到的一些方法耗时都很短,基本就是几毫秒,不会影响速度;
那么就是有方法没有追到;
2、仔细排查业务代码,发现如果类的set方法中的入参是一个方法,那么这个方法就不会被追踪到,这些比较隐蔽;
花了点时间一个个都找了出来,然后再用trace进行追踪,慢的方法一目了然。
3、也是在for循环的时候进行查询了mongo,查出每一个主体信息在该mongo中的全部数据,然后根据查出来的数据进行分类统计;
这样如果某一个主体在mongo中有大量数据时,就会非常耗时,数据量小的主体,响应速度就很快;
3、问题总结
开发业务时,不要先急着动手,要想清楚大概业务逻辑,可以适当写一些todo list,将业务列出来
这样可以明确哪些数据可能需要批量查询,哪些业务的数据需要聚合查询,哪些业务的统计数据可以提前做,而不是实时查实时统计等
要尽量避免以下操作:
1、循环调用rpc接口;
2、循环查询任何数据库;
3、对大量历史数据进行查询并实时统计。
20230329 异步注解导致RequestContextHolder.getRequestAttributes()获取请求信息空指针异常
1、问题场景:
正常业务中使用RequestContextHolder.getRequestAttributes()时可以获取到请求头中的一些用户信息, 在一次接口优化时,需要优化响应时间,目前网关设置响应时间默认为2分钟,但涉及要处理的数据量大时, 2分钟不够了,且直接返回了超时异常(实际业务正在处理中),导致用户体验很差, 于是将部分耗时业务使用异步的方式去处理(该业务为实际处理数据),先返回结果,提示处理中; 但是异步线程中使用了RequestContextHolder.getRequestAttributes()获取时无法获取用户信息,就抛出了空指针异常
2、问题排查:
1、空指针异常较好排查,直接把报空的对象打印一下,发现通过RequestContextHolder.getRequestAttributes()获取的对象就为空; 但是不是用异步注解时,程序无异常
3、问题原因
1、问题原因比较明显,使用异步注解时,会开启一个新线程去执行业务,新线程中没有传递Request相关的信息; 那么在新线程中肯定无法获取到相应的值使用如下方法可以解决: 1:将要获取的对象作为值传递到异步方法与业务中,但不会从根本上解决问题; 2:在使用异步方法前的业务加上如下代码:
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
通过该方法可以将主线程的上下文数据共享给子线程
RequestContextHolder内部也是使用了俩个ThreadLocal去存储数据
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
NamedInheritableThreadLocal为可继承线程,子线程会继承改线程中的内容,当setRequestAttributes设置为true时
子线程就可以使用父线程中的RequestAttribute信息了。
20230309 记录点:mongoDB默认区分大小写
20230214 @Aspect日志拦截器无法拦截api请求异常
1、问题场景:
在排查一次线上问题时,突然发现异常的接口(a)没有输出请求与响应日志,但是其他接口(b)请求响应日志正常输出, 且这俩个接口在同一个controller中
2、问题排查
1、a、b两接口在同一个controller中,那么先检查@Aspect日志拦截器,看是否做了特殊处理, 排查后发现没有特殊处理; 2、对比两个接口,使用的注解、路径、请求参数等信息,均无异常; 在反复对比的过程中,发现a接口没有修饰符,而b接口有public修饰符; a接口加上public修饰符后,日志拦截器正常工作;
3、问题原因
1、没有加修饰符时,api提供类中的方法的作用域为default,其他包无法访问; 也就导致了日志拦截类没有访问到该api,定义为public时,就可以拦截到了。
20230210 spring cloud gateway集成skywalking抛出空指针异常,且网关无法访问所有服务并产生跨域
1、问题场景
原有服务模块需要加入skywalking链路追踪相关插件,但是在网关中加入skywalking-agent时,网关启动后无法访问其他服务; 且接口无法调用,不是超时就是跨域,去掉agent后,网关恢复正常;这种现象只有部署在容器中时会出现; 本地启动网关,加入agent进行链路追踪时不会出现该现象。
2、问题排查
1、由于只在容器中出现该问题,无法本地调试,只能根据异常日志定位问题;
但是抛异常的代码块为skywalking的gateway插件,追踪相应代码发现响应对象为空导致的;
那么就是因为跨域无法获取到响应,然后追踪的时候网关插件抛出了空指针异常。
2、根据上述排查,可能为agent插件问题,插件更新替换后还有该问题;
3、根据日志在网上查询,有类似空指针的问题,但均不符合;
4、于是通过github上搜索skywalking的issue,看是否有类似的问题,问题是有,
但被归结为插件问题,需要自行调试测试(由于网络问题和依赖问题,无法进行本地调试)
5、这里开始回归代码,检查原有网关的配置和依赖等问题,但由于是老代码,依赖复杂,没有注释说明;
排查无法进行;
6、多次试验无果后,决定重新写一下网关模块,单独起项目,实现原有网关的所有业务功能,但是没有
增加限流插件和限流配置(原有同时网关使用了redis限流和sentinel限流)
7、重新部署新网关模块后,通过新网关访问接口时,问题消失了。。。
8、尝试在github请教skywalking的共享者,对方表示从未见过这样的问题。。。
附上链接(https://github.com/apache/skywalking/discussions/10357)
3、问题原因
1、猜测可能为限流模块和其限流配置导致的该问题,但为什么会产生这样的问题,无从定位,
只能通过一点点移除原有网关的限流模块,发布并验证了。
** 已彻底解决 ** :skyagent的插件包中,有gatew2.x和3.x的插件,启动时使用了2.x的插件,发送了上述问题,但项目中使用的gateway版本对应的应该是3.x的插件,为什么使用到了2.x的插件还有待排查,且依赖树中spring cloud gatew的版本均一致,没发现低版本gateway依赖
20230207 skywalking agent引入后,项目启动时Controller初始化异常
1、问题描述
- 项目引入skywalking agent后,部署服务时启动失败,抛如下异常
- 本地引入skywalking agent后,项目可正常启动
- skywalking agent版本为8.9.0
1.1 异常日志核心内容为实例化bean失败,缺少需要空参构造,但在本地引入agent后没有出现该异常
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1270)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1164)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
1.2 controller代码如下,此处采用了构造注入的方式实例化Service
2、问题排查
2.1 skywalking-agent版本与插件检查
- 本地的skywalkingagent无异常,docker镜像中使用的skywalking-agent会出现该异常,俩个agent版本均一致,唯一不同点在于镜像中的agent移除了不用的插件,添加了部分插件,与本地agent插件不同,怀疑可能是镜像中添加的插件影响导致,开始对比插件;
- 对比移除相应添加插件后,问题依旧无法解决,直接把本地agent中的全部插件复制到镜像中后,服务可正常启动,那么就不是添加插件导致的问题,而是缺少插件导致的问题;
- 由于插件过多,有八九十个,无法明确的找到缺少什么插件,于是去skywalking的github上看下是否有人遇到过相同的问题,进查询后崅实有类似的问题,虽然原因各不相同,但核心内容均为实例化类失败,猜测可能为缺少spring插件导致;
- 搜索本地agent的spring相关插件,根据插件名找出相关性较高的插件,猜测可能为如下几个插件
- 一个个引入,然后启动验证,最终定位到了是缺少了apm-spring-core-patch-8.9.0这个插件
3、问题原因
- 在上面的controller代码中,使用了构造注入的方式实例初始化,这就导致了初始该类时需要空参构造,然而代码只提供了有参构造,实例化找不到构造函数,就抛出了异常;
- 此时也可以通过增加空参注解去解决,但是涉及到很多代码的改动,故抛弃;
- 那么为什么引入了apm-spring-core-patch-8.9.0这个插件就可以正常运行启动呢?把这个包解压后,看下代码做了什么事情,发现了如下一段代码,在这段代码中,进行了实例化增强,对所有有构造函数的类先进行了初始化,并存入缓存,这样在spring实例化该类时,先在缓存中查到了该类,然后直接取出,就不会抛出实例化失败的异常了:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
public class AutowiredAnnotationProcessorInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor {
public AutowiredAnnotationProcessorInterceptor() {
}
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
}
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
Class<?> beanClass = (Class)allArguments[0];
if (!EnhancedInstance.class.isAssignableFrom(beanClass)) {
return ret;
} else {
Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = (Map)objInst.getSkyWalkingDynamicField();
Constructor<?>[] candidateConstructors = (Constructor[])candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
Constructor<?>[] returnCandidateConstructors = (Constructor[])((Constructor[])ret);
if (returnCandidateConstructors != null) {
candidateConstructors = returnCandidateConstructors;
} else {
Constructor<?>[] rawConstructor = beanClass.getDeclaredConstructors();
List<Constructor<?>> candidateRawConstructors = new ArrayList();
Constructor[] var12 = rawConstructor;
int var13 = rawConstructor.length;
for(int var14 = 0; var14 < var13; ++var14) {
Constructor<?> constructor = var12[var14];
if (!Modifier.isPrivate(constructor.getModifiers())) {
candidateRawConstructors.add(constructor);
}
}
if (candidateRawConstructors.size() == 1 && ((Constructor)candidateRawConstructors.get(0)).getParameterTypes().length > 0) {
candidateConstructors = new Constructor[]{(Constructor)candidateRawConstructors.get(0)};
} else {
candidateConstructors = new Constructor[0];
}
}
candidateConstructorsCache.put(beanClass, candidateConstructors);
}
return candidateConstructors.length > 0 ? candidateConstructors : null;
}
}
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
}
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap(20);
objInst.setSkyWalkingDynamicField(candidateConstructorsCache);
}
}
20230129 xxljob日志抛出中文异常导致任务无法停止,一直重试
xxljob执行的时候抛出的异常日志包含中文字段
重新拉取解析重试的时候 无法解析成json
所以就一直出错 一直重试
在这里插入图片描述
20230113 restTemplate调用接口数据返回缺少异常
1、问题场景
业务代码中,通过restTemplate.getForEntity(url,Map.class)调用接口时,出现了以下情况; 返回的json对象的list中,全部只有一条数据,但是服务提供方响应数据的时候确认了每个list中 都有多条数据。
2、问题排查
1、刚开始怀疑服务提供方数据返回异常,于是查看服务提供方的接口响应日志,发现并没有问题; 每个list中数据量均正常; 2、在服务调用方打印响应结果,发现打印出的结果中,list数据已经减少,那么问题即出现在服务调用方; 定位到问题出现位置,开始排查为什么缺少,debug时发现解析响应数据时,出现了数据丢失的问题;
3、问题原因
查了一些资料,大概是如下的问题:
举个个例子:是map中嵌入list,Map<String,Object>中Object一旦映射的是List,自动映射的返回数据只会返回List的最后一条数据,
原因是map.put()的键相同,导致覆盖。进而导致数据缺失。
使用java自动映射的对象类型:Map.class List.class Object.class等,必须保证接口返回的数据类型是单一的某一种,
(无论数量多少,只要种量为一即可),而不能是多种数据类型嵌套的复杂数据。一旦为复杂数据只能手动创建对应实体类。
总的来说就是远程接口返回的数据类型是使用了泛型,或者返回数据所使用的实体类中数据类型不唯一,
那么restTemplate接收的时候也只能使用对应实体类,而不能让java自己去映射。
4、解决方案,目前有如下俩种:
1、配置restTemplate序列化配置,具体方案可以自行查询; 2、通过restTemplate的exchange方法来解决:可以参考如下代码:
public void queryTest(String name) {
//CommonResponse和CustomDTO为自定义响应类
ParameterizedTypeReference<CommonResponse<CustomDTO>> typeRef = new ParameterizedTypeReference<CommonResponse<CustomDTO>>() {};
//设置Http的Header
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//设置访问参数
HashMap<String, Object> params = new HashMap<>();
params.put("name", name);
//设置访问的Entity
HttpEntity entity = new HttpEntity<>(params, headers);
CommonResponse<CustomDTO> response = restTemplate.exchange("url" , HttpMethod.GET,entity,typeRef).getBody();
log.info("queryOrderHeaderTest响应json,[{}]",JSONObject.toJSONString(response));
return response.getData();
}
5、参考链接:
www.bbsmax.com/A/1O5EY7PW5… www.cnblogs.com/yzyBalance/… blog.csdn.net/qq_37855749…
20230105 通过网关调用服务接口,接口间歇性异常返回500
1、问题场景
通过网关调用某模块接口时,接口偶现500异常,connection time out ,
2、问题排查
1、网关与服务模块为容器化部署,怀疑可能容器内网络有问题,排查后网络正常; 2、查看调用服务模块日志,服务正常,且没有异常日志输出; 3、查看网关模块,网关输出日志如下:显示服务网络不可达;排查网关后发现网关nacos配置 可能有问题,修改部署后,问题依然存在; 4、nacos排查,查看相应模块服务有没有注册到nacos,注册状态也正常; 于是在发生异常时,查看nacos服务状态,nacos服务健康状态为false,即服务不健康 但是查看相应服务模块,服务模块运行正常,且在几分钟后,nacos健康状态自动恢复; 将nacos中对应服务下线后,发现又有服务注册上来,但是这个服务只启动了一个, 删除该服务,重新注册后,问题修复。
3、问题原因:
原因就是nacos中注册的服务与实际服务不符,具体原因不明,猜测可能为nacos缓存问题, 下线的服务没有全部删除,导致新注册的服务ip被覆盖,忽隐忽现可能为nacos服务发现机制与缓存 相互覆盖导致。