一、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();
}