簡直騷操作,ThreadLocal還能當快取用
阿新 • • 發佈:2020-08-10
## 背景說明
有朋友問我一個關於介面優化的問題,他的優化點很清晰,由於介面中呼叫了內部很多的 service 去組成了一個完成的業務功能。每個 service 中的邏輯都是獨立的,這樣就導致了很多查詢是重複的,看下圖你就明白了。
![](https://img2020.cnblogs.com/blog/1618095/202008/1618095-20200810125708144-1756122254.png)
## 上層查詢傳遞下去
對於這種場景最好的就是在上層將需要的資料查詢出來,然後傳遞到下層去消費。這樣就不用重複查詢了。
![](https://img2020.cnblogs.com/blog/1618095/202008/1618095-20200810125721848-1764760935.png)
如果開始寫程式碼的時候是這樣做的沒問題,但很多時候,之前寫的時候都是獨立的,或者複用的老邏輯,裡面就是有獨立的查詢。
如果要做優化就只能將老的方法過載一個,將需要的資訊直接傳遞過去。
```plain
public void xxx(int goodsId) {
Goods goods = goodsService.get(goodsId);
.....
}
public void xxx(Goods goods) {
.....
}
```
## 加快取
如果你的業務場景允許資料有一定延遲,那麼重複呼叫你可以直接通過加快取來解決。這樣的好處在於不會重複查詢資料庫,而是直接從快取中取資料。
更大的好處在於對於優化類的影響最小,原有的程式碼邏輯都不用改變,只需要在查詢的方法上加註解進行快取即可。
```plain
public void xxx(int goodsId) {
Goods goods = goodsService.get(goodsId);
.....
}
public void xxx(Goods goods) {
Goods goods = goodsService.get(goodsId);
.....
}
class GoodsService {
@Cached(expire = 10, timeUnit = TimeUnit.SECONDS)
public Goods get(int goodsId) {
return dao.findById(goodsId);
}
}
```
如果你的業務場景不允許有快取的話,上面這個方法就不能用了。那麼是不是還得改程式碼,將需要的資訊一層層往下傳遞呢?
## 自定義執行緒內的快取
我們總結下目前的問題:
1. 同一次請求內,多次相同的查詢獲取 RPC 等的呼叫。
2. 資料實時性要求高,不適合加快取,主要是加快取也不好設定過期時間,除非採用資料變更主動更新快取的方式。
3. 只需要在這一次請求裡快取即可,不影響其他地方。
4. 不想改動已有程式碼。
總結後發現這個場景適合用 ThreadLocal 來傳遞資料,對已有程式碼改動量最小,而且也只對當前執行緒生效,不會影響其他執行緒。
```plain
public void xxx(int goodsId) {
Goods goods = ThreadLocal.get();
if (goods == null) {
goods = goodsService.get(goodsId);
}
.....
}
```
上面程式碼就是使用了 ThreadLocal 來獲取資料,如果有的話就直接使用,不用去重新查詢,沒有的話就去查詢,不影響老邏輯。
雖然能實現效果,但是不太好,不夠優雅。也不夠通用,如果一次請求內要快取多種型別的資料怎麼處理? ThreadLocal 就不能儲存固定的型別。還有就是老的邏輯還是得改,加了個判斷。
下面介紹一種比較優雅的方式:
1. 自定義快取註解,加在查詢的方法上。
2. 定義切面切到加了快取註解的方法上,第一次獲取返回值存入 ThreadLocal。第二次直接從 ThreadLocal 中取值返回。
3. ThreadLocal 中儲存 Map,Key 為某方法的某一標識,這樣可以快取多種型別的結果。
4. 在 Filter 中將 ThreadLocal 進行 remove 操作,因為執行緒是複用的,使用完需要清空。
注意:ThreadLocal 不能跨執行緒,如果有跨執行緒需求,請使用阿里的 ttl 來裝飾。
![](https://img2020.cnblogs.com/blog/1618095/202008/1618095-20200810125736229-2058413761.png)
### 註解定義
```plain
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ThreadLocalCache {
/**
* 快取key,支援SPEL表示式
* @return
*/
String key() default "";
}
```
### 儲存定義
```plain
/**
* 執行緒內快取管理
*
* @作者 尹吉歡
* @時間 2020-07-12 10:47
*/
public class ThreadLocalCacheManager {
private static ThreadLocal