IT_Programming/Dev Libs & Framework

[Spring] Spring 3.1 + Memcached 를 이용한 Cache 관리

JJun ™ 2015. 7. 9. 00:32



 출처: http://debop.blogspot.kr/2013/03/spring-31-memcached-cache.html








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);
    }
}


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