缓存使用模式

背景:本文是对团队培训的节选

Cache Aside Pattern

微软的解释 read more

    public User get(long uid){
        if (uid<0) {//failfast
            return null;
        }
        String key = "u_" + uid;
        User ucache= userCache.get(key);
        //cache miss
        if (ucache == null) {
            ucache=mysqlDAO.get(key);
            userCache.set(ucache);
        }
        return ucache;
    }

    public void save(User user) {
        if (user == null) {//failfast
            return;
        }
        String key = "u_" + user.getId();
        User user_succ = mysqlDAO.save(user);
        userCache.delete(key);
    }

    /**
     * 错误使用方式
     * @param user
     */
    public void saveWrong(User user) {
        if (user == null) {//failfast
            return;
        }
        String key = "u_" + user.getId();
        userCache.delete(key);
        mysqlDAO.save(user);
    }


    public void delete(User user) {
        if (user == null) {//failfast
            return;
        }
        String key = "u_" + user.getId();
        mysqlDAO.delete(user);
        userCache.delete(key);
    }

    /**
     * 错误处理方式
     * @param user
     */
    public void deleteWrong(User user) {
        if (user == null) {//failfast
            return;
        }
        String key = "u_" + user.getId();
        userCache.delete(key);
        mysqlDAO.delete(user);
    }

备注:更像是Read Through + Write invalidate 的混合模式

Read/Write Through

Oracle解释:read more

public User get(long uid){
        if (uid<0) {//failfast
            return null;
        }
        String key = "u_" + uid;
        User ucache= userCacheStorage.get(key);
        return ucache;
    }

    public void set(User user) {
        if (user == null) {//failfast
            return;
        }
        String key = "u_" + user.getId();
        userCacheStorage.set(user);
    }

UserCacheStorage 示例

    /**
     * 问题?
     * @param user
     */
    public void set(User user){
        System.out.println("put into the cache:"+user.toString());
        mysqlDAO.save(user);
        userCache.set(user);
    }
    /**
     * 问题?
     * @param key
     */
    public User get(String key){
        System.out.println("put into the cache:"+key);
        User ucache=userCache.get(key);
        //cache miss
        if (ucache == null) {
            ucache = mysqlDAO.get(key);
            userCache.set(ucache);
        }
        return ucache;
    }

Write Behind

Oracle解释:read more

public void set(User user) {
        if (user == null) {//failfast
            return;
        }
        String key = "u_" + user.getId();
        userCacheStorage.setAsyn(user);
    }

UserCacheStorage

public void setAsyn(User user){
        System.out.println("put into the cache:"+user.toString());
        userCache.set(user);
        try {
            userDAO.save(user);
        } catch (InterruptedException e) {
        }
    }

Refresh ahead

  • 预测热点数据并自动从数据库刷新缓存,永远不会阻塞读取,适合小型数据集
  • 比如我们的优惠券
  • 这里不进行code演示

Local Cached

前面四种是Remote cache访问模式

这里介绍本地cache,目的是减少网络带宽,访问更快

    private LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .expireAfterAccess(5, TimeUnit.SECONDS).recordStats()
            .build(
                    new CacheLoader<String, Integer>() {
                        public Integer load(String key) throws Exception {
                            return loadKeyfromMC(key);
                        }
                    });

    public Integer loadKeyfromMC(String key){
        System.out.println("get from mc:"+key);
        return 0;
    }

    public void put(String key, Integer value) {
        cache.put(key,value);
    }

    /**
     * get no reload
     * @param key
     * @return
     */
    public Integer get(String key) {
        return cache.getIfPresent(key);
    }

    /**
     * get with reload
     * @param key
     * @return
     */
    public Integer getWithReload(String key) {
        return cache.getUnchecked(key);
    }

    /**
     * 统计方法
     */
    public void stats(){
        CacheStats cacheStats=cache.stats();
        System.out.println("hit count:"+cacheStats.hitCount());
        System.out.println("hit rate:"+cacheStats.hitRate());
        System.out.println("miss count:"+cacheStats.missCount());
        System.out.println("miss rate:"+cacheStats.missRate());
    }

缓存和数据库一致性问题

  • 这是一个tradeoff问题

  • 分布式环境下,一致性需要更多的成本,分布式事务/补偿

  • 优先选择Cache-Aside模式,降低不一致的概率

    • 论文

    • 这个模式的问题

      • Read Through + Write invalidate并不能解决Read Through 不一致的问题

        • 需要增加CAS + TTL 来辅助解决,如memcached cas