使用 transmittable-thread-local 元件解決 ThreadLocal 父子執行緒資料傳遞問題
在某個專案中,需要使用mybatis-plus
多租戶功能以便資料隔離,前端將租戶id傳到後端,後端通過攔截器將該租戶id設定到ThreadLocal
以便後續使用,程式碼大體上如下所示:
ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
threadLocal.set(1);
我在Controller
層使用執行緒池取了租戶id,程式碼大體上如下所示:
ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(()->{ //獲取租戶id });
這時候出問題了,出現了有時候取得到有時候取不到租戶id的現象,但是經過若干次重試之後就能穩定獲取到租戶id;再次測試則發現如果前端傳了其它的租戶id,後端取得還是上一次獲取到的租戶id,這到底是為啥呢?
問題分析:首先,這裡使用了InheritableThreadLocal
為的就是實現父子執行緒傳值,傳了值也能取到,但是也不總是能取到,若干次之後就總是能取到了。看到這種現象,我們正常人的第一反應就是懷疑這裡有快取,每次使用的時候沒有,使用完了就快取起來,由於執行緒池在執行任務的時候並非總是使用同一條執行緒,當執行緒池中的核心執行緒全都快取完了,再請求就穩定不報錯了,然而有快取的原因所以就算這時候外部請求換了一個租戶id,執行緒池中的執行緒仍然使用的是老的租戶id,這也是快取最直接的體現
那怎麼解決該問題呢?
該問題產生的原因是InheritableThreadLocal
的bug,至於什麼bug,我也不清楚(笑),但是有解決方案,解決方案就是使用阿里的transmittable-thread-local
元件,github地址如下:https://github.com/alibaba/transmittable-thread-local
使用起來也非常簡單
首先,引入maven依賴:
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.12.0</version> </dependency>
1. 改變ThreadLocal的建立方式
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父執行緒中設定
context.set("value-set-in-parent");
// =====================================================
// 在子執行緒中可以讀取,值是"value-set-in-parent"
String value = context.get();
2.改變執行緒池建立方式
ExecutorService executorService = ...
// 額外的處理,生成修飾了的物件executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);
也就是說除了正常建立執行緒池之外,還要對該執行緒池做一個代理。
就這麼簡單,搞完之後父子執行緒傳資料就一切正常了。
ps. 個人覺得這裡稱呼"父子執行緒"並不妥當,因為執行緒池是系統啟動之後就已經建立好了的,算了,鑽牛角尖太沒勁了。