先分享下我基於MAP實現的一個本地緩存java
package org.hjb.component; import java.lang.ref.SoftReference; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * 本地緩存 * * 何錦彬 2017.02.24 */ public class LocalMemory { // 數據 static class CacheData { // 過時時間 private Long invalidTime; private Object data; public Long getInvalidTime() { return invalidTime; } public void setInvalidTime(Long invalidTime) { this.invalidTime = invalidTime; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } } private static Logger logger = LogManager.getLogger(LocalMemory.class); // 存儲本地緩存數據.用軟引用避免OutOfMemoryError static Map<String, SoftReference<CacheData>> localData = new ConcurrentHashMap<String, SoftReference<CacheData>>(); public static final int MAX_SIZE = 10000; public static final int WARN_VALUE = 8000; /** * @param key * 緩存KEY * @param value * 緩存數據 * @param timeOut * 超時時間,單位秒 */ public static void put(String key, Object value, Long timeOut) { if (localData.size() >= WARN_VALUE) { logger.warn("注意:本地緩存已經達到臨界值,size:" + localData.size()); } if (localData.size() > MAX_SIZE) { logger.error("超出最大值:" + localData.size()); return; } CacheData cacheData = new CacheData(); long now = System.currentTimeMillis(); long invalidTime = now + (timeOut * 1000); cacheData.setData(value); cacheData.setInvalidTime(invalidTime); SoftReference<CacheData> refCacheData = new SoftReference<CacheData>(cacheData); localData.put(key, refCacheData); } public static final Object get(String key) { SoftReference<CacheData> referenceData = localData.get(key); if (referenceData == null) { logger.debug("未找到數據,key => {}", key); } CacheData cacheData = localData.get(key).get(); if (cacheData == null) { logger.debug("未找到數據,key => {}", key); } Long invalidTime = cacheData.getInvalidTime(); if (invalidTime == null) { return null; } long now = System.currentTimeMillis(); if (now > invalidTime) { // 清除緩存 localData.remove(key); return null; } return cacheData.getData(); } public static void put(String key, Object value, long time, TimeUnit unit) { put(key, value, unit.toSeconds(time)); } public static void main(String[] args) throws InterruptedException { String key = "test"; Object value = "hello world"; LocalMemory.put("test", value, 1l); System.out.println(LocalMemory.get(key)); Thread.sleep(2000); System.out.println(LocalMemory.get(key)); } }
本地緩存
優點:
1,易用,只是比map多了個過時時間,有超時的概念
2,用軟引用,可防止對JVM的堆對象形成out memory redis
3, 相對集中緩存不須要進行網絡開銷,消除RPC
數據庫
缺點:
1,用的是堆內存。會對JVM的垃圾回收形成影響
2,大小控制只能是經過KEY值的存儲數量控制,沒法經過控制內存佔用大小
3,缺乏監控方面的設計
4,沒有緩存的移除,按期清除失效緩存
5,緩存穿透的問題,當緩存失效時間時,大量訪問到了緩存的傳統,壓到數據庫去了 apache
對於3,4問題能夠用google的guava數組
對於1,ehcache能夠用JAVA的直接內存. 緩存
對於直接內存這部分很差實現,JAVA只提供了個ByteBuffer.allocateDirect(capacity)的方法去應用直接內存,也就意味着要存入直接內存必須先把整個對象序列號成byte再放入直接內存。服務器
但這樣每次都須要序列號與反序列化的開銷,並且得全量加載的堆內存引發垃圾回收。ehcache有直接用native方法實現網絡
踩過的坑:this
當緩存出現失效, 瞬間大量訪問壓到了DB,形成DB的壓力google
解決:
1,不用失效時間來觸發緩存的更新
1, 後臺定時刷新最新內容到本地緩存,不依靠失效時間來觸發。
2, 結合廣播通知模式(如 redis)+本地緩存更新進行更新緩存,而不是經過失效來觸發(目前系統主要就是這個模式,待加上案例分享)
固然,兩種進行結合效果更好,
WEB服務器不停監控redis的訪問,同時定時輪詢,覆蓋緩存中的內容
2,經過控制進入DB操做的線程數進行控制
如, 經過重入鎖的,tryLock的condition,condition,阻塞超時方法,通知等進行控制(待加上案例分享)
當訪問不存在的KEY時,一直傳入到數據庫層面去,壓到DB,形成DB的壓
解決:
1, 添加計數器,如當一個KEY的次數達到了10次後, 在緩存總加入該KEY,進行null的返回
2, 是否符合KEY的規則 + Bloom Filter, 用redis的bitmap存數組,對已存在的值進行hash存入(若是是ID,直接存,不須要hash,準確率100%)。 若是訪問的有bit位置爲0的,一定不存在
本地緩存讀取後的修改,會相互影響的問題
解決:
若是須要修改,返回對象須要進行深度clone
歡迎關注個人公衆號,重現線上各類BUG, 一塊兒來構建咱們的知識體系