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資源避免內存泄漏的情況發(fā)生
     */
    @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());
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末孵稽,一起剝皮案震驚了整個濱河市许起,隨后出現(xiàn)的幾起案子十偶,更是在濱河造成了極大的恐慌菩鲜,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惦积,死亡現(xiàn)場離奇詭異接校,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門蛛勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹿寻,“玉大人,你說我怎么就攤上這事诽凌≌毖” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵侣诵,是天一觀的道長痢法。 經常有香客問我,道長杜顺,這世上最難降的妖魔是什么财搁? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮躬络,結果婚禮上尖奔,老公的妹妹穿的比我還像新娘。我一直安慰自己穷当,他們只是感情好提茁,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馁菜,像睡著了一般甘凭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上火邓,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天丹弱,我揣著相機與錄音,去河邊找鬼铲咨。 笑死躲胳,一個胖子當著我的面吹牛,可吹牛的內容都是我干的纤勒。 我是一名探鬼主播坯苹,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摇天!你這毒婦竟也來了粹湃?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤泉坐,失蹤者是張志新(化名)和其女友劉穎为鳄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腕让,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡孤钦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片偏形。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡静袖,死狀恐怖,靈堂內的尸體忽然破棺而出俊扭,到底是詐尸還是另有隱情队橙,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布萨惑,位于F島的核電站喘帚,受9級特大地震影響,放射性物質發(fā)生泄漏咒钟。R本人自食惡果不足惜吹由,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朱嘴。 院中可真熱鬧倾鲫,春花似錦、人聲如沸萍嬉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壤追。三九已至磕道,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間行冰,已是汗流浹背溺蕉。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悼做,地道東北人疯特。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像肛走,于是被迫代替她去往敵國和親漓雅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容