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

1、Guava緩存

Guava Cache適用於如下場景:git

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

倉庫座標以下:github

<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();

    }
}

2、Caffeine緩存

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

代碼倉庫以下:框架

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

代碼詳細示例以下:dom

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));
    }

}

3、堆外緩存

一、堆外內存是什麼?

在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);

二、堆外內存垃圾回收

參考下面連接: https://www.jianshu.com/p/35cf0f348275async

三、爲何用堆外內存

本地緩存是很是快速的,可是大量的本地緩存會帶來gc的壓力,因此這個時候堆外內存是一個很好的選擇。ide

四、堆外緩存(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();
    }
相關文章
相關標籤/搜索