异步feign调用第三方接口失败

在微服务环境中,使用Feign调用时需要传递登录状态的Cookie信息。通过RequestContextHolder在主线程设置Cookie,但在异步线程中无法获取。本文介绍了由于线程间无法共享ThreadLocal变量导致的问题,并提供了解决方案,包括启用@Async注解、配置异步执行器以及在子线程中正确设置请求上下文。

错误信息

Scope 'request' is not active for the current thread; consider defining a scoped proxy 
for this bean if you intend to refer to it from a singleton; nested exception is 
java.lang.IllegalStateException: No thread-bound request found: Are you referring to 
request attributes outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within a web request and still 
receive this message, your code is probably running outside of DispatcherServlet: In this
case, use RequestContextListener or RequestContextFilter to expose the current request.

当前线程的作用域“请求”未激活;如果您想从一个单体引用它,请考虑为这个bean定义一个作用域代理;嵌套的例外是java。lang.IllegalStateException:未找到线程绑定请求:您是指实际web请求之外的请求属性,还是在最初接收线程之外处理请求?如果您实际上是在web请求中操作,并且仍然收到此消息,那么您的代码可能是在DispatcherServlet之外运行的:在这种情况下,请使用RequestContextListener或RequestContextFilter公开当前请求。(百度翻译)

问题描述

  • 在微服务中使用Feign调用,为了使Feign调用都是登录状态,会把登录状态的Cookie信息都携带上,使用的拦截器往请求里面存储cookie信息使用RequestContextHolder;

  • 开启另外一个线程异步调用feign,因为httpRequest是绑定在ThreadLocal中的,只能在当前线程中共享,子线程是不能获取到父线程的属性.

本人小白,对多线程不太了解,以上都是百度几个小时,啪的一下,很快啊。灵感来了才把问题解决。
首先,你百度应该多多少少会看到这行代码:

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
        .getRequestAttributes();

或者这行:

  RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);

。。。算了不扯了,是兰斯不好玩吗?
总之,很多写的都是说加上以上代码子线程就能获取到父线程的信息,实际上本人亲测无用。正确的写法记录以下以便以后翻阅。

  1. 首先启动类的注解(@EnableAsync)不能忘哈
@EnableMutecSwagger2
@EnableDiscoveryClient
@EnableMutecResourceServer
@SpringBootApplication
@EnableAsync
public class MutecWebinarAdminApplication {
  public static void main(String[] args) {
    SpringApplication.run(MutecWebinarAdminApplication.class, args);
  }
}
  1. 然后是配置类
/**
 3. 线程配置类
 4.  5. @author huwei
 6. @date 2022-02-21 10:44:44
 */
@Configuration
@EnableAsync
public class AsyncTaskConfig implements AsyncConfigurer {

    // ThredPoolTaskExcutor的处理流程
    // 当池子大小小于corePoolSize,就新建线程,并处理请求
    // 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去workQueue中取任务并处理
    // 当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
    // 当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁

    @Override
    @Bean("asyncTaskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        //设置核心线程数
        threadPool.setCorePoolSize(10);
        //设置最大线程数
        threadPool.setMaxPoolSize(50);
        //线程池所使用的缓冲队列
        threadPool.setQueueCapacity(10);
        //等待任务在关机时完成--表明等待所有线程执行完
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
        threadPool.setAwaitTerminationSeconds(30);
        //  线程名称前缀
        threadPool.setThreadNamePrefix("Derry-Async-");
        // 初始化线程
        threadPool.initialize();
        return threadPool;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}
  1. 服务类使用@Async(“asyncTaskExecutor”)注解,注解里的值表示用哪个执行器(这里是@Bean(“asyncTaskExecutor”))。以下是部分代码
	/**
	* @Async有返回值,使用Future接收
	*/
 	@Async("asyncTaskExecutor")
    @Override
    public Future<Boolean> copyProduct(ProductCreateRequest createRequest, RequestAttributes requestAttributes) {
     	long start = System.currentTimeMillis();
    	//关键一步
        RequestContextHolder.setRequestAttributes(requestAttributes, true);
        //调用第三方接口
        ProductCreateResponse response = vhallService.createProduct(createRequest);
		long end = System.currentTimeMillis();
     return new AsyncResult<>(end - start);
    }
  1. 调用多线程方法
//copy产品
AsyncService asyncService = SpringUtil.getBean(AsyncService.class);
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
for (WebinarProduct copy : copyList) {
    Future<Long> task = asyncService.copyProduct(vhallId, copy, requestAttributes);
    // 接受异步方法结果
    while (true) {
        if (task.isDone()) {
            long async = task.get();
            log.info("异步任务执行的时间是:" + async + "(毫秒)");
            break;
        }
    }
}

这行代码确实可以让子线程获取父线程信息,不过必须在父线程中获取,然后在子线程中设置

RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);

也就是拆除两份

//父线程获取
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//子线程里设置,可通过线程方法把值带到子线程
RequestContextHolder.setRequestAttributes(requestAttributes, true);

以前刚学会用这个@Async(“asyncTaskExecutor”)的时候也是做的第三方接口批量调用,不过当时弱鸡搞不出来(讲的好像现在很会一样),后来需求又变了用不到线程就没有深入研究这个注解和问题。现在想想当时注解用的是没错,只是这其实是两个问题,多线程和feign线程调用第三方接口。当做一个问题就会看不懂报错信息是哪里的问题。

学习如白纸一张,不卷则平~

这位大佬写的详细些,可供参考:https://blog.youkuaiyun.com/asd136912/article/details/87716215

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值