JetCache源碼解析——API實現(持續更新中……)

在JetCache中不僅可以通過在類和接口的函數上使用注解@Cached、@CacheUpdate和@CacheInvalidate等實現緩存加載、更新和刪除操作,也支持通過調用API接口的形式來實現緩存的加載、更新和刪除操作。

緩存接口

緩存接口的定義如下:

/*** 緩存接口,支持空值。*/
public interface Cache<K, V> extends Closeable {/*** 從緩存中獲取一個條目。* <p>如果緩存的構造器指定了一個{@link CacheLoader},并且緩存中沒有關聯,* 它會嘗試加載該條目。</p>* <p>如果在緩存訪問過程中發生錯誤,方法會返回null而不是拋出異常。</p>* @param key 要返回其關聯值的鍵* @return 與指定鍵關聯的值。null可能表示:<ul>*     <li>條目不存在或已過期</li>*     <li>條目的值為null</li>*     <li>在緩存訪問過程中發生錯誤(不拋出異常)</li>* </ul>* @throws CacheInvokeException 僅當加載器拋出異常時* @see CacheLoader* @see #GET(Object)*/default V get(K key) throws CacheInvokeException {CacheGetResult<V> result = GET(key);if (result.isSuccess()) {return result.getValue();} else {return null;}}/*** 從緩存中獲取一組條目,將它們作為與請求的鍵集相關聯的值的Map返回。* <p>如果緩存的構造器指定了一個{@link CacheLoader},并且緩存中沒有關聯,* 它會嘗試加載條目。</p>* <p>如果在緩存訪問過程中發生錯誤,方法不會拋出異常。</p>* @param keys 要返回其關聯值的鍵集合。* @return 為給定鍵找到的條目的映射。在緩存中未找到的鍵不包含在返回的映射中。* @throws CacheInvokeException 僅當加載器拋出異常時* @see CacheLoader* @see #GET_ALL(Set)*/default Map<K, V> getAll(Set<? extends K> keys) throws CacheInvokeException {MultiGetResult<K, V> cacheGetResults = GET_ALL(keys);return cacheGetResults.unwrapValues();}/*** 將指定的值與指定的鍵在緩存中關聯起來。* <p>如果在緩存訪問過程中發生錯誤,方法不會拋出異常。</p>* <p>如果實現支持異步操作,此方法的緩存操作為異步。</p>* @param key 與指定值關聯的鍵* @param value 要與指定鍵關聯的值* @see #PUT(Object, Object)*/default void put(K key, V value) {PUT(key, value);}/*** 將指定映射中的所有條目復制到緩存中。* <p>如果在緩存訪問過程中發生錯誤,方法不會拋出異常。</p>* <p>如果實現支持異步操作,此方法的緩存操作為異步。</p>* @param map 要存儲在此緩存中的映射。* @see #PUT_ALL(Map)*/default void putAll(Map<? extends K, ? extends V> map) {PUT_ALL(map);}/*** 將指定映射中的所有條目復制到緩存中。* <p>如果在訪問緩存時發生錯誤,該方法不會拋出異常。</p>* @param map 要存儲在緩存中的映射。* @param expireAfterWrite KV關聯的TTL(生存時間)* @param timeUnit expireAfterWrite的時間單位* @see #PUT_ALL(Map, long, TimeUnit)*/default void putAll(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {PUT_ALL(map, expireAfterWrite, timeUnit);}/*** 原子地將指定的鍵與給定的值關聯,如果它還沒有與一個值關聯的話。* <p>如果在緩存訪問過程中發生錯誤,方法不會拋出異常。</p>* <p>{@link MultiLevelCache} 不支持此方法。</p>* @param key 要與指定的值關聯的鍵* @param value 要與指定的鍵關聯的值* @return 如果設置了值,則為true;如果KV關聯在緩存中不存在,或在緩存訪問過程中發生錯誤,則為false。* @see #PUT_IF_ABSENT(Object, Object, long, TimeUnit)*/default boolean putIfAbsent(K key, V value) {CacheResult result = PUT_IF_ABSENT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);return result.getResultCode() == CacheResultCode.SUCCESS;}/*** 如果存在,則從緩存中移除指定鍵的映射。* <p>如果在緩存訪問過程中發生錯誤,方法不會拋出異常。</p>* @param key 要從緩存中移除映射的鍵* @return 如果鍵成功移除,則為true;如果KV關聯在緩存中不存在,或在緩存訪問過程中發生錯誤,則為false。* @see #REMOVE(Object)*/default boolean remove(K key) {return REMOVE(key).isSuccess();}/*** 移除指定鍵的條目。* <p>如果在緩存訪問過程中發生錯誤,方法不會拋出異常。</p>* <p>如果實現支持異步操作,此方法的緩存操作是異步的。</p>* @param keys 要移除的鍵* @see #REMOVE_ALL(Set)*/default void removeAll(Set<? extends K> keys) {REMOVE_ALL(keys);}/*** 如果與給定鍵關聯的有值,則返回該值;否則使用加載器加載值并返回,然后更新緩存。* @param key 鍵* @param loader 值加載器* @return 與鍵關聯的值* @see CacheConfig#isCacheNullValue()*/default V computeIfAbsent(K key, Function<K, V> loader) {return computeIfAbsent(key, loader, config().isCacheNullValue());}/*** 如果與給定鍵關聯的有值,則返回該值;否則使用加載器加載值并返回,然后根據參數決定是否更新緩存。* @param key 鍵* @param loader 值加載器* @param cacheNullWhenLoaderReturnNull 當加載器返回null時,是否將null值放入緩存* @return 與鍵關聯的值*/V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull);/*** 如果與給定鍵關聯的有值,則返回該值;否則使用加載器加載值并返回,然后根據參數決定是否更新緩存,并設置過期時間。* @param key 鍵* @param loader 值加載器* @param cacheNullWhenLoaderReturnNull 當加載器返回null時,是否將null值放入緩存* @param expireAfterWrite 緩存項的TTL(生存時間)* @param timeUnit expireAfterWrite的時間單位* @return 與鍵關聯的值*/V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull, long expireAfterWrite, TimeUnit timeUnit);}

上面的方法是對緩存的增刪改查操作,代碼邏輯相對比較簡單,默認是直接調用Cache接口中的GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法來實現的。其中computeIfAbsent方法是用于解決緩存穿透的問題,即當緩存中沒有對應的數據時,需要調用指定的loader獲取相應的數據并寫入到緩存中,跟Map中computeIfAbsent方法基本上原理是相似的,即如果緩存中key不存在會調用loader獲取緩存的值,并寫入到緩存中。

在上面的接口定義中,我們注意到JetCache缺少批量加載緩存的功能,無論是JetCache的注解亦或是API接口都不支持,我們后續會專門增加一個章節用于介紹如何實現緩存的批量加載的功能。當然,JetCache也不支持根據緩存的鍵或值自定義緩存有效期的能力,我們在后面都會介紹如何進行擴展。

只需要實現GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法就能夠實現緩存的增刪改查操作。對應的源碼如下:

/*** 將指定映射中的所有條目復制到緩存中。* <p>如果實現支持異步操作,調用此方法后緩存訪問可能并未完成。* 可以通過調用結果的getResultCode()/isSuccess()/getMessage()方法進行阻塞等待直到緩存操作完成。* 調用結果的future()方法將獲取用于異步編程的CompletionStage實例。</p>* @param map 要存儲在緩存中的映射。* @return 操作結果*/
default CacheResult PUT_ALL(Map<? extends K, ? extends V> map) {if (map == null) {return CacheResult.FAIL_ILLEGAL_ARGUMENT;}return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
}/*** 將指定映射中的所有條目復制到緩存中。* <p>如果實現支持異步操作,調用此方法后緩存訪問可能并未完成。* 可以通過調用結果的getResultCode()/isSuccess()/getMessage()方法進行阻塞等待直到緩存操作完成。* 調用結果的future()方法將獲取用于異步編程的CompletionStage實例。</p>* @param map 要存儲在緩存中的映射。* @param expireAfterWrite KV關聯的TTL(生存時間)* @param timeUnit expireAfterWrite的時間單位* @return 操作結果*/
CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);/*** 如果緩存中存在指定鍵的映射,則從緩存中移除該映射。* <p>如果實現支持異步操作,調用此方法后緩存訪問可能并未完成。* 可以通過調用結果的getResultCode()/isSuccess()/getMessage()方法進行阻塞等待直到緩存操作完成。* 調用結果的future()方法將獲取用于異步編程的CompletionStage實例。</p>* @param key 要從緩存中移除映射的鍵* @return 操作結果*/
CacheResult REMOVE(K key);/*** 移除指定鍵的映射。* <p>如果實現支持異步操作,調用此方法后緩存訪問可能并未完成。* 可以通過調用結果的getResultCode()/isSuccess()/getMessage()方法進行阻塞等待直到緩存操作完成。* 調用結果的future()方法將獲取用于異步編程的CompletionStage實例。</p>* @param keys 要移除的鍵集合* @return 操作結果*/
CacheResult REMOVE_ALL(Set<? extends K> keys);/*** 如果指定鍵尚未與值關聯,則將其與給定值關聯。* <p>如果實現支持異步操作,調用此方法后緩存訪問可能并未完成。* 可以通過調用結果的getResultCode()/isSuccess()/getMessage()方法進行阻塞等待直到緩存操作完成。* 調用結果的future()方法將獲取用于異步編程的CompletionStage實例。</p>* @param key 與指定值關聯的鍵* @param value 要與指定鍵關聯的值* @param expireAfterWrite KV關聯的TTL(生存時間)* @param timeUnit expireAfterWrite的時間單位* @return 如果指定鍵尚未與值關聯,則返回SUCCESS;如果指定鍵已與值關聯,則返回EXISTS;如果發生錯誤,則返回FAIL。*/
CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);

我們知道緩存分為進程內緩存和遠程緩存,進程內的緩存如LinkedHashMap和caffeine,遠程緩存如通過lettuce、redisson和spring-data-redis操作的redis,還有就是現在使用越來越少的MemeryCache。這些只需要實現上面的現GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法就可以了。

????????但是細節上不會那么簡單,我會在下面繼續進行介紹,但是介紹之前還是需要介紹一下鎖,因為實現使用緩存的場景肯定會涉及多線程,這樣就需要使用鎖來避免不同線程同時去對相同的鍵進行緩存的增刪改操作了。在Cache接口中默認實現了鎖的方法,源碼如下:

    /*** 嘗試使用緩存獲取指定鍵的獨占鎖,此方法不會阻塞。* 用法示例:* <pre>*   try(AutoReleaseLock lock = cache.tryLock("MyKey",100, TimeUnit.SECONDS)){*      if(lock != null){*          // 執行某些操作*      }*   }* </pre>* <p>{@link MultiLevelCache} 將使用最后一級緩存來支持此操作。</p>* @param key      鎖鍵* @param expire   鎖的過期時間* @param timeUnit 鎖的過期時間單位* @return 如果成功獲取鎖,則返回一個 AutoReleaseLock 實例(實現了 java.lang.AutoCloseable 接口)。*         如果嘗試失敗(表示另一個線程/進程/服務器已持有鎖),或在訪問緩存時發生錯誤,則返回 null。* @see #tryLockAndRun(Object, long, TimeUnit, Runnable)*/@SuppressWarnings("unchecked")default AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit) {if (key == null) {return null;}// 生成唯一的UUID作為鎖標識final String uuid = UUID.randomUUID().toString();// 計算鎖的過期時間戳final long expireTimestamp = System.currentTimeMillis() + timeUnit.toMillis(expire);// 獲取緩存配置final CacheConfig config = config();// 定義一個AutoReleaseLock,它包含解鎖邏輯AutoReleaseLock lock = () -> {int unlockCount = 0;// 嘗試解鎖次數while (unlockCount++ < config.getTryLockUnlockCount()) {// 如果鎖未過期,則嘗試解鎖if(System.currentTimeMillis() < expireTimestamp) {CacheResult unlockResult = REMOVE(key);// 解鎖結果處理if (unlockResult.getResultCode() == CacheResultCode.FAIL|| unlockResult.getResultCode() == CacheResultCode.PART_SUCCESS) {logger.info("[tryLock] [{} of {}] [{}] unlock failed. Key={}, msg = {}",unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getMessage());// 重試解鎖} else if (unlockResult.isSuccess()) {logger.debug("[tryLock] [{} of {}] [{}] successfully release the lock. Key={}",unlockCount, config.getTryLockUnlockCount(), uuid, key);return;} else {logger.warn("[tryLock] [{} of {}] [{}] unexpected unlock result: Key={}, result={}",unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getResultCode());return;}} else {// 鎖已過期logger.info("[tryLock] [{} of {}] [{}] lock already expired: Key={}",unlockCount, config.getTryLockUnlockCount(), uuid, key);return;}}};int lockCount = 0;Cache cache = this;// 嘗試加鎖次數while (lockCount++ < config.getTryLockLockCount()) {// 嘗試添加鎖CacheResult lockResult = cache.PUT_IF_ABSENT(key, uuid, expire, timeUnit);// 加鎖結果處理if (lockResult.isSuccess()) {logger.debug("[tryLock] [{} of {}] [{}] successfully get a lock. Key={}",lockCount, config.getTryLockLockCount(), uuid, key);return lock;} else if (lockResult.getResultCode() == CacheResultCode.FAIL || lockResult.getResultCode() == CacheResultCode.PART_SUCCESS) {// 緩存訪問失敗時的處理邏輯logger.info("[tryLock] [{} of {}] [{}] cache access failed during get lock, will inquiry {} times. Key={}, msg={}",lockCount, config.getTryLockLockCount(), uuid,config.getTryLockInquiryCount(), key, lockResult.getMessage());int inquiryCount = 0;// 嘗試查詢次數while (inquiryCount++ < config.getTryLockInquiryCount()) {// 嘗試查詢鎖狀態CacheGetResult inquiryResult = cache.GET(key);// 查詢結果處理if (inquiryResult.isSuccess()) {if (uuid.equals(inquiryResult.getValue())) {// 成功獲得鎖logger.debug("[tryLock] [{} of {}] [{}] successfully get a lock after inquiry. Key={}",inquiryCount, config.getTryLockInquiryCount(), uuid, key);return lock;} else {// 不是鎖的所有者logger.debug("[tryLock] [{} of {}] [{}] not the owner of the lock, return null. Key={}",inquiryCount, config.getTryLockInquiryCount(), uuid, key);return null;}} else {logger.info("[tryLock] [{} of {}] [{}] inquiry failed. Key={}, msg={}",inquiryCount, config.getTryLockInquiryCount(), uuid, key, inquiryResult.getMessage());// 重試查詢}}} else {// 其他持有鎖logger.debug("[tryLock] [{} of {}] [{}] others holds the lock, return null. Key={}",lockCount, config.getTryLockLockCount(), uuid, key);return null;}}// 所有嘗試均未成功獲得鎖logger.debug("[tryLock] [{}] return null after {} attempts. Key={}", uuid, config.getTryLockLockCount(), key);return null;}/*** 嘗試以獨占方式執行一個操作。* <p>{@link MultiLevelCache} 將使用最后一級緩存來支持此操作。</p>* 用法示例:* <pre>* cache.tryLock("MyKey",100, TimeUnit.SECONDS),() -&gt; {*     // 執行某些操作* });* </pre>* @param key 鎖鍵* @param expire 鎖的過期時間* @param timeUnit 鎖的過期時間單位* @param action 需要執行的操作* @return 如果成功獲取鎖并執行了操作,則返回 true;否則返回 false。*/default boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action){try (AutoReleaseLock lock = tryLock(key, expire, timeUnit)) {if (lock != null) {action.run();return true;} else {return false;}}}

tryLock方法是嘗試獲取緩存鍵的鎖,其中key對應的是緩存的鍵,這樣可以保證即使在多線程條件下,同一個鍵在同一時間內只會獲取到一個鎖。

  1. 定義釋放鎖的方法,先判斷嘗試解鎖次數小于配置的最大解鎖次數,如果超過就不在嘗試解鎖。然后判斷鎖是否到期,如果鎖已到期就直接返回。最后調用REMOVE方法刪除鎖信息,如果刪除不成功就會進行重試,否則直接返回。
  2. 首先調用PUT_IF_ABSENT試圖在緩存中添加鎖標識(唯一的GUID),以及鎖的到期時間,該方法僅在鍵不存在時才會添加成功,否則會添加失敗。PUT_IF_ABSENT添加鎖不成功有兩種可能,即已經存在該鍵,或者是緩存中沒有該鍵,但是因為特殊原因(如網絡原因導致寫入Redis失敗)導致寫入鍵值失敗。如果緩存中存在該鍵,則獲取鎖失敗,返回null。否則會繼續嘗試獲取鎖信息。

tryLockAndRun方法實現了獲取鎖,執行指定的操作并釋放鎖,如果獲取鎖失敗就直接返回false。

AbstractCache

在JetCache中AbstractCache是一個抽象類,繼承自Cache,實現了GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法,源碼如下:

/*** 抽象緩存類,提供了緩存的基本實現,支持鍵值對的存取操作。該類是線程安全的。** @param <K> 鍵的類型* @param <V> 值的類型*/
public abstract class AbstractCache<K, V> implements Cache<K, V> {/*** 通知緩存事件監聽器。** @param e 緩存事件。*/public void notify(CacheEvent e) {List<CacheMonitor> monitors = config().getMonitors();for (CacheMonitor m : monitors) {m.afterOperation(e);}}/*** 獲取緩存中指定鍵的值。** @param key 鍵。* @return CacheGetResult<V> 獲取結果,包含值和操作狀態。*/@Overridepublic final CacheGetResult<V> GET(K key) {long t = System.currentTimeMillis();CacheGetResult<V> result;// 對于null鍵,直接返回錯誤結果。if (key == null) {result = new CacheGetResult<V>(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null);} else {result = do_GET(key);}// 異步觸發獲取事件的通知。result.future().thenRun(() -> {CacheGetEvent event = new CacheGetEvent(this, System.currentTimeMillis() - t, key, result);notify(event);});return result;}/*** 實際獲取緩存值的邏輯。** @param key 鍵。* @return CacheGetResult<V> 獲取結果,包含值和操作狀態。*/protected abstract CacheGetResult<V> do_GET(K key);/*** 批量獲取緩存中多個鍵對應的值。** @param keys 鍵的集合。* @return MultiGetResult<K, V> 批量獲取結果,包含值的映射和操作狀態。*/@Overridepublic final MultiGetResult<K, V> GET_ALL(Set<? extends K> keys) {long t = System.currentTimeMillis();MultiGetResult<K, V> result;// 對于null鍵集合,直接返回錯誤結果。if (keys == null) {result = new MultiGetResult<>(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null);} else {result = do_GET_ALL(keys);}// 異步觸發批量獲取事件的通知。result.future().thenRun(() -> {CacheGetAllEvent event = new CacheGetAllEvent(this, System.currentTimeMillis() - t, keys, result);notify(event);});return result;}/*** 實際批量獲取緩存值的邏輯。** @param keys 鍵的集合。* @return MultiGetResult<K, V> 批量獲取結果,包含值的映射和操作狀態。*/protected abstract MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys);/*** 將鍵值對存儲到緩存中。* @param key 鍵,不能為null。* @param value 值。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 操作結果,包含操作是否成功等信息。*/
@Override
public final CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {long t = System.currentTimeMillis();CacheResult result;if (key == null) {result = CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result = do_PUT(key, value, expireAfterWrite, timeUnit);}// 在異步操作完成后觸發事件通知result.future().thenRun(() -> {CachePutEvent event = new CachePutEvent(this, System.currentTimeMillis() - t, key, value, result);notify(event);});return result;
}/*** 實際執行PUT操作的抽象方法。* @param key 鍵。* @param value 值。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 操作結果,包含操作是否成功等信息。*/
protected abstract CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);/*** 批量將鍵值對存儲到緩存中。* @param map 要存儲的鍵值對集合。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 操作結果,包含操作是否成功等信息。*/
@Override
public final CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {long t = System.currentTimeMillis();CacheResult result;if (map == null) {result = CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result = do_PUT_ALL(map, expireAfterWrite, timeUnit);}// 在異步操作完成后觸發事件通知result.future().thenRun(() -> {CachePutAllEvent event = new CachePutAllEvent(this, System.currentTimeMillis() - t, map, result);notify(event);});return result;
}/*** 實際執行批量PUT操作的抽象方法。* @param map 要存儲的鍵值對集合。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 操作結果,包含操作是否成功等信息。*/
protected abstract CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);/*** 從緩存中移除指定鍵的項。* @param key 要移除的鍵,不能為null。* @return 操作結果,包含操作是否成功等信息。*/
@Override
public final CacheResult REMOVE(K key) {long t = System.currentTimeMillis();CacheResult result;if (key == null) {result = CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result = do_REMOVE(key);}// 在異步操作完成后觸發事件通知result.future().thenRun(() -> {CacheRemoveEvent event = new CacheRemoveEvent(this, System.currentTimeMillis() - t, key, result);notify(event);});return result;
}/*** 實際執行移除操作的抽象方法。* @param key 要移除的鍵。* @return 操作結果,包含操作是否成功等信息。*/
protected abstract CacheResult do_REMOVE(K key);/*** 從緩存中移除指定鍵集合對應的項。* @param keys 要移除的鍵的集合,不能為null。* @return 操作結果,包含操作是否成功等信息。*/
@Override
public final CacheResult REMOVE_ALL(Set<? extends K> keys) {long t = System.currentTimeMillis();CacheResult result;if (keys == null) {result = CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result = do_REMOVE_ALL(keys);}// 在異步操作完成后觸發事件通知result.future().thenRun(() -> {CacheRemoveAllEvent event = new CacheRemoveAllEvent(this, System.currentTimeMillis() - t, keys, result);notify(event);});return result;
}/*** 實際執行批量移除操作的抽象方法。* @param keys 要移除的鍵的集合。* @return 操作結果,包含操作是否成功等信息。*/
protected abstract CacheResult do_REMOVE_ALL(Set<? extends K> keys);/*** 如果指定的鍵在緩存中不存在,則將其添加。* @param key 鍵,不能為null。* @param value 值。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 操作結果,包含操作是否成功等信息。*/
@Override
public final CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {long t = System.currentTimeMillis();CacheResult result;if (key == null) {result = CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result = do_PUT_IF_ABSENT(key, value, expireAfterWrite, timeUnit);}// 在異步操作完成后觸發事件通知result.future().thenRun(() -> {CachePutEvent event = new CachePutEvent(this, System.currentTimeMillis() - t, key, value, result);notify(event);});return result;
}/*** 實際執行PUT_IF_ABSENT操作的抽象方法。* @param key 鍵。* @param value 值。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 操作結果,包含操作是否成功等信息。*/
protected abstract CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);

在AbstractCache類中,GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法主要是先判斷鍵是否有效,然后調用do_GET、do_GET_ALL、do_PUT、do_PUT_ALL、do_REMOVE、do_REMOVE_ALL等方法對緩存進行增刪改查,最后使用觀察者模式發布緩存的操作事件。

上面的代碼邏輯還是相對比較簡單的就不做太多的講解,接下來,我們著重介紹AbstractCache類中緩存加載的代碼邏輯,源碼如下:

// 使用ConcurrentHashMap來存儲加載器鎖,以支持并發控制。private volatile ConcurrentHashMap<Object, LoaderLock> loaderMap;// 標記緩存是否已關閉。protected volatile boolean closed;// 用于初始化loaderMap的互斥鎖,確保線程安全。private static final ReentrantLock reentrantLock = new ReentrantLock();/*** 初始化或獲取loaderMap。** @return ConcurrentHashMap<Object, LoaderLock> 返回loaderMap實例。*/ConcurrentHashMap<Object, LoaderLock> initOrGetLoaderMap() {if (loaderMap == null) {reentrantLock.lock();try {if (loaderMap == null) {loaderMap = new ConcurrentHashMap<>();}}finally {reentrantLock.unlock();}}return loaderMap;}/*** 如果給定的鍵在緩存中不存在,則使用給定的加載器函數來計算值,并將結果放入緩存。* * @param key 鍵,用于在緩存中查找值。* @param loader 加載器函數,用于在緩存中找不到鍵對應的值時計算值。* @param cacheNullWhenLoaderReturnNull 當加載器返回null時,是否將null緩存起來。* @return 緩存中鍵對應的值,或者是通過加載器計算出的值。*/
@Override
public final V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull) {return computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,0, null, this);
}/*** 如果給定的鍵在緩存中不存在,并且希望設置過期時間,則使用給定的加載器函數來計算值,并將結果放入緩存。* * @param key 鍵,用于在緩存中查找值。* @param loader 加載器函數,用于在緩存中找不到鍵對應的值時計算值。* @param cacheNullWhenLoaderReturnNull 當加載器返回null時,是否將null緩存起來。* @param expireAfterWrite 緩存條目的寫入后過期時間。* @param timeUnit 緩存條目的過期時間單位。* @return 緩存中鍵對應的值,或者是通過加載器計算出的值。*/
@Override
public final V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,long expireAfterWrite, TimeUnit timeUnit) {return computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,expireAfterWrite, timeUnit, this);
}/*** 判斷是否需要更新緩存。* * @param loadedValue 加載器計算出的值。* @param cacheNullWhenLoaderReturnNull 當加載器返回null時,是否將null緩存起來。* @param loader 加載器函數。* @return 如果需要更新緩存,則返回true;否則返回false。*/
private static <K, V> boolean needUpdate(V loadedValue, boolean cacheNullWhenLoaderReturnNull, Function<K, V> loader) {if (loadedValue == null && !cacheNullWhenLoaderReturnNull) {return false;}if (loader instanceof CacheLoader && ((CacheLoader<K, V>) loader).vetoCacheUpdate()) {return false;}return true;
}/*** 實際執行computeIfAbsent邏輯的方法。* * @param key 鍵,用于在緩存中查找值。* @param loader 加載器函數,用于在緩存中找不到鍵對應的值時計算值。* @param cacheNullWhenLoaderReturnNull 當加載器返回null時,是否將null緩存起來。* @param expireAfterWrite 緩存條目的寫入后過期時間。* @param timeUnit 緩存條目的過期時間單位。* @param cache 緩存實例。* @return 緩存中鍵對應的值,或者是通過加載器計算出的值。*/
static <K, V> V computeIfAbsentImpl(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,long expireAfterWrite, TimeUnit timeUnit, Cache<K, V> cache) {AbstractCache<K, V> abstractCache = CacheUtil.getAbstractCache(cache);CacheLoader<K, V> newLoader = CacheUtil.createProxyLoader(cache, loader, abstractCache::notify);CacheGetResult<V> r;if (cache instanceof RefreshCache) {RefreshCache<K, V> refreshCache = ((RefreshCache<K, V>) cache);r = refreshCache.GET(key);refreshCache.addOrUpdateRefreshTask(key, newLoader);} else {r = cache.GET(key);}if (r.isSuccess()) {return r.getValue();} else {Consumer<V> cacheUpdater = (loadedValue) -> {if(needUpdate(loadedValue, cacheNullWhenLoaderReturnNull, newLoader)) {if (timeUnit != null) {cache.PUT(key, loadedValue, expireAfterWrite, timeUnit).waitForResult();} else {cache.PUT(key, loadedValue).waitForResult();}}};V loadedValue;if (cache.config().isCachePenetrationProtect()) {loadedValue = synchronizedLoad(cache.config(), abstractCache, key, newLoader, cacheUpdater);} else {loadedValue = newLoader.apply(key);cacheUpdater.accept(loadedValue);}return loadedValue;}
}/*** 使用同步方式從緩存中加載值。* * @param config 緩存配置。* @param abstractCache 緩存的抽象實現。* @param key 鍵。* @param newLoader 加載器函數。* @param cacheUpdater 緩存更新的消費者接口。* @return 加載的值。*/
static <K, V> V synchronizedLoad(CacheConfig config, AbstractCache<K,V> abstractCache,K key, Function<K, V> newLoader, Consumer<V> cacheUpdater) {ConcurrentHashMap<Object, LoaderLock> loaderMap = abstractCache.initOrGetLoaderMap();Object lockKey = buildLoaderLockKey(abstractCache, key);while (true) {boolean create[] = new boolean[1];LoaderLock ll = loaderMap.computeIfAbsent(lockKey, (unusedKey) -> {create[0] = true;LoaderLock loaderLock = new LoaderLock();loaderLock.signal = new CountDownLatch(1);loaderLock.loaderThread = Thread.currentThread();return loaderLock;});if (create[0] || ll.loaderThread == Thread.currentThread()) {try {CacheGetResult<V> getResult = abstractCache.GET(key);if (getResult.isSuccess()) {ll.success = true;ll.value = getResult.getValue();return getResult.getValue();} else {V loadedValue = newLoader.apply(key);ll.success = true;ll.value = loadedValue;cacheUpdater.accept(loadedValue);return loadedValue;}} finally {if (create[0]) {ll.signal.countDown();loaderMap.remove(lockKey);}}} else {try {Duration timeout = config.getPenetrationProtectTimeout();if (timeout == null) {ll.signal.await();} else {boolean ok = ll.signal.await(timeout.toMillis(), TimeUnit.MILLISECONDS);if(!ok) {logger.info("loader wait timeout:" + timeout);return newLoader.apply(key);}}} catch (InterruptedException e) {logger.warn("loader wait interrupted");return newLoader.apply(key);}if (ll.success) {return (V) ll.value;} else {continue;}}}
}/*** 構建加載鎖的鍵。* * @param c 緩存實例。* @param key 鍵。* @return 用于加載鎖的鍵。*/
private static Object buildLoaderLockKey(Cache c, Object key) {if (c instanceof AbstractEmbeddedCache) {return ((AbstractEmbeddedCache) c).buildKey(key);} else if (c instanceof AbstractExternalCache) {byte bytes[] = ((AbstractExternalCache) c).buildKey(key);return ByteBuffer.wrap(bytes);} else if (c instanceof MultiLevelCache) {c = ((MultiLevelCache) c).caches()[0];return buildLoaderLockKey(c, key);} else if(c instanceof ProxyCache) {c = ((ProxyCache) c).getTargetCache();return buildLoaderLockKey(c, key);} else {throw new CacheException("impossible");}
}

這段Java代碼定義了一個名為computeIfAbsentImpl的靜態方法,該方法用于在給定的緩存中根據鍵獲取值,如果緩存中不存在該鍵對應的值,則使用提供的加載器函數加載值,并將加載的值放入緩存中。方法有多個參數,包括鍵、加載器函數、是否緩存null值、過期時間、時間單位和緩存對象。
該方法首先通過CacheUtil.getAbstractCache(cache)獲取緩存的抽象類對象,然后通過CacheUtil.createProxyLoader()創建一個代理加載器。接下來,根據緩存類型分別調用RefreshCache.GET()或cache.GET()方法獲取緩存值。如果獲取成功,則返回該值;否則,根據是否需要更新緩存以及是否設置了過期時間,調用cache.PUT()方法更新緩存。
如果緩存配置了穿透保護,則調用synchronizedLoad()方法進行加鎖加載。該方法使用ConcurrentHashMap維護一個加載器映射表,通過構建鎖鍵來實現細粒度鎖。在加鎖過程中,如果當前線程是加載器線程,則直接進行加載并更新緩存;否則,等待加載完成或超時后返回加載的值。
總之,這段代碼實現了根據鍵加載或獲取緩存值的功能,并提供了緩存穿透保護的機制。

內存緩存

進程內的緩存框架支持LinkedHashMap和caffeine,在實際開發過程中一般推薦使用caffeine,好處網上一大堆,這里不做贅述。

AbstractEmbeddedCache

抽象類AbstractEmbeddedCache繼承自AbstractCache,作為內存緩存的父類,其實現了常見內存緩存類的共通實現,由于是在內存緩存,不需要對緩存的數據進行序列化處理,相對處理速度自然相對會更快,序列化緩存鍵的操作已經在AbstractCache中實現,AbstractEmbeddedCache中就不會重復造輪子了。AbstractEmbeddedCache源碼如下:

/*** 抽象嵌入式緩存類,擴展自AbstractCache,提供緩存的內部映射和配置管理。* 需要由具體緩存實現類繼承并實現createAreaCache方法。** @param <K> 鍵的類型* @param <V> 值的類型*/
public abstract class AbstractEmbeddedCache<K, V> extends AbstractCache<K, V> {protected EmbeddedCacheConfig<K, V> config; // 緩存配置protected InnerMap innerMap; // 內部映射,用于緩存數據/*** 創建區域緩存,由子類具體實現。* * @return InnerMap 實例,表示特定緩存區域。*/protected abstract InnerMap createAreaCache();private final ReentrantLock lock = new ReentrantLock(); // 用于并發控制的鎖/*** 構造函數,初始化嵌入式緩存。** @param config 緩存配置,包含緩存的各種設置項。*/public AbstractEmbeddedCache(EmbeddedCacheConfig<K, V> config) {this.config = config;innerMap = createAreaCache();}/*** 獲取緩存配置。* * @return CacheConfig 緩存配置。*/@Overridepublic CacheConfig<K, V> config() {return config;}/*** 基于提供的鍵構建緩存鍵,如果配置了鍵轉換器,則會應用轉換。** @param key 原始鍵。* @return 轉換后的鍵,用于緩存存儲。*/public Object buildKey(K key) {Object newKey = key;Function<K, Object> keyConvertor = config.getKeyConvertor();if (keyConvertor != null) {newKey = keyConvertor.apply(key);}return newKey;}/*** 從緩存中獲取指定鍵的值。* * @param key 鍵。* @return CacheGetResult 包含獲取結果的對象,可能為不存在、已過期或其他狀態。*/@Overrideprotected CacheGetResult<V> do_GET(K key) {Object newKey = buildKey(key);CacheValueHolder<V> holder = (CacheValueHolder<V>) innerMap.getValue(newKey);return parseHolderResult(holder);}/*** 解析緩存值持有者結果,確定緩存狀態(存在、過期等)。** @param holder 緩存值持有者。* @return CacheGetResult 包含獲取結果的對象。*/protected CacheGetResult<V> parseHolderResult(CacheValueHolder<V> holder) {long now = System.currentTimeMillis();if (holder == null) {return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;} else if (now >= holder.getExpireTime()) {return CacheGetResult.EXPIRED_WITHOUT_MSG;} else {lock.lock();try{long accessTime = holder.getAccessTime();if (config.isExpireAfterAccess()) {long expireAfterAccess = config.getExpireAfterAccessInMillis();if (now >= accessTime + expireAfterAccess) {return CacheGetResult.EXPIRED_WITHOUT_MSG;}}holder.setAccessTime(now);}finally {lock.unlock();}return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);}}/*** 批量獲取緩存值。* * @param keys 鍵的集合。* @return MultiGetResult 包含批量獲取結果的對象。*/@Overrideprotected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {// 將原始鍵轉換為緩存鍵,并批量獲取值ArrayList<K> keyList = new ArrayList<K>(keys.size());ArrayList<Object> newKeyList = new ArrayList<Object>(keys.size());keys.stream().forEach((k) -> {Object newKey = buildKey(k);keyList.add(k);newKeyList.add(newKey);});Map<Object, CacheValueHolder<V>> innerResultMap = innerMap.getAllValues(newKeyList);Map<K, CacheGetResult<V>> resultMap = new HashMap<>();for (int i = 0; i < keyList.size(); i++) {K key = keyList.get(i);Object newKey = newKeyList.get(i);CacheValueHolder<V> holder = innerResultMap.get(newKey);resultMap.put(key, parseHolderResult(holder));}MultiGetResult<K, V> result = new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);return result;}/*** 向緩存中放入一個鍵值對,設置過期時間。* * @param key 鍵。* @param value 值。* @param expireAfterWrite 緩存項的寫入過期時間。* @param timeUnit 時間單位。* @return CacheResult 描述操作結果的對象。*/@Overrideprotected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {CacheValueHolder<V> cacheObject = new CacheValueHolder(value ,timeUnit.toMillis(expireAfterWrite));innerMap.putValue(buildKey(key), cacheObject);return CacheResult.SUCCESS_WITHOUT_MSG;}/*** 批量放入緩存項。* * @param map 鍵值對的映射。* @param expireAfterWrite 緩存項的寫入過期時間。* @param timeUnit 時間單位。* @return CacheResult 描述操作結果的對象。*/@Overrideprotected CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {HashMap newKeyMap = new HashMap();for (Map.Entry<? extends K, ? extends V> en : map.entrySet()) {CacheValueHolder<V> cacheObject = new CacheValueHolder(en.getValue(), timeUnit.toMillis(expireAfterWrite));newKeyMap.put(buildKey(en.getKey()), cacheObject);}innerMap.putAllValues(newKeyMap);final HashMap resultMap = new HashMap();map.keySet().forEach((k) -> resultMap.put(k, CacheResultCode.SUCCESS));return CacheResult.SUCCESS_WITHOUT_MSG;}/*** 從緩存中移除指定鍵的項。* * @param key 鍵。* @return CacheResult 描述操作結果的對象。*/@Overrideprotected CacheResult do_REMOVE(K key) {innerMap.removeValue(buildKey(key));return CacheResult.SUCCESS_WITHOUT_MSG;}/*** 批量移除緩存項。* * @param keys 鍵的集合。* @return CacheResult 描述操作結果的對象。*/@Overrideprotected CacheResult do_REMOVE_ALL(Set<? extends K> keys) {Set newKeys = keys.stream().map((key) -> buildKey(key)).collect(Collectors.toSet());innerMap.removeAllValues(newKeys);return CacheResult.SUCCESS_WITHOUT_MSG;}// 內部方法,用于徹底清除指定鍵的緩存項public void __removeAll(Set<? extends K> keys) {innerMap.removeAllValues(keys);}/*** 如果指定鍵的緩存項不存在,則放入該鍵值對,并設置過期時間。* * @param key 鍵。* @param value 值。* @param expireAfterWrite 緩存項的寫入過期時間。* @param timeUnit 時間單位。* @return CacheResult 描述操作結果的對象。*/@Overrideprotected CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {CacheValueHolder<V> cacheObject = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));if (innerMap.putIfAbsentValue(buildKey(key), cacheObject)) {return CacheResult.SUCCESS_WITHOUT_MSG;} else {return CacheResult.EXISTS_WITHOUT_MSG;}}
}

在上面的函數中,抽象函數createAreaCache()創建內部映射實例innerMap用于緩存數據,do_GET、do_GET_ALL、do_PUT、do_PUT_ALL、do_REMOVE、do_REMOVE_ALL等方法都是基于innerMap實例來實現緩存數據的增刪改查的。緩存數據時,會構建CacheValueHolder實例,用于保存緩存的數值、緩存創建時間和到期時間。之所以要緩存數據的過期時間,是便于框架在緩存數據過期后可以及時的將緩存數據從內存中清除出去。

InnerMap是JetCache專為內存緩存定義的通用緩存處理接口,源碼如下:

/*** InnerMap 接口定義了一套操作映射數據的方法,包括獲取值、存儲值、移除值等操作。*/
public interface InnerMap {/*** 通過鍵獲取對應的值。* * @param key 用于獲取值的鍵。* @return 鍵對應的值,如果不存在則返回 null。*/Object getValue(Object key);/*** 通過鍵的集合獲取所有對應的值。* * @param keys 鍵的集合。* @return 一個映射,包含給定鍵集合中每個鍵對應的值。*/Map getAllValues(Collection keys);/*** 為指定的鍵存儲一個值。* * @param key  要存儲值的鍵。* @param value 鍵對應的值。*/void putValue(Object key, Object value);/*** 從映射中批量添加鍵值對。* * @param map 包含要添加的鍵值對的映射。*/void putAllValues(Map map);/*** 移除指定鍵對應的值。* * @param key 要移除的鍵。* @return 如果成功移除,返回 true;如果鍵不存在,返回 false。*/boolean removeValue(Object key);/*** 如果指定鍵不存在,則添加該鍵對應的值。* * @param key   要添加值的鍵。* @param value 鍵對應的值。* @return 如果成功添加,返回 true;如果鍵已存在,返回 false。*/boolean putIfAbsentValue(Object key, Object value);/*** 移除指定鍵集合中所有鍵對應的值。* * @param keys 要移除的鍵的集合。*/void removeAllValues(Collection keys);
}

LinkedHashMapCache和CaffeineCache類繼承自AbstractEmbeddedCache,主要是實現抽象函數createAreaCache()創建內部映射InnerMap的實例。

LinkedHashMapCache

LinkedHashMapCache是一個基于 LinkedHashMap 的緩存實現類,繼承自 AbstractEmbeddedCache。
?它通過 LRUMap (最近最少使用)算法來管理緩存項,支持緩存過期清理。LinkedHashMapCache的源碼如下:

/*** LinkedHashMapCache是一個基于 LinkedHashMap 的緩存實現類,繼承自 AbstractEmbeddedCache。* 它通過 LRUMap (最近最少使用)算法來管理緩存項,支持緩存過期清理。** @param <K> 緩存鍵的類型* @param <V> 緩存值的類型*/
public class LinkedHashMapCache<K, V> extends AbstractEmbeddedCache<K, V> {private static Logger logger = LoggerFactory.getLogger(LinkedHashMapCache.class);/*** 構造函數,初始化緩存。** @param config 緩存配置,包含緩存大小等設置。*/public LinkedHashMapCache(EmbeddedCacheConfig<K, V> config) {super(config);addToCleaner(); // 將當前緩存實例添加到緩存清理器中,以便定期清理。}/*** 將當前緩存實例添加到緩存清理器。*/protected void addToCleaner() {Cleaner.add(this);}/*** 創建一個新的緩存區域,實際是一個 LRUMap 實例。** @return InnerMap 實例,用于存儲緩存項。*/@Overrideprotected InnerMap createAreaCache() {return new LRUMap(config.getLimit());}/*** 將當前緩存實例轉換為指定類型的對象。* * @param clazz 需要轉換成的目標類類型。* @return 轉換后的對象實例,如果無法轉換則拋出異常。* @throws IllegalArgumentException 如果指定的類不是 LinkedHashMap 類型,則拋出此異常。*/@Overridepublic <T> T unwrap(Class<T> clazz) {if (clazz.equals(LinkedHashMap.class)) {return (T) innerMap;}throw new IllegalArgumentException(clazz.getName());}/*** 清理已經過期的緩存項。*/public void cleanExpiredEntry() {((LRUMap) innerMap).cleanExpiredEntry();}/*** LRUMap 是一個基于 LinkedHashMap 實現的緩存映射,支持 LRU 算法和緩存過期機制。*/final class LRUMap extends LinkedHashMap implements InnerMap {private final int max; // 緩存最大容量private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 讀寫鎖,用于并發控制/*** 構造函數,初始化 LRUMap。** @param max 緩存的最大容量。*/public LRUMap(int max) {super((int) (max * 1.4f), 0.75f, true); // 調整 LinkedHashMap 的構造參數,以適應 LRU 策略this.max = max;}/*** 當緩存項數量超過最大容量時,移除最老的緩存項。** @param eldest 被考慮移除的最老緩存項。* @return 返回 true 如果最老緩存項被移除,否則返回 false。*/@Overrideprotected boolean removeEldestEntry(Map.Entry eldest) {return size() > max;}/*** 獲取指定鍵的緩存值,支持并發控制。** @param key 緩存項的鍵。* @return 返回對應鍵的緩存值,如果不存在則返回 null。*/@Overridepublic Object getValue(Object key) {Lock lock = readWriteLock.readLock();lock.lock();try{return get(key);}finally {lock.unlock();}}/*** 獲取多個鍵對應的緩存值,支持并發控制。** @param keys 緩存項的鍵集合。* @return 返回一個映射,包含所有指定鍵的緩存值,如果鍵不存在則對應值為 null。*/@Overridepublic Map getAllValues(Collection keys) {Lock lock = readWriteLock.readLock();lock.lock();Map values = new HashMap();try{for (Object key : keys) {Object v = get(key);if (v != null) {values.put(key, v);}}}finally {lock.unlock();}return values;}/*** 添加或更新一個緩存項,支持并發控制。** @param key 緩存項的鍵。* @param value 緩存項的值。*/@Overridepublic void putValue(Object key, Object value) {Lock lock = readWriteLock.writeLock();lock.lock();try{put(key, value);}finally {lock.unlock();}}/*** 批量添加或更新多個緩存項,支持并發控制。** @param map 包含要添加或更新的緩存項的鍵值對集合。*/@Overridepublic void putAllValues(Map map) {Lock lock = readWriteLock.writeLock();lock.lock();try{Set<Map.Entry> set = map.entrySet();for (Map.Entry en : set) {put(en.getKey(), en.getValue());}}finally {lock.unlock();}}/*** 移除指定鍵的緩存項,支持并發控制。** @param key 緩存項的鍵。* @return 如果緩存項被成功移除,返回 true,否則返回 false。*/@Overridepublic boolean removeValue(Object key) {Lock lock = readWriteLock.writeLock();lock.lock();try{return remove(key) != null;}finally {lock.unlock();}}/*** 移除多個鍵對應的緩存項,支持并發控制。** @param keys 緩存項的鍵集合。*/@Overridepublic void removeAllValues(Collection keys) {Lock lock = readWriteLock.writeLock();lock.lock();try{for (Object k : keys) {remove(k);}}finally {lock.unlock();}}/*** 如果指定鍵的緩存項不存在或已過期,則添加新的緩存項,支持并發控制。** @param key 緩存項的鍵。* @param value 緩存項的值。* @return 如果新的緩存項被成功添加,則返回 true,否則返回 false。*/@Override@SuppressWarnings("unchecked")public boolean putIfAbsentValue(Object key, Object value) {Lock lock = readWriteLock.writeLock();lock.lock();try{CacheValueHolder h = (CacheValueHolder) get(key);if (h == null || parseHolderResult(h).getResultCode() == CacheResultCode.EXPIRED) {put(key, value);return true;} else {return false;}}finally {lock.unlock();}}}
}

LinkedHashMapCache創建的InnerMap實例類型是LRUMap,LRUMap繼承自LinkedHashMap類并實現了InnerMap接口,由于LinkedHashMap可以維護插入順序或訪問順序兩種有序性,其中訪問順序指put和get操作已存在的Entry時,會將Entry移動到雙向鏈表的表尾。

LRUMap重寫了父類LinkedHashMap的removeEldestEntry函數,當緩存項數量超過最大容量時,移除最老的緩存項。可以看一下父類LinkedHashMap的afterNodeInsertion函數,該函數在removeEldestEntry返回true時,會刪除鏈表的表頭。源碼如下:

    void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}}

由于LinkedHashMap的方法put和get操作已存在的Entry時,會將Entry移動到雙向鏈表的表尾。則最近最少使用的緩存項自然而然的就被移動到雙向鏈表的表頭了。從字面理解,afterNodeInsertion是指LinkedHashMap中節點插入的后置函數,在HashMap的源碼中,put、merge、compute和computeIfAbsent函數的最后會調用afterNodeInsertion函數。

現在看到的是,當向緩存LinkedHashMap中寫入緩存數據,且緩存項大于LinkedHashMap的最大容量時才會刪除最近最少使用的緩存數據。對于已經到期的緩存數據是在什么時候刪除的呢?我們注意到addToCleaner函數會將當前的LinkedHashMapCache添加到緩存清理器中,緩存清理器Cleaner的功能就是定時的將已經過期的緩存數據從內存中清除掉。Cleaner源碼如下:

class Cleaner {static LinkedList<WeakReference<LinkedHashMapCache>> linkedHashMapCaches = new LinkedList<>();private static final ReentrantLock reentrantLock = new  ReentrantLock();static {ScheduledExecutorService executorService = JetCacheExecutor.defaultExecutor();executorService.scheduleWithFixedDelay(() -> run(), 60, 60, TimeUnit.SECONDS);}static void add(LinkedHashMapCache cache) {reentrantLock.lock();try{linkedHashMapCaches.add(new WeakReference<>(cache));}finally {reentrantLock.unlock();}}static void run() {reentrantLock.lock();try{Iterator<WeakReference<LinkedHashMapCache>> it = linkedHashMapCaches.iterator();while (it.hasNext()) {WeakReference<LinkedHashMapCache> ref = it.next();LinkedHashMapCache c = ref.get();if (c == null) {it.remove();} else {c.cleanExpiredEntry();}}}finally {reentrantLock.unlock();}}}

Cleaner會每隔60秒鐘,調用所有LinkedHashMapCache實例的cleanExpiredEntry函數來清空已經到期的緩存數據。函數cleanExpiredEntry的源碼如下:

public void cleanExpiredEntry() {((LRUMap) innerMap).cleanExpiredEntry();}

上面的函數會調用LRUMap類的cleanExpiredEntry函數來清空已經到期的緩存數據,cleanExpiredEntry源碼如下:

/*** 清理已經過期的緩存項。*/void cleanExpiredEntry() {Lock lock = readWriteLock.writeLock();lock.lock();try{// 遍歷緩存項,移除所有過期的緩存。for (Iterator it = entrySet().iterator(); it.hasNext();) {Map.Entry en = (Map.Entry) it.next();Object value = en.getValue();if (value != null && value instanceof CacheValueHolder) {CacheValueHolder h = (CacheValueHolder) value;if (System.currentTimeMillis() >= h.getExpireTime()) {it.remove();}} else {// 如果緩存項為空或不是 CacheValueHolder 類型,則記錄錯誤。if (value == null) {logger.error("key " + en.getKey() + " is null");} else {logger.error("value of key " + en.getKey() + " is not a CacheValueHolder. type=" + value.getClass());}}}}finally {lock.unlock();}}

cleanExpiredEntry函數會輪詢當前LinkedHashMap實例中的所有緩存數據,然后將已經過期的數據移除。

關于LinkedHashMapCache的設計還存在問題,在發現緩存數據已到期時,并沒有及時的將其移除。由于間隔時間是60秒,在短時間內當有大量的緩存數據進行讀寫時,可能會存在部分讀頻繁的數據已經過期,而部分未到期,且讀寫不頻繁的數據就會被移到雙向列表的表頭,一旦緩存數據量超過最大容量就會把未到期的數據給清了。

CaffeineCache

基于Caffeine實現的緩存類,擴展自AbstractEmbeddedCache,提供了緩存管理的功能。源碼如下:

/*** 基于Caffeine實現的緩存類,擴展自AbstractEmbeddedCache,提供了緩存管理的功能。*/
public class CaffeineCache<K, V> extends AbstractEmbeddedCache<K, V> {// Caffeine緩存實例private com.github.benmanes.caffeine.cache.Cache cache;/*** 構造函數,初始化Caffeine緩存。* * @param config 緩存配置信息,包含緩存大小、過期時間等配置。*/public CaffeineCache(EmbeddedCacheConfig<K, V> config) {super(config);}/*** 將當前緩存實例轉換為指定類型的緩存接口。* * @param clazz 需要轉換的目標緩存接口的Class對象。* @return 轉換后的緩存實例,如果無法轉換則拋出IllegalArgumentException。* @throws IllegalArgumentException 當指定的類不是com.github.benmanes.caffeine.cache.Cache時拋出。*/@Overridepublic <T> T unwrap(Class<T> clazz) {if (clazz.equals(com.github.benmanes.caffeine.cache.Cache.class)) {return (T) cache;}throw new IllegalArgumentException(clazz.getName());}/*** 創建緩存區域,配置緩存的大小、過期策略等。* * @return 內部映射接口,提供了對緩存操作的方法。*/@Override@SuppressWarnings("unchecked")protected InnerMap createAreaCache() {// 基于配置初始化Caffeine緩存構建器Caffeine<Object, Object> builder = Caffeine.newBuilder();builder.maximumSize(config.getLimit()); // 設置緩存最大大小// 配置緩存過期策略final boolean isExpireAfterAccess = config.isExpireAfterAccess(); // 是否按訪問時間過期final long expireAfterAccess = config.getExpireAfterAccessInMillis(); // 訪問過期時間builder.expireAfter(new Expiry<Object, CacheValueHolder>() {// 計算過期時間的方法private long getRestTimeInNanos(CacheValueHolder value) {long now = System.currentTimeMillis();long ttl = value.getExpireTime() - now;if(isExpireAfterAccess){ttl = Math.min(ttl, expireAfterAccess);}return TimeUnit.MILLISECONDS.toNanos(ttl);}@Overridepublic long expireAfterCreate(Object key, CacheValueHolder value, long currentTime) {return getRestTimeInNanos(value);}@Overridepublic long expireAfterUpdate(Object key, CacheValueHolder value,long currentTime, long currentDuration) {return currentDuration;}@Overridepublic long expireAfterRead(Object key, CacheValueHolder value,long currentTime, long currentDuration) {return getRestTimeInNanos(value);}});// 構建并初始化緩存cache = builder.build();// 返回一個內部映射,提供了基本的緩存操作接口return new InnerMap() {@Overridepublic Object getValue(Object key) {return cache.getIfPresent(key);}@Overridepublic Map getAllValues(Collection keys) {return cache.getAllPresent(keys);}@Overridepublic void putValue(Object key, Object value) {cache.put(key, value);}@Overridepublic void putAllValues(Map map) {cache.putAll(map);}@Overridepublic boolean removeValue(Object key) {return cache.asMap().remove(key) != null;}@Overridepublic void removeAllValues(Collection keys) {cache.invalidateAll(keys);}@Overridepublic boolean putIfAbsentValue(Object key, Object value) {return cache.asMap().putIfAbsent(key, value) == null;}};}
}

CaffeineCache的代碼相對比較簡單,在JetCache中內存緩存推薦使用Caffeine,性能相對LinkedHashMap更快,官方的Caffeine性能測試結果如下:

上面的結果僅僅是Caffeine官方提供的參考,詳細可以到Caffeine官方上查看。

遠程緩存

由于MemoryCache逐漸被Redis所取代,且Redis相對更加通用,所以JetCache目前支持的遠程緩存是Redis。鑒于信創的需求,已經有多家國產公司開發出可以替代Redis的內存緩存系統,如阿里巴巴的Tair,東方通的TongRDS等,不確定JetCache后續會不會支持其他內存緩存系統。

JetCache通過引入lettuce、redisson和spring-data-redis中間件來實現Redis緩存的操作,如果要支持阿里巴巴的Tair,東方通的TongRDS等相對也比較簡單。

AbstractExternalCache

抽象外部緩存類,擴展自AbstractCache,提供了緩存配置、鍵的轉換和構建、以及配置檢查等功能。源碼如下:

/*** 抽象外部緩存類,擴展自AbstractCache,提供了緩存配置、鍵的轉換和構建、以及配置檢查等功能。* @param <K> 鍵的類型* @param <V> 值的類型*/
public abstract class AbstractExternalCache<K, V> extends AbstractCache<K, V> {private ExternalCacheConfig<K, V> config; // 緩存配置對象/*** 構造函數,初始化外部緩存配置。* @param config 外部緩存的配置,不可為null。*/public AbstractExternalCache(ExternalCacheConfig<K, V> config) {this.config = config;checkConfig(); // 檢查配置的合法性}/*** 配置檢查方法,確保必要的配置項已經設置。*/protected void checkConfig() {// 檢查值編碼器、解碼器和鍵前綴是否已設置,未設置則拋出異常if (config.getValueEncoder() == null) {throw new CacheConfigException("no value encoder");}if (config.getValueDecoder() == null) {throw new CacheConfigException("no value decoder");}if (config.getKeyPrefix() == null) {throw new CacheConfigException("keyPrefix is required");}}/*** 構建緩存鍵。* @param key 用戶提供的鍵,可能需要轉換。* @return 經過轉換和組合前綴后的緩存鍵字節數組。* @throws CacheException 如果構建過程發生異常。*/public byte[] buildKey(K key) {try {Object newKey = key; // 初始化為原始鍵if (config.getKeyConvertor() != null) {// 根據版本適配不同的鍵轉換邏輯if (config.getKeyConvertor() instanceof KeyConvertor) {if (!isPreservedKey(key)) {newKey = config.getKeyConvertor().apply(key);}} else {// 舊版本的鍵轉換處理if (key instanceof byte[]) {newKey = key;} else if (key instanceof String) {newKey = key;} else {newKey = config.getKeyConvertor().apply(key);}}}// 構建最終的鍵return ExternalKeyUtil.buildKeyAfterConvert(newKey, config.getKeyPrefix());} catch (IOException e) {throw new CacheException(e);}}/*** 判斷鍵是否被保留不進行轉換。* @param key 原始鍵。* @return 如果鍵是保留鍵(如鎖鍵或時間戳鍵),則返回true,否則返回false。*/private boolean isPreservedKey(Object key) {if (key instanceof byte[]) {byte[] keyBytes = (byte[]) key;// 判斷鍵是否以特定后綴結尾,決定是否轉換return endWith(keyBytes, RefreshCache.LOCK_KEY_SUFFIX)|| endWith(keyBytes, RefreshCache.TIMESTAMP_KEY_SUFFIX);}return false;}/*** 判斷字節數組是否以指定的后綴結尾。* @param key 待檢查的字節數組。* @param suffix 指定的后綴字節數組。* @return 如果key以suffix結尾,返回true,否則返回false。*/private boolean endWith(byte[] key, byte[] suffix) {int len = suffix.length;if (key.length < len) {return false;}int startPos = key.length - len;// 比較key的后綴與suffix是否相同for (int i = 0; i < len; i++) {if (key[startPos + i] != suffix[i]) {return false;}}return true;}}

RedissonCache

/*** 基于Redisson客戶端實現的緩存類,繼承自AbstractExternalCache,提供了緩存的基本操作。** @param <K> 鍵的類型* @param <V> 值的類型*/
public class RedissonCache<K, V> extends AbstractExternalCache<K, V> {private final RedissonClient client; // Redisson客戶端實例private final RedissonCacheConfig<K, V> config; // 緩存配置private final Function<Object, byte[]> valueEncoder; // 值編碼器private final Function<byte[], Object> valueDecoder; // 值解碼器/*** 構造函數,初始化Redisson緩存。** @param config 緩存配置,包含Redisson客戶端、值編碼器和值解碼器等配置項。*/public RedissonCache(final RedissonCacheConfig<K, V> config) {super(config);this.config = config;this.client = config.getRedissonClient();this.valueEncoder = config.getValueEncoder();this.valueDecoder = config.getValueDecoder();}/*** 獲取緩存鍵的字符串形式。** @param key 緩存的鍵。* @return 鍵的字符串形式。*/protected String getCacheKey(final K key) {final byte[] newKey = buildKey(key);return new String(newKey, StandardCharsets.UTF_8);}/*** 獲取緩存配置。** @return 緩存配置。*/@Overridepublic CacheConfig<K, V> config() {return this.config;}/*** 不支持unwrap操作。** @param clazz 需要轉換成的類類型。* @throws UnsupportedOperationException 永遠拋出此異常,表示不支持unwrap操作。*/@Overridepublic <T> T unwrap(final Class<T> clazz) {throw new UnsupportedOperationException("RedissonCache does not support unwrap");}/*** 獲取Redisson使用的Codec。** @return 返回ByteArrayCodec實例。*/private Codec getCodec() {return ByteArrayCodec.INSTANCE;}/*** 編碼緩存值。** @param holder 緩存值持有者。* @return 編碼后的值。*/private byte[] encoder(final CacheValueHolder<V> holder) {if (Objects.nonNull(holder)) {return valueEncoder.apply(holder);}return null;}/*** 解碼緩存值。** @param key 緩存的鍵。* @param data 緩存的數據。* @param counter 解碼嘗試的次數。* @return 解碼后的緩存值持有者。*/@SuppressWarnings({"unchecked"})private CacheValueHolder<V> decoder(final K key, final byte[] data, final int counter) {CacheValueHolder<V> holder = null;if (Objects.nonNull(data) && data.length > 0) {try {holder = (CacheValueHolder<V>) valueDecoder.apply(data);} catch (CacheEncodeException e) {holder = compatibleOldVal(key, data, counter + 1);if(Objects.isNull(holder)){logError("decoder", key, e);}} catch (Throwable e) {logError("decoder", key, e);}}return holder;}/*** 簡化版的解碼緩存值,不傳入嘗試次數。** @param key 緩存的鍵。* @param data 緩存的數據。* @return 解碼后的緩存值持有者。*/private CacheValueHolder<V> decoder(final K key, final byte[] data) {return decoder(key, data, 0);}/*** 兼容舊版本值的解碼。** @param key 緩存的鍵。* @param data 緩存的數據。* @param counter 解碼嘗試的次數。* @return 兼容舊版本后的緩存值持有者。*/private CacheValueHolder<V> compatibleOldVal(final K key, final byte[] data, final int counter) {if (Objects.nonNull(key) && Objects.nonNull(data) && data.length > 0 && counter <= 1) {try {final Codec codec = this.client.getConfig().getCodec();if (Objects.nonNull(codec)) {final Class<?> cls = ByteArrayCodec.class;if (codec.getClass() != cls) {final ByteBuf in = ByteBufAllocator.DEFAULT.buffer().writeBytes(data);final byte[] out = (byte[]) codec.getValueDecoder().decode(in, null);return decoder(key, out, counter);}}} catch (Throwable e) {logError("compatibleOldVal", key, e);}}return null;}/*** 獲取緩存項。** @param key 緩存的鍵。* @return 緩存獲取結果,包含緩存狀態和值(如果存在)。*/@Override@SuppressWarnings({"unchecked"})protected CacheGetResult<V> do_GET(final K key) {try {final RBucket<byte[]> rb = this.client.getBucket(getCacheKey(key), getCodec());final CacheValueHolder<V> holder = decoder(key, rb.get());if (Objects.nonNull(holder)) {final long now = System.currentTimeMillis(), expire = holder.getExpireTime();if (expire > 0 && now >= expire) {return CacheGetResult.EXPIRED_WITHOUT_MSG;}return new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);}return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;} catch (Throwable e) {logError("GET", key, e);return new CacheGetResult<>(e);}}/*** 批量獲取緩存項。** @param keys 緩存的鍵集合。* @return 緩存批量獲取結果,包含每個鍵的獲取狀態和值(如果存在)。*/@Override@SuppressWarnings({"unchecked"})protected MultiGetResult<K, V> do_GET_ALL(final Set<? extends K> keys) {try {final Map<K, CacheGetResult<V>> retMap = new HashMap<>(1 << 4);if (Objects.nonNull(keys) && !keys.isEmpty()) {final Map<K, String> keyMap = new HashMap<>(keys.size());keys.stream().filter(Objects::nonNull).forEach(k -> {final String key = getCacheKey(k);if (Objects.nonNull(key)) {keyMap.put(k, key);}});if (!keyMap.isEmpty()) {final Map<String, byte[]> kvMap = this.client.getBuckets(getCodec()).get(keyMap.values().toArray(new String[0]));final long now = System.currentTimeMillis();for (K k : keys) {final String key = keyMap.get(k);if (Objects.nonNull(key) && Objects.nonNull(kvMap)) {final CacheValueHolder<V> holder = decoder(k, kvMap.get(key));if (Objects.nonNull(holder)) {final long expire = holder.getExpireTime();final CacheGetResult<V> ret = (expire > 0 && now >= expire) ? CacheGetResult.EXPIRED_WITHOUT_MSG :new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);retMap.put(k, ret);continue;}}retMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);}}}return new MultiGetResult<>(CacheResultCode.SUCCESS, null, retMap);} catch (Throwable e) {logError("GET_ALL", "keys(" + (Objects.nonNull(keys) ? keys.size() : 0) + ")", e);return new MultiGetResult<>(e);}}/*** 添加緩存項。** @param key 緩存的鍵。* @param value 緩存的值。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 緩存操作結果。*/@Overrideprotected CacheResult do_PUT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {try {final CacheValueHolder<V> holder = new CacheValueHolder<>(value, timeUnit.toMillis(expireAfterWrite));this.client.getBucket(getCacheKey(key), getCodec()).set(encoder(holder), expireAfterWrite, timeUnit);return CacheGetResult.SUCCESS_WITHOUT_MSG;} catch (Throwable e) {logError("PUT", key, e);return new CacheResult(e);}}/*** 批量添加緩存項。** @param map 鍵值對映射。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 過期時間的單位。* @return 緩存操作結果。*/@Overrideprotected CacheResult do_PUT_ALL(final Map<? extends K, ? extends V> map, final long expireAfterWrite, final TimeUnit timeUnit) {try {if (Objects.nonNull(map) && !map.isEmpty()) {final long expire = timeUnit.toMillis(expireAfterWrite);final RBatch batch = this.client.createBatch();map.forEach((k, v) -> {final CacheValueHolder<V> holder = new CacheValueHolder<>(v, expire);batch.getBucket(getCacheKey(k), getCodec()).setAsync(encoder(holder), expireAfterWrite, timeUnit);});batch.execute();}return CacheResult.SUCCESS_WITHOUT_MSG;} catch (Throwable e) {logError("PUT_ALL", "map(" + map.size() + ")", e);return new CacheResult(e);}}/*** 刪除緩存項。** @param key 緩存的鍵。* @return 緩存操作結果。*/@Overrideprotected CacheResult do_REMOVE(final K key) {try {final boolean ret = this.client.getBucket(getCacheKey(key), getCodec()).delete();return ret ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.FAIL_WITHOUT_MSG;} catch (Throwable e) {logError("REMOVE", key, e);return new CacheResult(e);}}/*** 批量刪除緩存項。** @param keys 緩存的鍵集合。* @return 緩存操作結果。*/@Overrideprotected CacheResult do_REMOVE_ALL(final Set<? extends K> keys) {try {if (Objects.nonNull(keys) && !keys.isEmpty()) {final RBatch batch = this.client.createBatch();keys.forEach(key -> batch.getBucket(getCacheKey(key), getCodec()).deleteAsync());batch.execute();}return CacheResult.SUCCESS_WITHOUT_MSG;} catch (Throwable e) {logError("REMOVE_ALL", "keys(" + keys.size() + ")", e);return new CacheResult(e);}}/*** 在緩存中添加一個鍵值對,如果該鍵不存在。此操作等同于 PUT 操作,但僅當指定的鍵不存在時才執行。* 如果鍵已存在,則不進行任何操作,并返回表示操作失敗的 CacheResult。* * @param key 緩存中的鍵,不可為 null。* @param value 緩存中的值,不可為 null。* @param expireAfterWrite 緩存項的過期時間。* @param timeUnit 緩存項的過期時間單位。* @return 返回一個 CacheResult 對象,表示操作的結果。成功且鍵不存在時返回 SUCCESS_WITHOUT_MSG,鍵已存在時返回 EXISTS_WITHOUT_MSG,操作失敗時返回包含錯誤信息的 CacheResult。*/@Overrideprotected CacheResult do_PUT_IF_ABSENT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {try {// 將過期時間轉換為毫秒,并創建 CacheValueHolder 對象final Duration expire = Duration.ofMillis(timeUnit.toMillis(expireAfterWrite));final CacheValueHolder<V> holder = new CacheValueHolder<>(value, expire.toMillis());// 嘗試在緩存中設置鍵值對,如果鍵不存在,則設置成功final boolean success = this.client.getBucket(getCacheKey(key), getCodec()).setIfAbsent(encoder(holder), expire);// 根據操作結果返回相應的 CacheResultreturn success ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.EXISTS_WITHOUT_MSG;} catch (Throwable e) {// 記錄操作失敗的日志logError("PUT_IF_ABSENT", key, e);// 返回包含錯誤信息的 CacheResultreturn new CacheResult(e);}}

多級緩存

多級緩存是指內存緩存+遠程緩存,操作時先操作內存緩存,然后再操作遠程緩存。例如查詢緩存數據時,先從內存緩存中獲取數據,如果不存在才會從遠程緩存中獲取數據。增刪改緩存數據時,會同時修改內存緩存和遠程緩存的數據。

MultiLevelCache

MultiLevelCache類的屬性private Cache[] caches的第一個Cache實例是內存緩存實例,第二個Cache實例是遠程緩存實例。在緩存的增刪改查操作時,會分別對caches中的Cache實例進行增刪改查處理。源碼如下:

/*** 多級緩存類,繼承自AbstractCache,支持多級緩存策略。* * @param <K> 鍵的類型* @param <V> 值的類型*/
public class MultiLevelCache<K, V> extends AbstractCache<K, V> {private Cache[] caches; // 緩存數組private MultiLevelCacheConfig<K, V> config; // 多級緩存配置/*** 已棄用的構造函數,使用可變參數初始化多級緩存。* * @param caches 緩存實例數組* @throws CacheConfigException 配置異常*/@SuppressWarnings("unchecked")@Deprecatedpublic MultiLevelCache(Cache... caches) throws CacheConfigException {this.caches = caches;checkCaches();CacheConfig lastConfig = caches[caches.length - 1].config();config = new MultiLevelCacheConfig<>();config.setCaches(Arrays.asList(caches));config.setExpireAfterWriteInMillis(lastConfig.getExpireAfterWriteInMillis());config.setCacheNullValue(lastConfig.isCacheNullValue());}/*** 使用多級緩存配置構造函數。* * @param cacheConfig 多級緩存配置* @throws CacheConfigException 配置異常*/@SuppressWarnings("unchecked")public MultiLevelCache(MultiLevelCacheConfig<K, V> cacheConfig) throws CacheConfigException {this.config = cacheConfig;this.caches = cacheConfig.getCaches().toArray(new Cache[]{});checkCaches();}/*** 檢查緩存配置是否合法。*/private void checkCaches() {if (caches == null || caches.length == 0) {throw new IllegalArgumentException();}for (Cache c : caches) {if (c.config().getLoader() != null) {throw new CacheConfigException("Loader on sub cache is not allowed, set the loader into MultiLevelCache.");}}}/*** 獲取緩存數組。* * @return 緩存數組*/public Cache[] caches() {return caches;}/*** 獲取緩存配置。* * @return 多級緩存配置*/@Overridepublic MultiLevelCacheConfig<K, V> config() {return config;}/*** 存儲鍵值對到緩存中。* * @param key 鍵* @param value 值* @return 緩存操作結果*/@Overridepublic CacheResult PUT(K key, V value) {if (config.isUseExpireOfSubCache()) {return PUT(key, value, 0, null);} else {return PUT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);}}/*** 批量存儲鍵值對到緩存中。* * @param map 鍵值對映射* @return 緩存操作結果*/@Overridepublic CacheResult PUT_ALL(Map<? extends K, ? extends V> map) {if (config.isUseExpireOfSubCache()) {return PUT_ALL(map, 0, null);} else {return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);}}/*** 從緩存中獲取值。* * @param key 鍵* @return 值獲取結果*/@Overrideprotected CacheGetResult<V> do_GET(K key) {for (int i = 0; i < caches.length; i++) {Cache cache = caches[i];CacheGetResult result = cache.GET(key);if (result.isSuccess()) {CacheValueHolder<V> holder = unwrapHolder(result.getHolder());checkResultAndFillUpperCache(key, i, holder);return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);}}return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;}/*** 解包緩存值持有者。* * @param h 緩存值持有者* @return 解包后的緩存值持有者*/private CacheValueHolder<V> unwrapHolder(CacheValueHolder<V> h) {Objects.requireNonNull(h);if (h.getValue() instanceof CacheValueHolder) {return (CacheValueHolder<V>) h.getValue();} else {return h;}}/*** 檢查獲取結果,并填充上級緩存。* * @param key 鍵* @param i 緩存索引* @param h 緩存值持有者*/private void checkResultAndFillUpperCache(K key, int i, CacheValueHolder<V> h) {Objects.requireNonNull(h);long currentExpire = h.getExpireTime();long now = System.currentTimeMillis();if (now <= currentExpire) {if(config.isUseExpireOfSubCache()){PUT_caches(i, key, h.getValue(), 0, null);} else {long restTtl = currentExpire - now;if (restTtl > 0) {PUT_caches(i, key, h.getValue(), restTtl, TimeUnit.MILLISECONDS);}}}}/*** 批量獲取緩存值。* * @param keys 鍵集合* @return 緩存批量獲取結果*/@Overrideprotected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {HashMap<K, CacheGetResult<V>> resultMap = new HashMap<>();Set<K> restKeys = new HashSet<>(keys);for (int i = 0; i < caches.length; i++) {if (restKeys.size() == 0) {break;}Cache<K, CacheValueHolder<V>> c = caches[i];MultiGetResult<K, CacheValueHolder<V>> allResult = c.GET_ALL(restKeys);if (allResult.isSuccess() && allResult.getValues() != null) {for (Map.Entry<K, CacheGetResult<CacheValueHolder<V>>> en : allResult.getValues().entrySet()) {K key = en.getKey();CacheGetResult result = en.getValue();if (result.isSuccess()) {CacheValueHolder<V> holder = unwrapHolder(result.getHolder());checkResultAndFillUpperCache(key, i, holder);resultMap.put(key, new CacheGetResult(CacheResultCode.SUCCESS, null, holder));restKeys.remove(key);}}}}for (K k : restKeys) {resultMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);}return new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);}/*** 在指定緩存中存儲鍵值對。* * @param key 鍵* @param value 值* @param expire 過期時間* @param timeUnit 時間單位* @return 緩存操作結果*/@Overrideprotected CacheResult do_PUT(K key, V value, long expire, TimeUnit timeUnit) {return PUT_caches(caches.length, key, value, expire, timeUnit);}/*** 在所有緩存中批量存儲鍵值對。* * @param map 鍵值對映射* @param expire 過期時間* @param timeUnit 時間單位* @return 緩存操作結果*/@Overrideprotected CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expire, TimeUnit timeUnit) {CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);for (Cache c : caches) {CacheResult r;if(timeUnit == null) {r = c.PUT_ALL(map);} else {r = c.PUT_ALL(map, expire, timeUnit);}future = combine(future, r);}return new CacheResult(future);}/*** 在指定緩存層級存儲鍵值對。* * @param lastIndex 最后一個緩存索引* @param key 鍵* @param value 值* @param expire 過期時間* @param timeUnit 時間單位* @return 緩存操作結果*/private CacheResult PUT_caches(int lastIndex, K key, V value, long expire, TimeUnit timeUnit) {CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);for (int i = 0; i < lastIndex; i++) {Cache cache = caches[i];CacheResult r;if (timeUnit == null) {r = cache.PUT(key, value);} else {r = cache.PUT(key, value, expire, timeUnit);}future = combine(future, r);}return new CacheResult(future);}/*** 合并多個CompletableFuture結果。* * @param future 當前CompletableFuture* @param result 新的緩存結果* @return 合并后的CompletableFuture*/private CompletableFuture<ResultData> combine(CompletableFuture<ResultData> future, CacheResult result) {return future.thenCombine(result.future(), (d1, d2) -> {if (d1 == null) {return d2;}if (d1.getResultCode() != d2.getResultCode()) {return new ResultData(CacheResultCode.PART_SUCCESS, null, null);}return d1;});}/*** 從緩存中移除指定鍵的條目。* * @param key 鍵* @return 緩存操作結果*/@Overrideprotected CacheResult do_REMOVE(K key) {CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);for (Cache cache : caches) {CacheResult r = cache.REMOVE(key);future = combine(future, r);}return new CacheResult(future);}/*** 從緩存中移除指定鍵集合的條目。* * @param keys 鍵集合* @return 緩存操作結果*/@Overrideprotected CacheResult do_REMOVE_ALL(Set<? extends K> keys) {CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);for (Cache cache : caches) {CacheResult r = cache.REMOVE_ALL(keys);future = combine(future, r);}return new CacheResult(future);}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717966.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717966.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717966.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【計算機網絡】HTTPS 協議原理

https 一、HTTPS 是什么二、加密1. 加密概念2. 加密的原因3. 常見的加密方式&#xff08;1&#xff09;對稱加密&#xff08;2&#xff09;非對稱加密 三、數據摘要(數據指紋)四、HTTPS 的工作原理探究1. 只使用對稱加密2. 只使用非對稱加密3. 雙方都使用非對稱加密4. 非對稱加…

Linux:kubernetes(k8s)部署CNI網絡插件(4)

在上一章進行了node加入master Linux&#xff1a;kubernetes&#xff08;k8s&#xff09;node節點加入master主節點&#xff08;3&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/136420447?spm1001.2014.3001.5501 但是他們顯示還是沒準備好 看一下…

面試筆記系列五之MySql+Mybaits基礎知識點整理及常見面試題

目錄 Myibatis myibatis執行過程 mybatis的優缺點有哪些&#xff1f; mybatis和hibernate有什么區別&#xff1f; mybatis中#{}和${}的區別是什么&#xff1f; 簡述一下mybatis插件運行原理及開發流程&#xff1f;&#xff08;插件四大天王&#xff09; mybatis的mapper沒…

2.模擬問題——5.星期幾與字符串對應

輸入輸出示例 輸入&#xff1a; 9 October 2001 14 October 2001 輸出&#xff1a; Tuesday Sunday 【原題鏈接】 字符串處理 C風格的字符串 字符數組&#xff0c;以’\0‘結尾建議在輸入輸出語句中使用 C風格的字符串 #include <string> using namespace std;初始化…

「優選算法刷題」:最長回文子串

一、題目 給你一個字符串 s&#xff0c;找到 s 中最長的回文子串。 如果字符串的反序與原始字符串相同&#xff0c;則該字符串稱為回文字符串。 示例 1&#xff1a; 輸入&#xff1a;s "babad" 輸出&#xff1a;"bab" 解釋&#xff1a;"aba"…

【字符串】馬拉車(Manacher)算法

本篇文章參考&#xff1a;比較易懂的 Manacher&#xff08;馬拉車&#xff09;算法配圖詳解 馬拉車算法可以求出一個字符串中的最長回文子串&#xff0c;時間復雜度 O ( n ) O(n) O(n) 因為字符串長度的奇偶性&#xff0c;回文子串的中心可能是一個字符&#xff0c;也可能是…

uniapp聊天記錄本地存儲(詳細易懂)

目錄 目錄 1、通過websocket拿取數據 2、獲取聊天數據 3、聊天信息存儲 、更新 4、讀取聊天記錄 5、發送信息&#xff0c;信息獲取 6、最終效果 1.聊天信息的存儲格式 2、樣式效果 寫聊天項目&#xff0c;使用到了本地存儲。需要把聊天信息保存在本地&#xff0c;實時獲…

GPT對話知識庫——ARM-Cortex架構分為哪幾個系列?每個系列有幾種工作模式?各種工作模式之間的定義和區別?每種架構不同的特點和應用需求?

提問模型&#xff1a;GPT-4-TURBO-PREVIEW 提問時間&#xff1a;2024.03.02 1&#xff0c;問&#xff1a; Cortex-M系列有幾種工作模式 1&#xff0c;答&#xff1a; Cortex-M系列微控制器是ARM公司開發的一類低功耗、高性能的32位微處理器&#xff0c;廣泛應用于嵌入式系統中…

Centos7使用man查找命令時,報錯No manual entry for xxxx

Centos7使用man查找命令時&#xff0c;報錯No manual entry for xxxx 在Linux中使用man指令查找指令信息時&#xff0c;報No manual entry for xxxx。 比如使用man指令查找sleep3號手冊時&#xff0c;出現以下錯誤&#xff1a; 這是由于沒有安裝man-pages這個rpm包導致的&#…

掌握基本排序算法:冒泡、選擇、插入和快速排序

在計算機科學的世界里&#xff0c;排序是一項基本而重要的操作。無論是數據庫管理、搜索引擎&#xff0c;還是日常編程&#xff0c;高效的排序算法都是提高性能的關鍵。本文將介紹四種基本的排序算法&#xff1a;冒泡排序、選擇排序、插入排序和快速排序&#xff0c;并探討它們…

從0開始學習NEON(1)

1、前言 在上個博客中對NEON有了基礎的了解&#xff0c;本文將針對一個圖像下采樣的例子對NEON進行學習。 學習鏈接:CPU優化技術 - NEON 開發進階 上文鏈接:https://blog.csdn.net/weixin_42108183/article/details/136412104 2、第一個例子 現在有一張圖片&#xff0c;需…

獲取 Windows 通知中心彈窗通知內容(含工具漢化)

目錄 前言 技術原理概述 測試代碼和程序下載連接 本文出處鏈接&#xff1a;https://blog.csdn.net/qq_59075481/article/details/136440280。 前言 從 Windows 8.1 開始&#xff0c;Windows 通知現在以 Toast 而非 Balloon 形式顯示&#xff08; Bollon 通知其實現在是應用…

在ubuntu上安裝hadoop完分布式

準備工作 Xshell安裝包 Xftp7安裝包 虛擬機安裝包 Ubuntu鏡像源文件 Hadoop包 Java包 一、安裝虛擬機 創建ubuntu系統 完成之后會彈出一個新的窗口 跑完之后會重啟一下 按住首先用ctrlaltf3進入命令界面&#xff0c;輸入root&#xff0c;密碼登錄管理員賬號 按Esc 然后輸入 …

數據結構常用的字符串函數(中英雙釋)

頭文件&#xff1a;string.h 1.strchr const char * strchr ( const char * str, int character ); Locate first occurrence of character in string str C string. character Character to be located. Return Value A pointer to the first occurrence of character in s…

適用于恢復iOS數據的 10 款免費 iPhone 恢復軟件

現在&#xff0c;您可以獲得的 iPhone 的存儲容量比大多數人的筆記本電腦和臺式電腦的存儲容量還要大。雖然能夠存儲數千張高分辨率照片和視頻文件、安裝數百個應用程序并隨身攜帶大量音樂庫以供離線收聽固然很棒&#xff0c;但在一個地方擁有如此多的數據可能會帶來毀滅性的后…

2.2_5 調度算法

文章目錄 2.2_5 調度算法一、適用于早期的批處理系統&#xff08;一&#xff09;先來先服務&#xff08;FCFS&#xff0c;First Come First Serve&#xff09;&#xff08;二&#xff09;短作業優先&#xff08;SJF&#xff0c;Shortest Job First&#xff09;&#xff08;三&a…

SpringMVC總結

SpringMVC SpringMVC是隸屬于Spring框架的一部分&#xff0c;主要是用來進行Web開發&#xff0c;是對Servlet進行了封裝。 對于SpringMVC我們主要學習如下內容: SpringMVC簡介 請求與響應 REST風格 SSM整合(注解版) 攔截器 SpringMVC是處理Web層/表現層的框架&#xff…

易語言源代碼5000例

僅供學習研究交流使用 加群下載

探索MyBatis-Plus的高階用法

引言 MyBatis-Plus 是 MyBatis 的增強工具包&#xff0c;提供了許多方便快捷的功能來簡化開發&#xff0c;提高效率。除了基本的 CRUD 操作外&#xff0c;MyBatis-Plus 還提供了一些高級功能&#xff0c;本文將探討 MyBatis-Plus 的高階用法&#xff0c;幫助開發者更好地利用該…

Linux服務器搭建超簡易跳板機連接阿里云服務器

簡介 想要規范內部連接阿里云云服務器的方式&#xff0c;但是最近懶病犯了&#xff0c;先搞一個簡易式的跳板機過渡一下&#xff0c;順便在出一個教程&#xff0c;其他以后再說&#xff01; 配置方法 創建密鑰 登錄阿里云&#xff0c;找到云服務器ECS控制臺&#xff0c;點擊…