springboot中使用异步线程导致Request请求头丢失问题

背景

异步线程请求头丢失的问题通常发生在多线程环境中,特别是在使用 CompletableFuture 或其他异步编程模型时。具体原因如下:

异步线程请求头丢失的原因

当主线程将任务提交到异步线程池执行时,当前线程中的 RequestAttributes(包括请求头等上下文信息)不会自动传递到异步线程中。这是因为 RequestAttributes 是基于当前线程的本地存储(ThreadLocal)实现的,而异步线程是在不同的线程中执行任务,因此无法访问到主线程中的 RequestAttributes。这种情况下,异步线程执行任务时会丢失请求头等上下文信息。
为了确保异步线程能够正确获取请求上下文信息,需要在任务执行前后显式地设置和重置 RequestAttributes。

一、问题描述

笔者在开发中使用CompletableFuture去异步调用openFegin服务,但是在传递过程中发现了问题,原来存储在header中的信息,在进入其他服务后header中存储的user信息都丢失了

CompletableFuture<R<Boolean>> orderCheckFuture = CompletableFuture.supplyAsync(() -> remoteBusinessService.checkUnfinishedOrder(SecurityConstants.INNER, userId));
CompletableFuture<R<Boolean>> taskCheckFuture = CompletableFuture.supplyAsync(() -> remoteInspectionService.checkUnfinishedTask(SecurityConstants.INNER, userId));
CompletableFuture.allOf(orderCheckFuture, taskCheckFuture).join();
R<Boolean> orderCheck = orderCheckFuture.join();
R<Boolean> taskCheck = taskCheckFuture.join();

这种写法就导致了请求头信息丢失,服务调用的发起方在system服务模块,header中的租户id为12361

在这里插入图片描述

请求在到达business服务模块或者inspection服务模块时,租户id丢失,变成了默认值9999

在这里插入图片描述

二、解决方案

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

CompletableFuture<R<Boolean>> orderCheckFuture = CompletableFuture.supplyAsync(() -> {
    RequestContextHolder.setRequestAttributes(requestAttributes);
    return remoteBusinessService.checkUnfinishedOrder(SecurityConstants.INNER, userId);
});

CompletableFuture<R<Boolean>> taskCheckFuture = CompletableFuture.supplyAsync(() -> {
    RequestContextHolder.setRequestAttributes(requestAttributes);
    return remoteInspectionService.checkUnfinishedTask(SecurityConstants.INNER, userId);
});

CompletableFuture.allOf(orderCheckFuture, taskCheckFuture).join();

R<Boolean> orderCheck = orderCheckFuture.join();
R<Boolean> taskCheck = taskCheckFuture.join();

先通过如下代码获取请求头属性

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

接着在异步线程中重新设置下异步线程所携带请求头的信息,这样就保证了请求头的连续传递性

三、拓展

不想每次都在CompletableFuture中都写如下设置请求头信息的代码怎么办?

RequestContextHolder.setRequestAttributes(requestAttributes);

笔者这里给出一个自认为相对能减少点代码量的解决方法(具体有无必要完全看个人)

自定义一个继承CompletableFuture的类 CustomCompletableFuture

package com.zlbc.system.config;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.RequestAttributes;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

public class CustomCompletableFuture<T> extends CompletableFuture<T> {

    public CustomCompletableFuture(RequestAttributes requestAttributes) {
    }

    public static <T> CustomCompletableFuture<T> supplyAsync(Supplier<T> supplier, Executor executor, RequestAttributes requestAttributes) {
        CustomCompletableFuture<T> future = new CustomCompletableFuture<>(requestAttributes);
        executor.execute(() -> {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                T result = supplier.get();
                future.complete(result);
            } catch (Exception e) {
                future.completeExceptionally(e);
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        });
        return future;
    }
}

使用方法如下,笔者设置了一个可以传递线程池的参数

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        
ExecutorService customExecutor = Executors.newFixedThreadPool(5);
        
CustomCompletableFuture<R<Boolean>> orderCheckFuture = CustomCompletableFuture.supplyAsync(
                () -> remoteBusinessService.checkUnfinishedOrder(SecurityConstants.INNER, userId),
                customExecutor,
                requestAttributes
);

CustomCompletableFuture<R<Boolean>> taskCheckFuture = CustomCompletableFuture.supplyAsync(
                () -> remoteInspectionService.checkUnfinishedTask(SecurityConstants.INNER, userId),
                customExecutor,
                requestAttributes
);

CompletableFuture.allOf(orderCheckFuture, taskCheckFuture).join();

R<Boolean> orderCheck = orderCheckFuture.join();
R<Boolean> taskCheck = taskCheckFuture.join();

这种写法就是减少了设置的代码,传一个参数进去即可

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

这个可以写在某些全局工具类如ServletUtils

在这里插入图片描述
使用时如下获取即可

RequestAttributes requestAttributes = ServletUtils.getRequestAttributes();

四、总结

本篇文章主要描述了笔者实际开发中遇到的采用异步线程导致请求头丢失的问题,本文详细介绍了问题发生的场景和解决方法。实际可以从以下几个方面考虑

  1. 显式传递 RequestAttributes:
  • 在异步任务执行前,获取当前线程的 RequestAttributes。
  • 在异步任务执行时,显式地设置 RequestAttributes。
  • 在异步任务执行后,重置 RequestAttributes,以避免影响后续任务。
  1. 使用自定义 CompletableFuture 实现:
  • 创建一个自定义的 CompletableFuture 实现,在任务执行前后自动设置和重置 RequestAttributes。
  • 这样可以确保每个异步任务都能访问到正确的请求上下文信息。
  1. 使用 ThreadLocal 传递上下文信息:
  • 利用 ThreadLocal 将请求上下文信息(如请求头等)封装在一个对象中。
  • 在异步任务执行前后,显式地传递并设置这个对象。
  1. 使用 AOP(面向切面编程):
  • 通过 AOP 在方法调用前后自动设置和重置 RequestAttributes。
  • 这样可以减少手动设置和重置 RequestAttributes 的代码量。
  1. 使用 Spring 提供的工具类:
  • 利用 Spring 提供的工具类(如 RequestContextHolder 和 RequestAttributes)来管理请求上下文。
  • 在异步任务执行前后,显式地设置和重置这些工具类的状态。

通过上述方法,可以确保异步线程在执行任务时能够正确访问到请求上下文信息,从而避免请求头丢失的问题。对您有帮助的话,请关注点赞支持一波哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值