2013년 3월 25일 월요일

Spring 3.1 + Memcached 를 이용한 Cache 관리

전 글에 이어 Spring 3.1 캐시 저장소를 몇가지 늘려 봤습니다. Memcached, MongoDB, Couchbase 에 대해 만들어 봤습니다.
이번에는 Memcached 에 대해서만 설명해 보겠습니다.
Memcached 는 캐시들을 구분하는 개념이 없다는 게 하나의 특징이고, 분산 환경을 지원하므로, 직렬화/역직렬화를 통해 저장/로드 됩니다.
이것 때문에 당연히 In-Proc 인 ehcache 보다야 속도가 느리지만, HA 구성 시에는 캐시 서버로 좋은 선택이 될 수 있습니다.

그럼 먼저 MemcachedCacheManager 를 정의하면

@Slf4j
public class MemcachedCacheManager extends AbstractTransactionSupportingCacheManager {
@Getter
private MemcachedCache memcachedCache;
//protected MemcachedCacheManager() {}
public MemcachedCacheManager(MemcachedClient memcachedClient) {
Guard.shouldNotBeNull(memcachedClient, "memcachedClient");
memcachedCache = new MemcachedCache(memcachedClient);
}
public MemcachedCacheManager(MemcachedClient memcachedClient, int expireSeconds) {
Guard.shouldNotBeNull(memcachedClient, "memcachedClient");
memcachedCache = new MemcachedCache(memcachedClient, expireSeconds);
}
@Override
protected Collection<? extends Cache> loadCaches() {
super.getCacheNames();
return Sets.newHashSet(memcachedCache);
}
@Override
public Cache getCache(String name) {
return memcachedCache;
}
}

과 같습니다.

실제 캐시에 데이터를 저장/로드하는 Cache 는 다음과 같습니다.

@Slf4j
public class MemcachedCache implements Cache {
public static final int EXP_TIME = 100000;
@Getter
private String name = "default";
@Getter
private MemcachedClient nativeCache = null;
@Getter
private int expireSeconds;
// protected MemcachedCache() {}
public MemcachedCache(MemcachedClient client) {
this(client, EXP_TIME);
}
public MemcachedCache(MemcachedClient client, int expireSeconds) {
Guard.shouldNotBeNull(client, "client");
this.nativeCache = client;
this.expireSeconds = expireSeconds;
if (log.isDebugEnabled())
log.debug("MemcachedCache를 생성했습니다");
}
@Override
public ValueWrapper get(Object key) {
Guard.shouldNotBeNull(key, "key");
GetFuture<Object> result = nativeCache.asyncGet(key.toString());
SimpleValueWrapper wrapper = null;
try {
if (result.get() != null)
wrapper = new SimpleValueWrapper(result.get());
} catch (Exception ignored) {}
return wrapper;
}
@Override
public void put(Object key, Object value) {
Guard.shouldNotBeNull(key, "key");
if (!(key instanceof String)) {
log.error("Invalid key type: " + key.getClass());
return;
}
OperationFuture<Boolean> setOp = null;
setOp = nativeCache.set(key.toString(), expireSeconds, value);
if (log.isInfoEnabled()) {
if (setOp.getStatus().isSuccess()) {
log.info("객체를 캐시 키[{}]로 저장했습니다.", key);
} else {
log.info("객체를 캐시 키[{}]로 저장하는데 실패했습니다. operation=[{}]", key, setOp);
}
}
}
@Override
public void evict(Object key) {
if (key != null)
nativeCache.delete(key.toString());
}
@Override
public void clear() {
nativeCache.flush();
}
}


캐시 저장 시, 이미 있다면 update 되도록 set() 메소드를 사용합니다.

다음으로는 Spring 환경 설정을 보겠습니다. 뭐 특별한 것은 없고, MemcachedClient 를 생성해서 제공해 주면 됩니다.

@Configuration
@EnableCaching
@ComponentScan(basePackageClasses = {UserRepository.class})
@Slf4j
public class MemcachedCacheConfiguration {
@Bean
public Transcoder<Object> transcoder() {
SerializingTranscoder transcoder = new SerializingTranscoder(Integer.MAX_VALUE);
transcoder.setCompressionThreshold(1024);
return transcoder;
}
/**
* MemcachedClient 를 제공해야 합니다
*/
@Bean
public MemcachedClient memcachedClient() {
try {
// 설정항목 : https://code.google.com/p/spymemcached/wiki/SpringIntegration
MemcachedClientFactoryBean bean = new MemcachedClientFactoryBean();
bean.setServers("localhost:11211"); // servers="host1:11211,host2:11211";
bean.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY);
bean.setTranscoder(transcoder());
bean.setOpTimeout(1000); // 1000 msec
bean.setHashAlg(DefaultHashAlgorithm.KETAMA_HASH);
bean.setLocatorType(ConnectionFactoryBuilder.Locator.CONSISTENT);
return (MemcachedClient) bean.getObject();
} catch (Exception ignored) {
throw new RuntimeException(ignored);
}
}
@Bean
public MemcachedCacheManager memcachedCacheManager() {
int timeoutInSeconds = 300;
return new MemcachedCacheManager(memcachedClient(), timeoutInSeconds);
}
}


마지막으로 테스트 코드는 ehcache 예와 같습니다.

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MemcachedCacheConfiguration.class})
public class MemcachedCacheTest {
@Autowired
MemcachedCacheManager cacheManager;
@Autowired
UserRepository userRepository;
@Test
public void getUserFromCache() {
Stopwatch sw = new Stopwatch("initial User");
sw.start();
User user1 = userRepository.getUser("debop", 100);
sw.stop();
sw = new Stopwatch("from Cache");
sw.start();
User user2 = userRepository.getUser("debop", 100);
sw.stop();
Assert.assertEquals(user1, user2);
}
@Test
public void componentConfigurationTest() {
Assert.assertNotNull(cacheManager);
Cache cache = cacheManager.getCache("user");
Assert.assertNotNull(cache);
Assert.assertNotNull(userRepository);
}
}

어떻습니까? 캐시와 관련해서 개발자가 최소한의 설정만으로 캐시를 효과적으로 사용할 수 있게 되었습니다.
물론 다양한 캐시를 쓸 수 있어, 용도에 맞게 사용하면 더 좋을 듯 합니다.


댓글 없음: