後端緩存

每日一課【後端緩存系統】
後端緩存可以分爲分佈式緩存本地緩存兩種。
 
兩者區別:
 
  • 分佈式緩存需要遠程調用,多了一次網絡開銷。
  • 同樣情況下,分佈式緩存要慢於本地緩存。
  • 本地緩存不能跨區域共享。
 
適用場景:
 
 
緩存與數據庫同步問題:
第一種:Cache Aside
 
優點:簡單易實現。
缺點:需要自己維護數據更新後的邏輯,對業務代碼有侵入。
 
第二種:Read/Write Through
 
本質上和第一種是一樣的,不過使用了緩存服務代理,不需要自己維護,對業務代碼無侵入。
不過需要封裝緩存和數據庫的同步機制,實現更復雜。
 
 
第三種:Write Back
在更新數據時只更新緩存,然後異步更新數據庫。
 
優點:性能高,因爲讀寫的是內存。
缺點:數據不是強一致性,如果緩存宕機,數據可能丟失。
 
適合對數據一致性要求不高的場景。
 
總結:
  • Cache Aside:適用於輕量級應用
  • Read/Write Through:適合需要頻繁使用緩存的應用
  • Write Back:適合數據一致性不高的場景
 
 
 
緩存命中率:
 
命中率越高,緩存的效率越高,到數據庫的請求就越少。
 
變更頻率高的數據不適合緩存。因爲數據經常變更會導致緩存失效。
 
 
緩存過期時間:緩存空間需設置上限,爲數據設置過期時間,以維護存儲空間的可用性。
常見的如LRU算法。
 
實現一個簡單的本地緩存:
public class LocalCache<K,V> implements Cacheable<K,V>{
    // 緩存數據的集合
    private Map<K, Data> map = new ConcurrentHashMap<>();

    // 每60s請理過期數據,延遲1s執行
    public LocalCache(){
        Timer t = new Timer();
        t.schedule(new ExpiringChecker(), 1000, 60 * 1000);
    }

    @Override
    public V get(K key) {
        Data data = map.get(key);
        if (isExpired(data)) {
            delete(key);
            return null;
        }
        return data.getVal();
    }

    private boolean isExpired(Data data) {
        if (data == null) {
            return true;
        }
        return data.getExpire() > 0 && Instant.now().toEpochMilli() > data.getExpire();
    }


    /**
     * 添加數據
     * @param key
     * @param val
     * @param expire 過期時間,單位s
     */
    @Override
    public void set(K key, V val, long expire) {
        map.put(key, new Data(val, expire));
    }


    @Override
    public void delete(K key) {
        map.remove(key);
    }


    @Override
    public boolean exist(K key) {
        return map.containsKey(key);
    }


    // 數據包裝類,保存val的時間戳
    private class Data{
        V val;
        long expire;


        Data(V val, long expire) {
            this.val = val;
            this.expire = expire > 0 ? Instant.now().toEpochMilli() + expire * 1000 : expire;
        }


        public V getVal() {
            return val;
        }


        public void setVal(V val) {
            this.val = val;
        }


        public long getExpire() {
            return expire;
        }


        public void setExpire(long expire) {
            this.expire = expire;
        }
    }


    /**
     * 過期數據檢測器
     */
    private class ExpiringChecker extends TimerTask{


        @Override
        public void run() {
            map.forEach((k, v) -> {
                if (isExpired(v)) {
                    delete(k);
                }
            });
        }
    }
}

 

 
 
 
 
 
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章