guava、caffeine、ohc(堆外緩存)詳解


一、Guava緩存

 

Guava Cache適用於以下場景:

  • 你願意消耗一些內存空間來提升速度。
  • 你預料到某些鍵會被查詢一次以上。
  • 緩存中存放的數據總量不會超出內存容量。(Guava Cache是單個應用運行時的本地緩存。它不把數據存放到文件或外部服務器。如果這不符合你的需求,請嘗試Redis這類工具)

倉庫坐標如下:

<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>19.0</version> </dependency> 

代碼詳細示例:

@Data public class CacheVO { private String name; public CacheVO(String name) { this.name = name; } } 
public class GuavaCacheMangerService { private static LoadingCache<String, CacheVO> cache; private static ExecutorService executorService = new ThreadPoolExecutor(8, 8, 8, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1204)); static { cache = CacheBuilder.newBuilder() // 緩存項在給定時間內沒有被讀/寫訪問,則回收。 .expireAfterAccess(500, TimeUnit.SECONDS) // 緩存項在給定時間內沒有被寫訪問(創建或覆蓋),則回收。 // 如果認為緩存數據總是在固定時候后變得陳舊不可用,這種回收方式是可取的。 .expireAfterWrite(500, TimeUnit.SECONDS) // 初始化容量大小 .initialCapacity(1024 * 100) // 緩存項的數目不超過固定值 .maximumSize(1024 * 100) // 可以為緩存增加自動刷新功能,配合CacheLoader reload使用 .refreshAfterWrite(1, TimeUnit.SECONDS) // 加載緩存 .build(new CacheLoader<String, CacheVO>() { // 單個加載,要么返回已經緩存的值,要么使用CacheLoader向緩存原子地加載新值。 @Override public CacheVO load(String s) throws Exception { return createCacheVO(s); } // 批量加載,對每個不在緩存的鍵,getAll方法會單獨調用CacheLoader.load來加載緩存項。 // 如果批量加載比多個單獨加載更高效,你可以重載CacheLoader.loadAll來利用這一點。 @Override public Map<String, CacheVO> loadAll(Iterable<? extends String> keys) throws Exception { return createBatchCacheVOs(keys); } // 異步刷新加載新值,在刷新操作進行時,緩存仍然可以向其他線程返回舊值, // 而不像回收操作,讀緩存的線程必須等待新值加載完成。 @Override public ListenableFuture<CacheVO> reload(String key, CacheVO oldValue) throws Exception { if (needRefresh()) { return Futures.immediateFuture(oldValue); } ListenableFutureTask<CacheVO> task = ListenableFutureTask.create(() -> {return createCacheVO(key);}); executorService.execute(task); return task; } }); } public static boolean needRefresh() { Random ra =new Random(); return (ra.nextInt(10) % 2) > 0 ? true : false; } public static CacheVO createCacheVO(String key){ return new CacheVO(key); } public static Map<String, CacheVO> createBatchCacheVOs(Iterable<? extends String> keys) { Map<String, CacheVO> result = new HashMap<>(); for (String key : keys) { result.put(key, new CacheVO(key)); } return result; } public static void main(String[] args) throws Exception{ // 單個獲取 CacheVO cacheVO1 = cache.get("AA"); // 如果有緩存則返回;否則運算、緩存、然后返回,整個過程是阻塞的 // 在整個加載方法完成前,緩存項相關的可觀察狀態都不會更改。 CacheVO cacheVO2 = cache.get("BB", () -> {return createCacheVO("BB");}); List<String> list = new ArrayList<>(); list.add("CC"); list.add("DD"); // 批量獲取 Map<String, CacheVO> cacheMap = cache.getAll(list); // 個別清除 cache.invalidate("AA"); // 批量清除 cache.invalidateAll(list); // 清除所有 cache.invalidateAll(); } } 

二、Caffeine緩存

Caffeine是一種高性能的緩存庫,是基於Java 8的最佳(最優)緩存框架,性能各方面優於guava。

代碼倉庫如下:

<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.4.0</version> </dependency> 

代碼詳細示例如下:

public class CaffeineCacheMangerService { private static LoadingCache<String, CacheVO> cache; private static AsyncLoadingCache<String, CacheVO> asyncCache; private static AsyncLoadingCache<String, CacheVO> asyncCache1; private static ExecutorService executorService = new ThreadPoolExecutor(8, 8, 8, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1204)); static { cache = Caffeine.newBuilder() // 初始化緩存長度 .initialCapacity(1024 * 10) // 最大長度 .maximumSize(1024 * 10) // 更新策略 .refreshAfterWrite(10, TimeUnit.SECONDS) // 設置緩存的過期時間 .expireAfterWrite(10, TimeUnit.SECONDS) .build(new CacheLoader<String, CacheVO>() { // 同步加載 @CheckForNull @Override public CacheVO load(@Nonnull String key) throws Exception { return createCacheVO(key); } // getAll將會對緩存中沒有值的key分別調用CacheLoader.load方法來構建緩存的值。 // 我們可以重寫CacheLoader.loadAll方法來提高getAll的效率。 @Nonnull @Override public Map<String, CacheVO> loadAll(@Nonnull Iterable<? extends String> keys) throws Exception { return createBatchCacheVOs(keys); } }); // 異步加載 同步load寫法,最后也會轉異步 asyncCache = Caffeine.newBuilder() .maximumSize(1024 * 10) .expireAfterWrite(10, TimeUnit.SECONDS) .buildAsync(new CacheLoader<String, CacheVO>() { @CheckForNull @Override public CacheVO load(@Nonnull String key) throws Exception { return createCacheVO(key); } @Nonnull @Override public Map<String, CacheVO> loadAll(@Nonnull Iterable<? extends String> keys) { return createBatchCacheVOs(keys); } }); // 異步加載 異步load寫法 asyncCache1 = Caffeine.newBuilder() .maximumSize(1024 * 10) .expireAfterWrite(10, TimeUnit.SECONDS) .buildAsync(new AsyncCacheLoader<String, CacheVO>() { @Nonnull @Override public CompletableFuture<CacheVO> asyncLoad(@Nonnull String key, @Nonnull Executor executor) { return asyncCreateCacheVO(key, executor); } @Nonnull @Override public CompletableFuture<Map<String, CacheVO>> asyncLoadAll(@Nonnull Iterable<? extends String> keys, @Nonnull Executor executor) { return asyncCreateBatchCacheVOs(keys, executor); } }); } public static CompletableFuture<CacheVO> asyncCreateCacheVO(String key, Executor executor) { return CompletableFuture.supplyAsync(() -> createCacheVO(key), executor); } public static CompletableFuture<Map<String, CacheVO>> asyncCreateBatchCacheVOs(Iterable<? extends String> keys, Executor executor) { return CompletableFuture.supplyAsync(() -> createBatchCacheVOs(keys),executor); } public static CacheVO createCacheVO(String key) { return new CacheVO(key); } public static Map<String, CacheVO> createBatchCacheVOs(Iterable<? extends String> keys) { Map<String, CacheVO> result = new HashMap<>(); for (String key : keys) { result.put(key, new CacheVO(key)); } return result; } public static void main(String[] args) throws Exception { CacheVO cacheVO1 = cache.get("AA"); List<String> list = new ArrayList<>(); list.add("BB"); list.add("CC"); Map<String, CacheVO> map = cache.getAll(list); // 如果有緩存則返回;否則運算、緩存、然后返回,整個過程是阻塞的 // 即使多個線程同時請求該值也只會調用一次Function方法 CacheVO cacheVO2 = cache.get("DD", (k) -> createCacheVO(k)); System.out.println(JSON.toJSONString(cacheVO2)); // 單個清除 cache.invalidate("AA"); // 批量清除 cache.invalidateAll(list); // 全部清除 cache.invalidateAll(); // 返回一個CompletableFuture CompletableFuture<CacheVO> future = asyncCache.get("EE"); CacheVO asyncCacheVO = future.get(); System.out.println(JSON.toJSONString(asyncCacheVO)); // 返回一個CompletableFuture<MAP<>> CompletableFuture<Map<String, CacheVO>> allFuture = asyncCache.getAll(list); Map<String, CacheVO> asyncMap = allFuture.get(); System.out.println(JSON.toJSONString(asyncMap)); CompletableFuture<CacheVO> future1 = asyncCache1.get("FF"); CacheVO asyncCacheVO1 = future1.get(); System.out.println(JSON.toJSONString(asyncCacheVO1)); CompletableFuture<Map<String, CacheVO>> allFuture1 = asyncCache1.getAll(list); Map<String, CacheVO> asyncMap1 = allFuture.get(); System.out.println(JSON.toJSONString(asyncMap1)); } } 

三、堆外緩存

1、堆外內存是什么?

在JAVA中,JVM內存指的是堆內存。機器內存中,不屬於堆內存的部分即為堆外內存。

  • Unsafe類操作堆外內存
  • NIO類操作堆外內存
    代碼示例:
// 反射獲取Unsafe實例 Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe)f.get(null); // 分配一塊內存空間 long address = unsafe.allocateMemory(1024); unsafe.putLong(address, 1); System.out.println(unsafe.getAddress(address)); // 釋放內存 unsafe.freeMemory(address); // 通過nio包下的ByteBuffer直接分配內存 ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024); 

2、堆外內存垃圾回收

參考下面鏈接:
https://www.jianshu.com/p/35cf0f348275

3、為什么用堆外內存

本地緩存是非常快速的,但是大量的本地緩存會帶來gc的壓力,所以這個時候堆外內存是一個很好的選擇。

4、堆外緩存(ohc)

倉庫坐標如下:

            <dependency> <groupId>org.caffinitas.ohc</groupId> <artifactId>ohc-core</artifactId> <version>0.7.0</version> </dependency> 

代碼示例如下:

public static OHCache<byte[], byte[]> ohcCache; static{ ohcCache = OHCacheBuilder.<byte[], byte[]>newBuilder() .keySerializer(new CacheSerializer<byte[]>() { @Override public void serialize(byte[] bytes, ByteBuffer byteBuffer) { byteBuffer.put(bytes); } @Override public byte[] deserialize(ByteBuffer byteBuffer) { return new byte[]{byteBuffer.get()}; } @Override public int serializedSize(byte[] bytes) { return bytes.length; } }) .valueSerializer(new CacheSerializer<byte[]>() { @Override public void serialize(byte[] bytes, ByteBuffer byteBuffer) { byteBuffer.putInt(bytes.length); byteBuffer.put(bytes); } @Override public byte[] deserialize(ByteBuffer byteBuffer) { byte[] arr = new byte[byteBuffer.getInt()]; byteBuffer.get(arr); return arr; } @Override public int serializedSize(byte[] bytes) { return bytes.length; } }) // number of segments (must be a power of 2), defaults to number-of-cores * 2 .segmentCount(2) // hash table size (must be a power of 2), defaults to 8192 .hashTableSize(1024) .capacity(2 * 1024 * 8) .timeouts(true) // 每個段的超時插槽數 .timeoutsSlots(64) // 每個timeouts-slot的時間量(以毫秒為單位) .timeoutsPrecision(512) // "LRU" 最舊的(最近最少使用的)條目被逐出 // "W_TINY_LFU" 使用頻率較低的條目被逐出,以便為新條目騰出空間 .eviction(Eviction.W_TINY_LFU) .build(); } 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM