2012년 12월 6일 목요일

Google Guava 의 LoadingCache 를 이용한 메모리 캐시

Goolge Guava 는 Apache Commons 에서 제공하지 않는 유용한 Utility성 기능들이 상당히 많습니다.
특히 단순한 캐시 시스템의 경우, 캐시 시스템을 데이터 소스와는 별개로 다뤄야 하는 귀찮은 점이 있는데, 아주 간단하지만, 아예 "캐시 시스템 네가 필요할 때 데이터 소스를 직접 조회해서 캐시에 담아 놓으렴", 그럼 난 그냥 그걸 사용할께...  이런 목적에 딱 맞게 제공되는 클래스가 LoadingCache 라는 게 있습니다.

아래 코드는 특정 URL 의 컨텐츠를 메모리 상에서 캐시하고, 제공할 수 있도록 해주는 클래스인데, Google Guava의 LoadingCache를 이용하여 구현하였습니다.

package kr.kth.commons.caching.repository;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import kr.kth.commons.caching.CacheRepositoryBase;
import kr.kth.commons.tools.StringTool;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.nio.client.DefaultHttpAsyncClient;
import org.apache.http.nio.client.HttpAsyncClient;
import org.apache.http.util.EntityUtils;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * Google Guava 의 {@link LoadingCache} 를 이용하여, 캐시 값을 구하는 방법을 미리 지정하여, 쉽게 캐시를 운영할 수 있도록 캐시입니다.
 * User: sunghyouk.bae@gmail.com
 * Date: 12. 12. 5.
 */
@Slf4j
public class FutureWebCacheRepository extends CacheRepositoryBase {

 private final LoadingCache cache;

 public FutureWebCacheRepository() {
  cache =CacheBuilder.newBuilder().build(getCacheLoader());
 }

 @Override
 public Object get(String key) {
  try {
   return cache.get(key);
  } catch (ExecutionException e) {
   throw new RuntimeException(e);
  }
 }

 @Override
 public void set(String key, Object value, long validFor) {
  String str = (value != null) ? value.toString() : "";
  cache.put(key, str);
 }

 @Override
 public void remove(String key) {
  cache.invalidate(key);
 }

 @Override
 public void removes(String... keys) {
  cache.invalidateAll(Arrays.asList(keys));
 }

 @Override
 public boolean exists(String key) {
  return cache.getIfPresent(key) != null;
 }

 @Override
 public void clear() {
  cache.cleanUp();
 }

 private static CacheLoader getCacheLoader() {
  return
   new CacheLoader() {
    @Override
    public String load(String key) throws Exception {

     if (FutureWebCacheRepository.log.isDebugEnabled())
      FutureWebCacheRepository.log.debug("URI=[{}] 의 웹 컨텐츠를 비동기 방식으로 다운로드 받아 캐시합니다.", key);

     String responseStr = "";
     HttpAsyncClient httpClient = new DefaultHttpAsyncClient();
     try {
      httpClient.start();
      HttpGet request = new HttpGet(key);
      Future future = httpClient.execute(request, null);

      HttpResponse response = future.get();
      responseStr = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8"));

      if (log.isDebugEnabled())
       log.debug("URI=[{}]로부터 웹 컨텐츠를 다운로드 받았습니다. responseStr=[{}]",
                 key, StringTool.ellipsisChar(responseStr, 255));
     } finally {
      httpClient.shutdown();
     }
     return responseStr;
    }
   };
 }
}


코드를 보시면 아시겠지만, 처음 필요할 때 웹에서 컨텐츠를 다운받고, 그것을 캐시하여 사용하도록 하고 있습니다.

이 코드에서는 Expiry 에 대한 정책은 지정할 수 없게 되어 있지만, 직접 구현하던가, Guava에서 지원하는지 조사해서 지원한다면 그걸 사용하는 게 정신 건강에 좋겠죠^^

이번 글은 java 에서 apache commons 가 기본 중의 기본 라이브러리지만, google guava 도 이제 기본 중에 기본이 될 것입니다.
그 이외에도 아주 많은 유용한 기능이 있으니 공부해 보시기 바랍니다.^^



댓글 없음: