CompletableFuture调用OpenFegin,谁用谁傻

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 当调用FeignBlockdingloadBalancerClient使用并行流(多线程)会有问题。

欢迎点击头像进入主页查看更多内容....

业务场景

在业务看板中,需明确各种单据状态不及其数量,以便能跳转相应系统,由于看板节点较多,涉及多个外部业务系统,且都为读操作,则设计为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才对,但还是重启了对应服务,但重启后仍旧报错

image.png
image.png
排查代码层面,确认无误后,排查上游,第一时间并未怀疑是Forkjoin的问题,双方重新打包,构建后仍旧失败,随着问题越来越焦灼,测试下普通调用是否正常,遂把异步调用改为同步,重启后成功。

问题原因

在搜索后github上的issues看到了此问题
image.png

image.png
本地环境可以,但是Linux环境不行
image.png
但使用线程池后即可解决此问题,两个方法区别如下,如果不指定线程池则使用系统级别的


    /**
     * 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);
    }

image.png
当调用FeignBlockdingloadBalancerClient使用并行流(多线程)会有问题。

问题结论

当使用boot build 打包的镜像在docker环境运行时,使用CompletableFuture的runAsync()调用会出现此问题,或者当标记@Async异步时也会出现,但是jdk8中是好的,jdk11会出现此问题,原因是Fegin接口是懒加载的,只有在我们第一次使用该Fegin接口的时候才会对Fegin接口进行初始化,但是如果在ForkJoinWorkerThread中使用Fegin接口的话,就会出现ClassNotFoundException。,既然是ClassNotfound,必然是ClassLoader相关引起的,随后在修复方案中也验证了,此时的classLoader在默认线程中TCCL是找不到Spring管理的加载器的,为null时应在ApplicationInitializer中设置为默认加载器,使用其他普通的JVM线程池是不会出现问题的。这个解释其实并不是很形象,或者很明确,因为恰好是符合这个场景,并且不是直接解决,而是排查解决的,虽然但是,觉得并不是很好的回答,细节太少,等在往期补充。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
4月前
|
缓存 Java Maven
CompletableFuture
【7月更文挑战第29天】
46 4
|
4月前
|
Java 开发者 Spring
CompletableFuture 使用总结
CompletableFuture 使用总结
119 1
|
4月前
|
存储 缓存 安全
(八)深入并发之Runnable、Callable、FutureTask及CompletableFuture原理分析
关于Runnable、Callable接口大家可能在最开始学习Java多线程编程时,都曾学习过一个概念:在Java中创建多线程的方式有三种:继承Thread类、实现Runnable接口以及实现Callable接口。但是实则不然,真正创建多线程的方式只有一种:继承Thread类,因为只有`new Thread().start()`这种方式才能真正的映射一条OS的内核线程执行,而关于实现Runnable接口以及实现Callable接口创建出的Runnable、Callable对象在我看来只能姑且被称为“多线程任务”,因为无论是Runnable对象还是Callable对象,最终执行都要交由Threa
|
3月前
CompletableFuture 打桌球的应用
CompletableFuture 打桌球的应用
17 0
|
3月前
|
Java 测试技术
CompletableFuture 使用
CompletableFuture 使用
45 0
|
4月前
|
并行计算 Java
Future、CompletableFuture概述
Future、CompletableFuture概述
89 0
|
消息中间件 Java 中间件
Future and CompletableFuture
Future代表异步执行的结果,也就是说异步执行完毕后,结果保存在Future里, 我们在使用线程池submit()时需要传入Callable接口,线程池的返回值为一个Future,而Future则保存了执行的结果 ,可通过Future的get()方法取出结果,如果线程池使用的是execute(),则传入的是Runnable接口 无返回值。
71 0
|
Java 测试技术
CompletableFuture使用详解
CompletableFuture是jdk8的新特性。CompletableFuture实现了CompletionStage接口和Future接口
272 0
CompletableFuture使用详解
Zp
【CompletableFuture】CompletableFuture中join()和get()方法的区别
【CompletableFuture】CompletableFuture中join()和get()方法的区别
Zp
852 0