이번에는 Memcached 에 대해서만 설명해 보겠습니다.
Memcached 는 캐시들을 구분하는 개념이 없다는 게 하나의 특징이고, 분산 환경을 지원하므로, 직렬화/역직렬화를 통해 저장/로드 됩니다.
이것 때문에 당연히 In-Proc 인 ehcache 보다야 속도가 느리지만, HA 구성 시에는 캐시 서버로 좋은 선택이 될 수 있습니다.
그럼 먼저 MemcachedCacheManager 를 정의하면
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 는 다음과 같습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 를 생성해서 제공해 주면 됩니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 예와 같습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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); | |
} | |
} |
어떻습니까? 캐시와 관련해서 개발자가 최소한의 설정만으로 캐시를 효과적으로 사용할 수 있게 되었습니다.
물론 다양한 캐시를 쓸 수 있어, 용도에 맞게 사용하면 더 좋을 듯 합니다.
댓글 없음:
댓글 쓰기