欢迎点击头像进入主页查看更多内容....
业务场景
在业务看板中,需明确各种单据状态不及其数量,以便能跳转相应系统,由于看板节点较多,涉及多个外部业务系统,且都为读操作,则设计为CompletableFuture的runAsync()方法实现异步,然后将统计操作返回,原代码结构如下:
CompletableFuture<Void> t1 = CompletableFuture.runAsync(() -> {
response.xxx;
});
CompletableFuture<Void> t2 = CompletableFuture.runAsync(() -> {
response.xxx;
});
CompletableFuture<Void> t3 = CompletableFuture.runAsync(() -> {
response.xxx;
});
CompletableFuture.allOf(t1, t2, t3 ....).join();
开发环境
SpringCloud + jdk11
报错如下
java.util.concurrent.CompletionException: xxxjava.lang.reflect.InvocationTargetException
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1739) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1728) ~[na:na]
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290) ~[na:na]
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020) ~[na:na]
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656) ~[na:na]
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594) ~[na:na]
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177) ~[na:na]
Caused by: xxx
at xxx
at xxx
at xxx
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java) ~[na:na]
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient$$FastClassBySpringCGLIB$$fb8167ef.invoke(<generated>) ~[na:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at xxx
at jdk.internal.reflect.GeneratedMethodAccessor384.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient$$EnhancerBySpringCGLIB$$d0eeb310.execute(<generated>) ~[na:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:108) ~[feign-core-10.1.0.jar!/:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78) ~[feign-core-10.1.0.jar!/:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-10.1.0.jar!/:na]
at com.sun.proxy.$Proxy217.findShippedButNotReceived(Unknown Source) ~[na:na]
at xxx
at xxx
at xxx
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736) ~[na:na]
... 6 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
at jdk.internal.reflect.GeneratedMethodAccessor385.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at xxx
... 34 common frames omitted
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration]; nested exception is java.io.FileNotFoundException: class path resource [org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar.class] cannot be opened because it does not exist
at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:599) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:302) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:199) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:167) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:315) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.cloud.context.named.NamedContextFactory.createContext(NamedContextFactory.java:120) ~[spring-cloud-context-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.cloud.context.named.NamedContextFactory.getContext(NamedContextFactory.java:88) ~[spring-cloud-context-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.cloud.netflix.ribbon.SpringClientFactory.getContext(SpringClientFactory.java:118) ~[spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.cloud.context.named.NamedContextFactory.getInstance(NamedContextFactory.java:129) ~[spring-cloud-context-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.cloud.netflix.ribbon.SpringClientFactory.getInstance(SpringClientFactory.java:108) ~[spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.cloud.netflix.ribbon.SpringClientFactory.getClientConfig(SpringClientFactory.java:65) ~[spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar!/:2.1.0.RELEASE]
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.getClientConfig(LoadBalancerFeignClient.java:80) ~[na:na]
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.executeOld(LoadBalancerFeignClient.java:64) ~[na:na]
... 38 common frames omitted
Caused by: java.io.FileNotFoundException: class path resource [org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar.class] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:180) ~[spring-core-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:51) ~[spring-core-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[spring-core-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:123) ~[spring-core-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:81) ~[spring-core-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.asSourceClass(ConfigurationClassParser.java:685) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.asSourceClasses(ConfigurationClassParser.java:664) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:570) ~[spring-context-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
... 56 common frames omitted
问题排查过程
首先这个接口并不是新开接口,也是新开接口,但是个人测试业务很简单,所以早已上线,近期版本也由于业务更替较大,升级了版本,而且这个接口已经在测试环境测试过了,怎么会换个环境就报错了呢?第一反应是不是老版本冲突,但自己调用rpc后正常响应,而且如果是我这边的问题,报错不是这样的应该是诸如feign.FeignException: status 500 readingxxxxx才对,但还是重启了对应服务,但重启后仍旧报错
排查代码层面,确认无误后,排查上游,第一时间并未怀疑是Forkjoin的问题,双方重新打包,构建后仍旧失败,随着问题越来越焦灼,测试下普通调用是否正常,遂把异步调用改为同步,重启后成功。
问题原因
在搜索后github上的issues看到了此问题
本地环境可以,但是Linux环境不行
但使用线程池后即可解决此问题,两个方法区别如下,如果不指定线程池则使用系统级别的
/**
* Returns a new CompletableFuture that is asynchronously completed
* by a task running in the {@link ForkJoinPool#commonPool()} after
* it runs the given action.
*
* @param runnable the action to run before completing the
* returned CompletableFuture
* @return the new CompletableFuture
*/
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(ASYNC_POOL, runnable);
}
/**
* Returns a new CompletableFuture that is asynchronously completed
* by a task running in the given executor after it runs the given
* action.
*
* @param runnable the action to run before completing the
* returned CompletableFuture
* @param executor the executor to use for asynchronous execution
* @return the new CompletableFuture
*/
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
当调用FeignBlockdingloadBalancerClient使用并行流(多线程)会有问题。
问题结论
当使用boot build 打包的镜像在docker环境运行时,使用CompletableFuture的runAsync()调用会出现此问题,或者当标记@Async异步时也会出现,但是jdk8中是好的,jdk11会出现此问题,原因是Fegin接口是懒加载的,只有在我们第一次使用该Fegin接口的时候才会对Fegin接口进行初始化,但是如果在ForkJoinWorkerThread中使用Fegin接口的话,就会出现ClassNotFoundException。,既然是ClassNotfound,必然是ClassLoader相关引起的,随后在修复方案中也验证了,此时的classLoader在默认线程中TCCL是找不到Spring管理的加载器的,为null时应在ApplicationInitializer中设置为默认加载器,使用其他普通的JVM线程池是不会出现问题的。这个解释其实并不是很形象,或者很明确,因为恰好是符合这个场景,并且不是直接解决,而是排查解决的,虽然但是,觉得并不是很好的回答,细节太少,等在往期补充。