文章出處

先分享下我基于MAP實現的一個本地緩存

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 

3, 相對集中緩存不需要進行網絡開銷,消除RPC

 

缺點: 
1,用的是堆內存。會對JVM的垃圾回收造成影響 
2,大小控制只能是通過KEY值的存儲數量控制,無法通過控制內存占用大小 
3,缺少監控方面的設計 
4,沒有緩存的移除,定期清除失效緩存 
5,緩存穿透的問題,當緩存失效時間時,大量訪問到了緩存的傳統,壓到數據庫去了 

 

對于3,4問題可以用google的guava

對于1,ehcache可以用JAVA的直接內存. 

對于直接內存這部分不好實現,JAVA只提供了個ByteBuffer.allocateDirect(capacity)的方法去應用直接內存,也就意味著要存入直接內存必須先把整個對象序列號成byte再放入直接內存。

但這樣每次都需要序列號與反序列化的開銷,而且得全量加載的堆內存引起垃圾回收。ehcache有直接用native方法實現

 

踩過的坑:

 

緩存失效

當緩存出現失效, 瞬間大量訪問壓到了DB,造成DB的壓力

 

解決:

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, 一起來構建我們的知識體系

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()