问题

在微服务中,多线程异步+Feign调用会出现请求头丢失

解决
  1. 在主线程中先获取请求头参数
  2. 传入子线程中
  3. 由子线程将请求头参数设置到上下文中
  4. 最后在Feign转发处理中拿到子线程设置的上下文的请求头数据,转发到下游。

获取上下文请求参数工具类

@Slf4j
public class RequestContextUtil {

    /**
     * 获取请求头数据
     *
     * @return key->请求头名称 value->请求头值
     * @author zhengqingya
     * @date 2021/6/30 9:39 下午
     */
    public static Map<String, String> getHeaderMap() {
        Map<String, String> headerMap = Maps.newLinkedHashMap();
        try {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                return headerMap;
            }
            HttpServletRequest request = requestAttributes.getRequest();
            Enumeration<String> enumeration = request.getHeaderNames();
            while (enumeration.hasMoreElements()) {
                String key = enumeration.nextElement();
                String value = request.getHeader(key);
                headerMap.put(key, value);
            }
        } catch (Exception e) {
            log.error("《RequestContextUtil》 获取请求头参数失败:", e);
        }
        return headerMap;
    }

}

请求头上下文

@Slf4j
public class RequestHeaderHandler {

    public static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();

    public static void setHeaderMap(Map<String, String> headerMap) {
        THREAD_LOCAL.set(headerMap);
    }

    public static Map<String, String> getHeaderMap() {
        return THREAD_LOCAL.get();
    }

    public static void remove() {
        THREAD_LOCAL.remove();
    }

}

Feign转发处理rpc调用传参

 */
@Slf4j
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    @SneakyThrows
    public void apply(RequestTemplate requestTemplate) {
        log.debug("========================== ↓↓↓↓↓↓ 《FeignRequestInterceptor》 Start... ↓↓↓↓↓↓ ==========================");
        Map<String, String> threadHeaderNameMap = RequestHeaderHandler.getHeaderMap();
        if (!CollectionUtils.isEmpty(threadHeaderNameMap)) {
            threadHeaderNameMap.forEach((headerName, headerValue) -> {
                log.debug("《FeignRequestInterceptor》 多线程 headerName:【{}】 headerValue:【{}】", headerName, headerValue);
                requestTemplate.header(headerName, headerValue);
            });
        }
        Map<String, String> headerMap = RequestContextUtil.getHeaderMap();
        headerMap.forEach((headerName, headerValue) -> {
            log.debug("《FeignRequestInterceptor》 headerName:【{}】 headerValue:【{}】", headerName, headerValue);
            requestTemplate.header(headerName, headerValue);
        });
        log.debug("========================== ↑↑↑↑↑↑ 《FeignRequestInterceptor》 End... ↑↑↑↑↑↑ ==========================");
    }

}

使用案例

@Slf4j
@RestController
@RequestMapping("/web/api/demo/test")
@Api(tags = "测试api")
@AllArgsConstructor
public class RpcController extends BaseController {

    private SystemTaskThread systemTaskThread;

    @GetMapping("getContextUserId")
    @ApiOperation("rpc调用测试 - Async")
    public void getContextUserId() {
        Map<String, String> headerMap = RequestContextUtil.getHeaderMap();
        log.info("主线程请求头值: {}", headerMap.get("userId"));
        this.systemTaskThread.getRequestHeaderUserId(RequestContextUtil.getHeaderMap());
    }

}

@Slf4j
@Component
@AllArgsConstructor
public class SystemTaskThread {

    private ISystemClient systemClient;

    @SneakyThrows
    @Async(ThreadPoolConstant.SMALL_TOOLS_THREAD_POOL)
    public void getRequestHeaderUserId(Map<String, String> headerMap) {
        RequestHeaderHandler.setHeaderMap(headerMap);
        log.info("子线程请求头值: {}", this.systemClient.getRequestHeaderUserId());
    }

}

注:网上也有资料提到在主线程获取请求参数RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();传到子线程中,再重新赋值RequestContextHolder.setRequestAttributes(requestAttributes); 但是这种方式小编尝试无效,顺便记录在这里吧~

本文案例demo源码

https://gitee.com/zhengqingya/small-tools


今日分享语句:
在你失落时,千万不好失去对生活的信心;
在你受挫折时,千万不好埋怨上天的不公;
当你失败时,千万不好失去对成功的追求;
人,总要经受住各种考验。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐