ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Guava Cache 메모리 캐 시 사용 실천 - 정시 비동기 리 셋 및 단순 추상 패키지
    java 2022. 5. 24. 14:00
    728x90
    반응형
    캐 시 는 응용 프로그램 에서 없어 서 는 안 될 것 입 니 다. 예 를 들 어 redis, memcache, 메모리 캐 시 등 을 자주 사용 합 니 다.Guava 는 Google 에서 만 든 도구 패키지 입 니 다. 그 안에 있 는 cache 는 로 컬 메모리 캐 시 에 대한 구현 입 니 다. 다양한 캐 시 만 료 정책 을 지원 합 니 다.Guava cache 의 캐 시 로드 방식 은 두 가지 가 있 습 니 다.
    • CacheLoader
    • Callable callback

    구체 적 인 두 가지 방식 의 소 개 는 공식 문 서 를 보십시오.http://ifeve.com/google-guava-cachesexplained/
    다음은 흔히 볼 수 있 는 사용법 을 살 펴 보 자.뒤의 예제 실천 은 모두 CacheLoader 방식 으로 캐 시 값 을 불 러 옵 니 다.
    1. 간단 한 사용: 시한 부 만 료
    LoadingCache caches = CacheBuilder.newBuilder()
                    .maximumSize(100)
                    .expireAfterWrite(10, TimeUnit.MINUTES)
                    .build(new CacheLoader() {
                        @Override
                        public Object load(String key) throws Exception {
                            return generateValueByKey(key);
                        }
                    });
    try {
        System.out.println(caches.get("key-zorro"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    

    코드 에서 보 듯 이 caches 라 는 캐 시 대상 을 새로 만 들 었 습 니 다. maximumSize 는 캐 시 용량 크기 를 정의 합 니 다. 캐 시 수량 이 용량 이 출시 될 때 캐 시 를 회수 하고 최근 에 사용 하지 않 았 거나 전체적으로 사용 하지 않 은 캐 시 항목 을 회수 합 니 다.주의해 야 할 것 은 이 용량 상한 선 에 가 까 울 때 발생 하기 때문에 이 값 을 정의 할 때 상황 에 따라 적당히 늘 려 야 한다.또한 expireAfterWrite 라 는 방법 을 통 해 캐 시 만 료 시간 을 정의 하고 10 분 후에 만 료 됩 니 다.build 방법 에 CacheLoader 대상 이 들 어 와 load 방법 을 다시 썼 습 니 다.가 져 온 캐 시 값 이 존재 하지 않 거나 만 료 되 었 을 때 이 load 방법 을 사용 하여 캐 시 값 을 계산 합 니 다.이것 이 바로 가장 간단 하고 우리 가 평소에 가장 자주 사용 하 는 사용 방법 이다.캐 시 크기, 만 료 시간 및 캐 시 값 생 성 방법 을 정의 합 니 다.
    만약 에 redis 와 같은 다른 캐 시 방식 을 사용한다 면 우 리 는 위 에 있 는 '캐 시 가 있 으 면 되 돌아 갑 니 다. 그렇지 않 으 면 연산, 캐 시, 그리고 되 돌아 갑 니 다' 의 캐 시 모드 가 매우 큰 단점 이 있다 는 것 을 알 고 있 습 니 다.높 은 동시 다발 조건 에서 get 작업 을 동시에 진행 할 때 캐 시 값 이 만 료 되 었 을 때 대량의 스 레 드 가 캐 시 값 을 만 드 는 방법 을 호출 할 수 있 습 니 다. 예 를 들 어 데이터 베이스 에서 읽 는 것 입 니 다.이 럴 때 대량의 요청 을 하면 서 데이터베이스 에 있 는 이 기록, 즉 '캐 시 뚫 기' 를 조회 하기 쉽다.(예전 에 '관통' 과 '눈사태' 라 고 불 렀 는데 개념 이해 오류 에 속 하고 댓 글 수정 에 감 사 드 립 니 다) 과 바 캐 치 는 이런 상황 을 어느 정도 통제 하고 있 습 니 다.대량의 스 레 드 가 같은 key 로 캐 시 값 을 가 져 올 때 하나의 스 레 드 만 load 방법 에 들 어가 고 다른 스 레 드 는 캐 시 값 이 생 성 될 때 까지 기 다 립 니 다.이렇게 하면 캐 시가 뚫 릴 위험 을 피 할 수 있다.
    2. 진급 사용: 정시 갱신
    위의 사용 방법 과 같이 캐 시 를 뚫 는 경 우 는 없 지만 캐 시 값 이 만 료 될 때마다 대량의 요청 스 레 드 가 막 힐 수 있 습 니 다.한편, Guava 는 다른 캐 시 정책 을 제공 합 니 다. 캐 시 값 을 정기 적 으로 갱신 합 니 다. 스 레 드 호출 load 방법 으로 캐 시 를 업데이트 하고 다른 요청 스 레 드 는 이 캐 시 의 이전 값 을 되 돌려 줍 니 다.이렇게 하면 어떤 key 의 캐 시 에 있어 서 하나의 스 레 드 만 막 혀 캐 시 값 을 만 들 고 다른 스 레 드 는 오래된 캐 시 값 으로 돌아 가 막 히 지 않 습 니 다.Guava cache 의 refresh After Write 방법 이 필요 합 니 다.다음 과 같다.
    LoadingCache caches = CacheBuilder.newBuilder()
                    .maximumSize(100)
                    .refreshAfterWrite(10, TimeUnit.MINUTES)
                    .build(new CacheLoader() {
                        @Override
                        public Object load(String key) throws Exception {
                            return generateValueByKey(key);
                        }
                    });
    try {
        System.out.println(caches.get("key-zorro"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    

    코드 에서 보 듯 이 10 분 간격 으로 캐 시 값 이 갱 신 됩 니 다.
    그리고 주의해 야 할 점 은 이곳 의 시간 은 진정한 의미 의 시간 이 아니다.Guava cache 의 리 셋 은 사용자 가 스 레 드 를 요청 하여 이 스 레 드 를 load 방법 으로 호출 해 야 하기 때문에 사용자 가 이 캐 시 값 을 가 져 오 려 고 시도 하지 않 으 면 이 캐 시 는 리 셋 되 지 않 습 니 다.
    3. 진급 사용: 비동기 리 셋
    2 의 사용 방법 과 같이 같은 키 의 캐 시가 만 료 되면 여러 개의 스 레 드 가 막 히 는 문 제 를 해결 하고 캐 시 새로 고침 작업 을 수행 하 는 한 사용자 스 레 드 만 막 힙 니 다.이 를 통 해 다른 문 제 를 생각 할 수 있 습 니 다. 캐 시 된 key 가 많 을 때 높 은 병행 조건 에서 대량의 스 레 드 가 서로 다른 key 에 대응 하 는 캐 시 를 가 져 옵 니 다. 이때 도 대량의 스 레 드 가 막 히 고 데이터 베이스 에 큰 부담 을 줄 수 있 습 니 다.이 문제 의 해결 방법 은 캐 시 값 을 새로 고 치 는 작업 을 배경 스 레 드 에 맡 기 는 것 입 니 다. 모든 사용자 요청 스 레 드 는 오래된 캐 시 값 으로 돌아 갑 니 다. 그러면 사용자 스 레 드 가 막 히 지 않 습 니 다.상세 한 방법 은 다음 과 같다.
    ListeningExecutorService backgroundRefreshPools = 
    				MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));
            LoadingCache caches = CacheBuilder.newBuilder()
                    .maximumSize(100)
                    .refreshAfterWrite(10, TimeUnit.MINUTES)
                    .build(new CacheLoader() {
                        @Override
                        public Object load(String key) throws Exception {
                            return generateValueByKey(key);
                        }
                        
                        @Override
                        public ListenableFuture reload(String key,
                        		Object oldValue) throws Exception {
                        	return backgroundRefreshPools.submit(new Callable() {
    
    							@Override
    							public Object call() throws Exception {
    								return generateValueByKey(key);
    							}
    						});
                        }
                    });
    try {
        System.out.println(caches.get("key-zorro"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    

    위의 코드 에서 캐 시 리 셋 작업 을 수행 하기 위해 스 레 드 탱크 를 새로 만 들 었 습 니 다.또한 CacheLoader 의 reload 방법 을 다시 썼 습 니 다. 이 방법 에서 캐 시 새로 고침 작업 을 만 들 고 스 레 드 탱크 에 제출 합 니 다.이 때 캐 시 리 셋 은 여전히 사용자 스 레 드 에 의 해 작 동 되 어야 합 니 다. 다만 2 와 다른 점 은 이 사용자 스 레 드 가 리 셋 작업 을 촉발 한 후에 바로 오래된 캐 시 값 을 되 돌려 줍 니 다.
    TIPS
    • 캐 시 차단 과 사용자 스 레 드 차단 은 모두 이전 값 으로 돌아 가 는 것 을 볼 수 있 습 니 다.따라서 오래된 값 이 없 으 면 모두 막 힐 수 있 으 므 로 상황 에 따라 시스템 이 시 작 될 때 캐 시 내용 을 메모리 에 불 러 와 야 합 니 다.
    • 캐 시 를 새로 고 칠 때 generateValueByKey 방법 에 이상 이 생기 거나 null 로 되 돌아 오 면 이전 값 은 업데이트 되 지 않 습 니 다.
    • 별말: 메모리 캐 시 를 사용 할 때 캐 시 값 을 받 은 후에 업무 코드 에서 캐 시 를 직접 수정 하지 마 십시오. 이때 받 은 대상 의 인용 은 캐 시의 진정한 내용 을 가리 키 기 때 문 입 니 다.이 대상 에서 직접 수정 이 필요 하 다 면 캐 시 값 을 가 져 온 후 복사 본 을 복사 한 후 이 복사 본 을 전달 하여 수정 작업 을 한다.(나 는 일찍이 이 저급한 잘못 을 저 지 른 적 이 있다 -!)
    4. 단순 추상 패키지
    다음은 Guava cache 를 기반 으로 추상 화 된 캐 시 도구 류 입 니 다.(추상 이 좋 지 않 아 겨우 쓸 수 있다 -!)개선 의견 이 있 으 면 잘 부탁드립니다.
    /**
     * @description:   guava       。          ,           。             。
     * 					   getValue  ,     refreshDuration, refreshTimeunit, maxSize     
     * 					                  ,   20.
     * @author: luozhuo
     * @date: 2017 6 21    10:03:45 
     * @version: V1.0.0
     * @param 
     * @param 
     */
    public abstract class BaseGuavaCache  {
        
        private Logger logger = LoggerFactory.getLogger(getClass()); 
    	
    	//         
    	protected int refreshDuration = 10;
    	//           
    	protected TimeUnit refreshTimeunit = TimeUnit.MINUTES;
    	//       (   )
    	protected int expireDuration = -1;
    	//           
    	protected TimeUnit expireTimeunit = TimeUnit.HOURS;
    	//       
    	protected int maxSize = 4;
    	//        
    	protected static ListeningExecutorService refreshPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));
    	
    	private LoadingCache cache = null;
    	
    	/**
    	 *         (       ,                )
    	 */
    	public abstract void loadValueWhenStarted();
    	
    	/**
    	 * @description:           
    	 * @description:            ,get            
    	 * @param key
    	 * @author: luozhuo
    	 * @throws Exception 
    	 * @date: 2017 6 14    7:11:10
    	 */
    	protected abstract V getValueWhenExpired(K key) throws Exception;
    	
    	/**
         * @description:  cache       
         * @param key
         * @author: luozhuo
    	 * @throws Exception 
         * @date: 2017 6 13    5:07:11
         */
        public V getValue(K key) throws Exception {
        	try {
    			return getCache().get(key);
    		} catch (Exception e) {
    			logger.error("               ,key: " + key, e);
    			throw e;
    		}
        }
    	
        public V getValueOrDefault(K key, V defaultValue) {
        	try {
    			return getCache().get(key);
    		} catch (Exception e) {
    			logger.error("               ,key: " + key, e);
    			return defaultValue;
    		}
        }
        
    	/**
         *       
         */
        public BaseGuavaCache setRefreshDuration( int refreshDuration ){
            this.refreshDuration = refreshDuration;
            return this;
        }
        
        public BaseGuavaCache setRefreshTimeUnit(TimeUnit refreshTimeunit){
            this.refreshTimeunit = refreshTimeunit;
            return this;
        }
        
        public BaseGuavaCache setExpireDuration( int expireDuration ){
            this.expireDuration = expireDuration;
            return this;
        }
        
        public BaseGuavaCache setExpireTimeUnit(TimeUnit expireTimeunit){
            this.expireTimeunit = expireTimeunit;
            return this;
        }
        
        public BaseGuavaCache setMaxSize( int maxSize ){
            this.maxSize = maxSize;
            return this;
        }
    
        public void clearAll(){
        	this.getCache().invalidateAll();
    	}
    	
    	/**
         * @description:   cache  
         * @author: luozhuo
         * @date: 2017 6 13    2:50:11
         */
        private LoadingCache getCache() {
            if(cache == null){
                synchronized (this) {
                    if(cache == null){
                    	CacheBuilder cacheBuilder = CacheBuilder.newBuilder()
                                .maximumSize(maxSize);
                                
                        if(refreshDuration > 0) {
                        	cacheBuilder = cacheBuilder.refreshAfterWrite(refreshDuration, refreshTimeunit);
                        }
                    	if(expireDuration > 0) {
                    		cacheBuilder = cacheBuilder.expireAfterWrite(expireDuration, expireTimeunit);
                    	}
                    	
                        cache = cacheBuilder.build(new CacheLoader() {
                                @Override
                                public V load(K key) throws Exception {
                                    return getValueWhenExpired(key);
                                }
                                
                                @Override
                                public ListenableFuture reload(final K key,
                                        V oldValue) throws Exception {
                                    return refreshPool.submit(new Callable() {
                                        public V call() throws Exception {
                                            return getValueWhenExpired(key);
                                        }
                                    });
                                }
                            } );
                    }
                }
            }
            return cache;
        }
    
        @Override
        public String toString() {
            return "GuavaCache";
        }
    }
    
    
     
    728x90
    반응형
Designed by Tistory.