目录
最近做一个导出聊天记录的需求,为了提高接口响应速度,我便使用CompletableFuture开启两个线程,一个下载文件,另一个生成PDF,两个线程都执行完成再压缩返回;本来一切顺利推进,但线上环境一个偶然出现的异常信息引起了我的注意,奇怪的是在本地调试没有遇到过,而且此异常的触发条件是在线上打包部署后第一次调用此接口才有几率复现这个问题;emm........mmp,原本想睁一只眼闭一只眼不管这个问题,我不说测试也不一定能发现对吧;可惜我是有点强迫症的,怎么能允许自己的接口有一丁点的瑕疵呢,誓与bug不共戴天,于是一场艰难的战斗打响了...
1、异常信息
2022-10-17 20:18:06,949 [TID: N/A] [ERROR] [sendMessage-0] c.z.r.s.NewRocketApiService [NewRocketApiService.java : 346] fileTask exception:
java.util.concurrent.CompletionException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: Could not find class [org.springframework.boot.autoconfigure.condition.OnPropertyCondition]
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1739)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: Could not find class [org.springframework.boot.autoconfigure.condition.OnPropertyCondition]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:600)
at java.base/java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:678)
at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:737)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:661)
at com.zzw.redops.service.NewRocketApiService.lambda$exportChatRecords$6(NewRocketApiService.java:328)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
... 3 common frames omitted
Caused by: java.lang.IllegalArgumentException: Could not find class [org.springframework.boot.autoconfigure.condition.OnPropertyCondition]
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:334)
at org.springframework.context.annotation.ConditionEvaluator.getCondition(ConditionEvaluator.java:124)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:96)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:88)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:71)
at org.springframework.context.annotation.AnnotatedBeanDefinitionReader.doRegisterBean(AnnotatedBeanDefinitionReader.java:254)
at org.springframework.context.annotation.AnnotatedBeanDefinitionReader.registerBean(AnnotatedBeanDefinitionReader.java:147)
at org.springframework.context.annotation.AnnotatedBeanDefinitionReader.register(AnnotatedBeanDefinitionReader.java:137)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.register(AnnotationConfigApplicationContext.java:162)
at org.springframework.cloud.context.named.NamedContextFactory.createContext(NamedContextFactory.java:120)
at org.springframework.cloud.context.named.NamedContextFactory.getContext(NamedContextFactory.java:102)
at org.springframework.cloud.netflix.ribbon.SpringClientFactory.getContext(SpringClientFactory.java:131)
at org.springframework.cloud.context.named.NamedContextFactory.getInstance(NamedContextFactory.java:146)
at org.springframework.cloud.netflix.ribbon.SpringClientFactory.getInstance(SpringClientFactory.java:121)
at org.springframework.cloud.netflix.ribbon.SpringClientFactory.getClientConfig(SpringClientFactory.java:75)
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.getClientConfig(LoadBalancerFeignClient.java:98)
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:82)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at com.sun.proxy.$Proxy189.getObjectFile(Unknown Source)
at com.zzw.redops.service.NewRocketApiService.lambda$null$5(NewRocketApiService.java:334)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.autoconfigure.condition.OnPropertyCondition
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:398)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:324)
... 31 common frames omitted
2、异常代码实现(优化前)
CompletableFuture<Void> fileTask = CompletableFuture.runAsync(() -> {
recordsVOList.parallelStream().filter(item-> RocketConstants.MESSAGE_TYPE_FILE.equals(item.getType())).forEach(item->{
JSONObject jsonObject = JSONObject.parseObject(item.getMsg());
String fileName = jsonObject.getString("fileName");
String fileURI = jsonObject.getString("fileURI");
String objectName = fileURI.substring(fileURI.lastIndexOf("/") + 1);
Response res = ossClient.getObjectFile("redops-test", objectName);
File downloadFile = new File("downloadFile/"+fileName);
try {
FileUtils.copyInputStreamToFile(res.body().asInputStream(), downloadFile);
} catch (IOException e) {
log.error("copyInputStreamToFile exception:",e);
}
files.add(downloadFile);
});
log.info("文件完成");
}, threadPool).exceptionally(e->{
log.error("fileTask exception:",e);
return null;
});
3、原因分析
找了许久也没找到适用的解决方案,最终在GitHub的开源项目spring-cloud-openfeign中看到了相似问题的描述,我从中的理解是由于在异步任务中使用并行流通过openfeign调用其它模块的接口造成,可能是本身就存在这个问题,原文如下
原文链接:https://github.com/spring-cloud/spring-cloud-openfeign/issues/475
官方维护团队不建议使用此种写法,这个问题并未关闭,维护团队并未给出好的解决方案
4、本文解决方案
原本使用的是并行流下载文件,现改为串行流,问题解决
CompletableFuture<Void> fileTask = CompletableFuture.runAsync(() -> {
recordsVOList.stream().filter(item-> RocketConstants.MESSAGE_TYPE_FILE.equals(item.getType())).forEach(item->{
JSONObject jsonObject = JSONObject.parseObject(item.getMsg());
String fileName = jsonObject.getString("fileName");
String fileURI = jsonObject.getString("fileURI");
String objectName = fileURI.substring(fileURI.lastIndexOf("/") + 1);
Response res = ossClient.getObjectFile("redops-test", objectName);
File downloadFile = new File("downloadFile/"+fileName);
try {
FileUtils.copyInputStreamToFile(res.body().asInputStream(), downloadFile);
} catch (IOException e) {
log.error("copyInputStreamToFile exception:",e);
}
files.add(downloadFile);
});
log.info("文件完成");
}, threadPool).exceptionally(e->{
log.error("fileTask exception:",e);
return null;
});
5、其它解决方案
自定义LoadBalancerClientFactory
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
@Bean
@ConditionalOnMissingBean
public LoadBalancerClientFactory loadBalancerClientFactory(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(){
@Override
protected AnnotationConfigApplicationContext createContext(String name) {
// FIXME: temporary switch classloader to use the correct one when creating the context
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
AnnotationConfigApplicationContext context = super.createContext(name);
Thread.currentThread().setContextClassLoader(originalClassLoader);
return context;
}
};
clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}
6、小结
博主实力有限,暂时没有能力深究其中的原因,大家可根据实际情况尝试使用本文中的方法看是否有效;大家也可关注spring-cloud-openfeign官方维护团队的动态,看官方给出什么样的解决方案,说不定在新的版本中就解决了
有任何问题,欢迎大家指正!
转载请注明出处!