91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringCache如何實現請求級別緩存

發布時間:2022-03-05 10:16:07 來源:億速云 閱讀:393 作者:小新 欄目:web開發

這篇文章將為大家詳細講解有關SpringCache如何實現請求級別緩存,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

方案分析

要將數據緩存在一次請求周期內,那我們先得區分是什么環境下的請求,以分析我們如何存儲數據。

1. Web

Web環境下的有個絕佳的數據存儲位置 HttpServletRequest的Attribute 。調用setAttribute和getAttribute方法就能輕易地將我們的數據用key-value的形式存儲在請求上,而且每次請求都自動擁有一個干凈的Request。想要獲取到HttpServletRequest 也非常簡單,在web請求中隨時隨地調用((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest() 即可。

2. RPC框架

我司所使用的rpc框架是基于finagle自研的,對外提供服務時使用線程池進行處理請求,即對于一次完整的請求,會使用同一個線程進行處理。首先想到的辦法還是改動這個rpc框架服務端,增加一個可以對外暴露的、可以key-value存儲的請求上下文。為了能在方便的地方獲取到這個請求上下文,得將其存儲在ThreadLocal中。

綜合這兩種環境考慮,我們最好還是實現一個統一的方案以減少維護和開發成本。Spring的RequestContextHolder.getRequestAttributes()其實也是使用ThreadLocal來實現的,那我們可以統一將數據存到ThreadLocal<Map<Object,Object>>,自己來維護緩存的清理。

存儲位置有了,接下來實現SpringCache思路就比較清晰了。

實現SpringCache

要實現SpringCache需要一個CacheManager,接口定義如下

xxxxxxxxxx
public interface CacheManager {
           Cache getCache(String name);
           Collection<String> getCacheNames();
}

可以看到其實只需要實現Cache接口就行了。 在上一篇文章中提到的SimpleCacheManager,它的Cache實現ConcurrentMapCache內部的存儲是依賴ConcurrentMap<Object, Object>。我們的實現跟它非常類似,最主要的不同是我們需要使用ThreadLocal<Map<Object, Object>> 下面給出幾處關鍵的實現,其他部分簡單看下ConcurrentMapCache就能明白。

1 extends  

我們選擇不直接繼承Cache而是AbstractValueAdaptingCache,其被大多數緩存實現所繼承,它的作用主要是包裝value值以區分是沒有命中緩存還是緩存的null值。

2 store

xxxxxxxxxx
private final ThreadLocal<Map<Object, Object>> store = ThreadLocal.withInitial(() -> new HashMap<>(128));

我們的緩存數據存儲的地方,ThreadLocal保證緩存只會存在于這一個線程中。同時又因為只有一個線程能夠訪問,我們簡單地使用HashMap即可。

3 get

xxxxxxxxxx
public <T> T get(Object key, Callable<T> valueLoader) {
    return (T) fromStoreValue(this.store.get().computeIfAbsent(key, r -> {
        try {
            return toStoreValue(valueLoader.call());
        } catch (Throwable ex) {
            throw new ValueRetrievalException(key, valueLoader, ex);
        }
     }));
 }

至此我們即將大功告成,只差一個步驟,ThreadLocal的清理:使用AOP實現即可。

xxxxxxxxxx
    @After("bean(server)")
    public void clearThreadCache() {
        threadCacheManager.clear();
    }

記得將Cache的clear方法通過我們自定義的CacheManager暴露出來。同時也要確保切面能覆蓋每個請求的結束。

總結與擴展

從以上一個簡單的ThreadLocalCacheManager實現,我們對CacheManager又有了更多的理解。

同時可能也會有更多的疑問。

1. 我們實現的這些方法,從方法名和邏輯上看起來都很簡單,那他們是如何配合使用的?跟@Cacheable上的sync又有什么關系呢?

再回顧Spring Cache為我們提供的@Cacheable中的sync的注釋,它提到此功能的作用是: 同步化對被注解方法的調用,使得多個線程試圖調用此方法時,只有一個線程能夠成功調用,其他線程直接取這次調用的返回值。同時也提到這僅僅只是個hint,是否真的能成還是要看緩存提供者。

我們找到Spring Cache處理緩存調用的關鍵方法org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts) (spring-context-5.1.5.RELEASE)

經過分析,當sync = true 時, 只會調用如下代碼

xxxxxxxxxx
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))))

即我們上文實現的T get(Object key, Callable<T> valueLoader) 方法,回頭一看一切都清晰了。 只要我們的this.store.get().computeIfAbsent是同步的,那這個sync = true就起作用了。 當然我們這里使用的HashMap不支持,但是我們如果換成ConcurrentMap就能夠實現同步化的功能。另外簡單粗暴地讓方法同步也是可以的(RedisCache就是這樣做的)。

當sync = false時,會組合Cache中其他的方法進行緩存的處理。邏輯較為簡單清晰,自行閱讀源碼即可。

2. 用ThreadLocal嚴格來說實現的只是線程內的緩存,萬一一次請求中有異步操作怎么辦?

異步操作分兩種情況,直接創建線程或者使用線程池。對于第一種情況我們可以簡單地使用java.lang.InheritableThreadLocal 來替代ThreadLocal,創建的子進程會自然而然地共享父進程的InheritableThreadLocal;第二種情況就相對比較復雜了,建議可以參考 alibaba/transmittable-thread-local ,它實現了線程池下的ThreadLocal值傳遞功能。

關于“SpringCache如何實現請求級別緩存”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

焦作市| 宜兰县| 晋城| 乐山市| 阿瓦提县| 吐鲁番市| 敦煌市| 册亨县| 罗田县| 桦甸市| 阿拉善盟| 大厂| 集安市| 诸城市| 彭泽县| 尼玛县| 林口县| 东至县| 祁东县| 瓮安县| 普安县| 昌都县| 定边县| 宽城| 洛南县| 浙江省| 金寨县| 原阳县| 长葛市| 松原市| 萝北县| 秦皇岛市| 赤城县| 辽中县| 凤山市| 平武县| 怀宁县| 肃北| 卓尼县| 日喀则市| 逊克县|