SpringBoot 多线程调用Feign丢失请求头解决办法

测试代码

    /**
     * 自定义 - 线程池
     */
    private static final ThreadPoolExecutor executorService = new ThreadPoolExecutor(50, 200,
            180L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(3000), new ThreadFactory() {

        final ThreadFactory defaultFactory = Executors.defaultThreadFactory();

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = defaultFactory.newThread(r);
            thread.setName("testRequest - " + thread.getName());
            return thread;
        }
    }, new ThreadPoolExecutor.CallerRunsPolicy());


    private String getToken() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }
        HttpServletRequest currentRequest = requestAttributes.getRequest();
        return currentRequest.getHeader("token");
    }

    @SneakyThrows
    public void test() {
        String token = getToken();
        log.info("测试1:主线程 mainThreadToken = {}", token);
        log.info("----------------------------------------");

        // 子线程1 Future
        Future<String> future1 = CompletableFuture.supplyAsync(() -> {
            // 子线程中获取token
            String childThreadToken = getToken();
            log.info("测试2:子线程 childThreadToken = {}", childThreadToken);
            return childThreadToken;
        }, executorService);
        future1.get();
        log.info("----------------------------------------");

        // 子线程2
        Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
            // 子线程中获取token
            String childThreadToken = getToken();
            log.info("测试3:子线程 childThreadToken = {}", childThreadToken);
        });
        log.info("----------------------------------------");

        // 子线程 嵌套 子线程
        Future<String> future2 = CompletableFuture.supplyAsync(() -> {
            Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
                // 子线程中获取token
                String childThreadToken = getToken();
                log.info("测试4:子线程 childThreadToken = {}", childThreadToken);
            });
            return null;
        }, executorService);
        future2.get();
        log.info("----------------------------------------");

        // 子线程 嵌套 子线程 传递 header 方式1
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        Future<String> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                RequestContextHolder.setRequestAttributes(attributes);
                Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
                    RequestContextHolder.setRequestAttributes(attributes);
                    // 子线程中获取token
                    String childThreadToken = getToken();
                    log.info("方式1:子线程 childThreadToken = {}", childThreadToken);
                });
                return null;
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }, executorService);
        future3.get();
        log.info("----------------------------------------");

        // 子线程 嵌套 子线程 传递 header 方式2
        Future<String> future4 = CompletableFuture.supplyAsync(() -> {
            try {
                Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
                    // 子线程中获取token
                    String childThreadToken = TokenInheritableThreadLocal.getToken();
                    log.info("方式2:子线程 childThreadToken = {}", childThreadToken);
                });
                return null;
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }, executorService);
        future4.get();
        log.info("----------------------------------------");
    }

测试结果

--------------------------------------------------------------------------------
测试1:主线程 http-nio-8083-exec-3,mainThreadToken = 123456
--------------------------------------------------------------------------------
测试2:子线程 testRequest - pool-1-thread-1,childThreadToken = null
--------------------------------------------------------------------------------
测试3:子线程 http-nio-8083-exec-3,childThreadToken = 123456
测试3:子线程 ForkJoinPool.commonPool-worker-2,childThreadToken = null
测试3:子线程 ForkJoinPool.commonPool-worker-9,childThreadToken = null
测试3:子线程 ForkJoinPool.commonPool-worker-2,childThreadToken = null
--------------------------------------------------------------------------------
测试4:子线程 testRequest - pool-1-thread-2,childThreadToken = null
测试4:子线程 ForkJoinPool.commonPool-worker-4,childThreadToken = null
测试4:子线程 ForkJoinPool.commonPool-worker-11,childThreadToken = null
测试4:子线程 ForkJoinPool.commonPool-worker-2,childThreadToken = null
--------------------------------------------------------------------------------
方式1:子线程 ForkJoinPool.commonPool-worker-2,childThreadToken = 123456
方式1:子线程 ForkJoinPool.commonPool-worker-4,childThreadToken = 123456
方式1:子线程 ForkJoinPool.commonPool-worker-11,childThreadToken = 123456
方式1:子线程 testRequest - pool-1-thread-3,childThreadToken = 123456
--------------------------------------------------------------------------------
方式2:子线程 testRequest - pool-1-thread-4,childThreadToken = 123456
方式2:子线程 ForkJoinPool.commonPool-worker-2,childThreadToken = 123456
方式2:子线程 ForkJoinPool.commonPool-worker-11,childThreadToken = 123456
方式2:子线程 ForkJoinPool.commonPool-worker-4,childThreadToken = 123456
--------------------------------------------------------------------------------

分析

  1. parallelStream() 也会导致请求头丢失,没丢失的是其中一个并行的主线程 http-nio-8083-exec-3
  2. 通过RequestContextHolder.setRequestAttributes(attributes); 可以透传header,但是麻烦
  3. InheritableThreadLocal 可以绑定父线程的变量,子线程也可以获取,使用方便

最佳解决方案

  • TokenInheritableThreadLocal
public class TokenInheritableThreadLocal {

    private TokenInheritableThreadLocal() {
    }

    public static final InheritableThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal() {
        @Override
        protected String initialValue() {
            return null;
        }
    };

    public static String getToken() {
        return THREAD_LOCAL.get();
    }

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

}
  • TokenInterceptor
public class TokenInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        TokenInheritableThreadLocal.THREAD_LOCAL.set(request.getHeader("token"));
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * 本次调用结束了,就可以释放threadLocal资源避免内存泄漏的情况发生
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        TokenInheritableThreadLocal.THREAD_LOCAL.remove();
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

}
  • CommonWebAppConfigurer
@Configuration
public class CommonWebAppConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TokenInterceptor());
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容