后端缓存

每日一课【后端缓存系统】
后端缓存可以分为分布式缓存本地缓存两种。
 
两者区别:
 
  • 分布式缓存需要远程调用,多了一次网络开销。
  • 同样情况下,分布式缓存要慢于本地缓存。
  • 本地缓存不能跨区域共享。
 
适用场景:
 
 
缓存与数据库同步问题:
第一种: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);
                }
            });
        }
    }
}

 

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