在某個項目中,需要使用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. 個人覺得這里稱呼"父子線程"並不妥當,因為線程池是系統啟動之后就已經創建好了的,算了,鑽牛角尖太沒勁了。