场景很简单,在已有的服务中新加几个接口,已有的服务全部的基础配置都是稳定可用的(已在线上跑了很久了);前端项目为单独的一个新项目,联调开发阶段都没问题后,部署测试环境时,出现了一个很小的问题,接口跨域了。这种小问题比较简单,定位也比较明确,少配置的加配置就完事,但是,配置都是全的,这就很尴尬了。
第一步,当然是看配置了,稳定运行,配置不一定全,又很尴尬,没少配置,其他的前端项目也有调用这个后端服务的接口;
第二步,检查前端请求参数,请求头信息,发现前端没有传递 Origin 这个请求头,那么这个请求头有什么用呢?后端项目使用了 com.thetransactioncompany.cors 包下面的com.thetransactioncompany.cors.CORSFilter过滤器,
具体的执行过滤器是:**com.thetransactioncompany.cors.CORSRequestHandler#CORSRequestHandler**下面的这个方法,当没有**Origin** 这个请求头的时候,是会出现跨域问题
![image.png](https://img-blog.csdnimg.cn/img_convert/d2fbab211fca77b4bfb237712fb9e766.png#averageHue=#2f2c2c&clientId=ue070923b-c2c2-4&from=paste&height=606&id=u89d2e82e&originHeight=606&originWidth=1016&originalType=binary&ratio=1&rotation=0&showTitle=false&size=86969&status=done&style=none&taskId=u46050a5f-5f92-4b9b-8e37-467b6705d70&title=&width=1016)
与前端沟通后,前端打开了下面的配置,加上了Origin 这个请求头
加上去后,浏览器看不到跨域CORS异常了,出现了以下异常,这就和跨域不沾边了,触发了俩次请求一次预请求,一次正式请求,预请求通过后,才会正式请求接口:
问题找到了,修改浏览器配置后项目可以正常访问了,我的浏览器版本是114.0.5735.110(正式版本) (64 位),也有方案说后端响应时添加响应头,上面的文章中有,我实践后没有生效,以下方案我均有测试,但均未生效:
1:增加响应头–无效;
2:增加响应头+用自定义过滤器进行跨域处理并处理options请求—无效;
3:还有使用一个spring boot的预请求配置项— 无效
4:还有一些前端手段,但是需要前端开发调试测试。。。
根本原因就是网络的问题,因为没有服务器权限,没有办法使用nginx代理的方式去做实验,或者都改为https请求,理论上也可以解决该问题。
在进行oss文件上传时,要先读取一次InputStream流,进行文件内容校验,然后再读取一次InputStream流;
使用oss的skd上传到oss,但是在上传至oss时,抛出了空指针异常,但是在校验文件时没有抛出空指针异常,
也就是说InputStream是有值的,那么为什么第二次读取会异常
这一次问题比较明确,就是第一次读取正常,第二次读取InputStream流的时候抛出空指针异常;
问题肯定存在读取的地方,我使用了 IOUtils.copy(inputStream, outputStream
)的方式读取的流;
进入看代码就一目了然了,InputStream里有个偏移量的标志,读取流时需要根据偏移量读取
第一次读取的时候偏移量会从0递增,且不会重置,第二次读取的时候是从该流的最大偏移量读取的,
自然什么都读不到了,就抛出了空指针异常,可以通过流的reset()方法重置偏移量,就可以正常读取了;
但是InputStream不支持reset(),只能使用其子类实现,可以参考如下方式:
//可以重复读取流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
IOUtils.copy(inputStream, byteArrayOutputStream);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
byteArrayOutputStream.reset();
byteArrayInputStream.reset();
在一次的分页数据查询时,出现了以下情况:第二页的前几条数据为第一页的最后几条数据,且数据总数正常
1、怀疑为自定义分页sql逻辑有问题,取出日志中的分页sql运行后发现,sql执行的分页结果就会重复
那么应该就是mysql的一些默认机制导致的该现象,搜索阅读文档后,发现mysql5.6及以后的版本,没加排序时
是会出现该现象,加排序后解决。
在MySQL 5.6的版本上,优化器在遇到order by limit语句的时候,做了一个优化,即 使用了priority queue。使用 priority queue 的目的,
就是在不能使用索引有序性的时候,如果要排序,并且使用了limit n,那么只需要在排序的过程中,保留n条记录即可,
这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer 少量的内存就可以完成排序。
之所以MySQL 5.6出现了第二页数据重复的问题,是因为 priority queue 使用了堆排序的排序方法,
而堆排序是一个不稳定的排序方法,也就是相同的值可能排序出来的结果和读出来的数据顺序不一致。
MySQL 5.5 没有这个优化,所以也就不会出现这个问题。
也就是说,MySQL 5.5是不存在本文提到的问题的,5.6版本之后才出现了这种情况。再看下MySQL解释sql语言时的执行顺序:
(1) SELECT
(2) DISTINCT <select_list>
(3) FROM <left_table>
(4) <join_type> JOIN <right_table>
(5) ON <join_condition>
(6) WHERE <where_condition>
(7) GROUP BY <group_by_list>
(8) HAVING <having_condition>
(9) ORDER BY <order_by_condition>
(10) LIMIT <limit_number>
执行顺序依次为 form… where… select… order by… limit…
,由于上述priority queue的原因,在完成select之后,
所有记录是以堆排序的方法排列的,在进行order by时,仅把view_count值大的往前移动。
但由于limit的因素,排序过程中只需要保留到5条记录即可,view_count并不具备索引有序性,所以当第二页数据要展示时,
mysql见到哪一条就拿哪一条,因此,当排序值相同的时候,
第一次排序是随意排的,第二次再执行该sql的时候,其结果应该和第一次结果一样。
https://developer.aliyun.com/article/869044
https://zhuanlan.zhihu.com/p/102980603
场景如下,目前有两个微服务A、B,两个微服务依赖公共的请求体包;
A需要通过Feign调用B微服务的的接口,请求体如下
{
"title": "test",
"dataList": [
12345678910,
123456788910
]
}
请求体中的dataList,是一个long类型的数组,但是在B服务接收时,接收到的参数是下面这样的:
{
"title": "test",
"dataList": [
"12345678910",
"123456788910"
]
}
1、先补充A、B服务的接口请求日志,打印发送出去的请求参数与接收到的请求参数具体是什么,
发现A发送的时候是正常的,但是B接收到的就已经变成了字符串数组;那么应该是序列化和反序列化上出了问题;
2、要知道服务A发送请求时,需要将参数序列化,服务B接收到请求时,需要将参数反序列化;
但是反序列化只是把二进制内容变成了java对象,应该不会对数据类型做什么处理,
那就是服务A在发送数据时序列化的问题
3、然后就一步步的debug往里调试,最终发现了在序列化时,将long类型转换成了String类型
那为什么会要把long类型字段转成String?开始检查项目中的序列化配置,发现的确配置了这个序列化规则,
会把Long类型转换为String;原因就是过长的long类型返回给前端的时候,前端会出现数据精度丢失;
所以转成了String,就不会丢失精度了,而此处的Feign调用没有配置序列化规则,使用了Spring默认的配置;
就出现了上述的问题,修改之后恢复正常;
有一个历史综合信息查询接口,响应速度很慢,已经到了快10s才会响应,导致前端界面加载过慢,必须得优化。
列表查询的业务比较明确,即查询一个主体的不同的附属信息,需要查询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中有大量数据时,就会非常耗时,数据量小的主体,响应速度就很快;
开发业务时,不要先急着动手,要想清楚大概业务逻辑,可以适当写一些todo list,将业务列出来
这样可以明确哪些数据可能需要批量查询,哪些业务的数据需要聚合查询,哪些业务的统计数据可以提前做,而不是实时查实时统计等
要尽量避免以下操作:
1、循环调用rpc接口;
2、循环查询任何数据库;
3、对大量历史数据进行查询并实时统计。
正常业务中使用RequestContextHolder.getRequestAttributes()时可以获取到请求头中的一些用户信息,
在一次接口优化时,需要优化响应时间,目前网关设置响应时间默认为2分钟,但涉及要处理的数据量大时,
2分钟不够了,且直接返回了超时异常(实际业务正在处理中),导致用户体验很差,
于是将部分耗时业务使用异步的方式去处理(该业务为实际处理数据),先返回结果,提示处理中;
但是异步线程中使用了RequestContextHolder.getRequestAttributes()获取时无法获取用户信息,就抛出了空指针异常
1、空指针异常较好排查,直接把报空的对象打印一下,发现通过RequestContextHolder.getRequestAttributes()获取的对象就为空;
但是不是用异步注解时,程序无异常
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信息了。
在排查一次线上问题时,突然发现异常的接口(a)没有输出请求与响应日志,但是其他接口(b)请求响应日志正常输出,
且这俩个接口在同一个controller中
1、a、b两接口在同一个controller中,那么先检查@Aspect日志拦截器,看是否做了特殊处理,
排查后发现没有特殊处理;
2、对比两个接口,使用的注解、路径、请求参数等信息,均无异常;
在反复对比的过程中,发现a接口没有修饰符,而b接口有public修饰符;
a接口加上public修饰符后,日志拦截器正常工作;
1、没有加修饰符时,api提供类中的方法的作用域为default,其他包无法访问;
也就导致了日志拦截类没有访问到该api,定义为public时,就可以拦截到了。
原有服务模块需要加入skywalking链路追踪相关插件,但是在网关中加入skywalking-agent时,网关启动后无法访问其他服务;
且接口无法调用,不是超时就是跨域,去掉agent后,网关恢复正常;这种现象只有部署在容器中时会出现;
本地启动网关,加入agent进行链路追踪时不会出现该现象。
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)
1、猜测可能为限流模块和其限流配置导致的该问题,但为什么会产生这样的问题,无从定位,
只能通过一点点移除原有网关的限流模块,发布并验证了。
** 已彻底解决 ** :skyagent的插件包中,有gatew2.x和3.x的插件,启动时使用了2.x的插件,发送了上述问题,但项目中使用的gateway版本对应的应该是3.x的插件,为什么使用到了2.x的插件还有待排查,且依赖树中spring cloud gatew的版本均一致,没发现低版本gateway依赖
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)
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);
}
}
xxljob执行的时候抛出的异常日志包含中文字段
重新拉取解析重试的时候 无法解析成json
所以就一直出错 一直重试
在这里插入图片描述
业务代码中,通过restTemplate.getForEntity(url,Map.class)调用接口时,出现了以下情况;
返回的json对象的list中,全部只有一条数据,但是服务提供方响应数据的时候确认了每个list中
都有多条数据。
1、刚开始怀疑服务提供方数据返回异常,于是查看服务提供方的接口响应日志,发现并没有问题;
每个list中数据量均正常;
2、在服务调用方打印响应结果,发现打印出的结果中,list数据已经减少,那么问题即出现在服务调用方;
定位到问题出现位置,开始排查为什么缺少,debug时发现解析响应数据时,出现了数据丢失的问题;
查了一些资料,大概是如下的问题:
举个个例子:是map中嵌入list,Map中Object一旦映射的是List,自动映射的返回数据只会返回List的最后一条数据,
原因是map.put()的键相同,导致覆盖。进而导致数据缺失。
使用java自动映射的对象类型:Map.class List.class Object.class等,必须保证接口返回的数据类型是单一的某一种,
(无论数量多少,只要种量为一即可),而不能是多种数据类型嵌套的复杂数据。一旦为复杂数据只能手动创建对应实体类。
总的来说就是远程接口返回的数据类型是使用了泛型,或者返回数据所使用的实体类中数据类型不唯一,
那么restTemplate接收的时候也只能使用对应实体类,而不能让java自己去映射。
1、配置restTemplate序列化配置,具体方案可以自行查询;
2、通过restTemplate的exchange方法来解决:可以参考如下代码:
public void queryTest(String name) {
//CommonResponse和CustomDTO为自定义响应类
ParameterizedTypeReference> typeRef = new ParameterizedTypeReference>() {};
//设置Http的Header
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//设置访问参数
HashMap params = new HashMap<>();
params.put("name", name);
//设置访问的Entity
HttpEntity entity = new HttpEntity<>(params, headers);
CommonResponse response = restTemplate.exchange("url" , HttpMethod.GET,entity,typeRef).getBody();
log.info("queryOrderHeaderTest响应json,[{}]",JSONObject.toJSONString(response));
return response.getData();
}
https://www.bbsmax.com/A/1O5EY7PW57/
https://www.cnblogs.com/yzyBalance/p/13546552.html
https://blog.csdn.net/qq_37855749/article/details/117691268
通过网关调用某模块接口时,接口偶现500异常,connection time out ,
1、网关与服务模块为容器化部署,怀疑可能容器内网络有问题,排查后网络正常;
2、查看调用服务模块日志,服务正常,且没有异常日志输出;
3、查看网关模块,网关输出日志如下:显示服务网络不可达;排查网关后发现网关nacos配置
可能有问题,修改部署后,问题依然存在;
4、nacos排查,查看相应模块服务有没有注册到nacos,注册状态也正常;
于是在发生异常时,查看nacos服务状态,nacos服务健康状态为false,即服务不健康
但是查看相应服务模块,服务模块运行正常,且在几分钟后,nacos健康状态自动恢复;
将nacos中对应服务下线后,发现又有服务注册上来,但是这个服务只启动了一个,
删除该服务,重新注册后,问题修复。
原因就是nacos中注册的服务与实际服务不符,具体原因不明,猜测可能为nacos缓存问题,
下线的服务没有全部删除,导致新注册的服务ip被覆盖,忽隐忽现可能为nacos服务发现机制与缓存
相互覆盖导致。