分散キャッシュ更新アプリケーション(redis破壊問題)


キャッシュブレークダウン
キャッシュ破壊とは?データベースにデータが存在し、キャッシュデータが何らかの理由で存在しないため、データベースにデータの取得を大量に要求する現象が発生します.この現象は、データベースconnectionsの数が尽き、データベース・サービスが停止する可能性があります.
分散ロックソリューション
要求がデータベースに侵入することを防止し、商品データの照会などの分散ロック方式を用いて実現することができる.
 public Product getProductById(Long productId){
        log.debug("      id:{}", productId);
        //1、       
        Product product = ehcache.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
        if (product != null){
            return product;
        }
        //2、 redis    
        product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
        if (product != null){
            return product;
        }
        //3、 db  ,  【    】
        String uuid = UUID.randomUUID().toString();
        boolean lockFlag = false;
        try {
            //      
            lockFlag = redis.lock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
            if (lockFlag) {
                //      ,               
                product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
                if (product != null) {
                    return product;
                }
                product = productService.findByProductId(productId);
                if (product != null) {
                    redis.setex(Constants.CACHE_PRODUCT_PREFIX + productId, EXPIRE_TIME, product);
                }
            }
        }catch (Exception e){
            log.error("       ", e);
            return null;  //          
        }finally {
            if (lockFlag){
                redis.unlock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
            }
        }
        return product;
    }

分散ロックスキームの最適化
分散には問題があり、分散ロックの要求を取得できなかった場合、null空のデータが返され、nullデータがカプセル化されるか、whileスリープ方式でキャッシュredisからデータが取得されるのを待つか、以下ではスリープ方式で待つことを例にtry部分コードのみを再構築する.
 try {
            //      
            lockFlag = redis.lock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
            if (lockFlag) {
                //      ,               
                product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
                if (product != null) {
                    return product;
                }
                product = productService.findByProductId(productId);
                if (product != null) {
                    redis.setex(Constants.CACHE_PRODUCT_PREFIX + productId, EXPIRE_TIME, product);
                }
            }else{
			 long endTime = 0L;
             long waitTime = 0L;
             while (true) {
                //      ,           200ms
                if (waitTime > 200) {
                    break;
                }
                //     redis     
                product =  redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
                if (product  != null) {
                    return product ;
                } else {
                    //          ,        
                    Thread.sleep(20);
                    endTime = System.currentTimeMillis();
                    waitTime = endTime - startTime;
                }
            }
        }catch (Exception e){
            log.error("       ", e);
            return null;  //          
        }finally {
            if (lockFlag){
                redis.unlock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
            }
        }