출처: http://debop.blogspot.kr/2013/03/spring-31-redis-cache.html
Redis 를 구현하기 위해서 spring-data-redis 와 jedis 라이브러리를 사용했습니다.
jedis 만으로도 구현할 수 있지만, 편하게 spring-data-redis 의 RedisTemplate 를 사용하기로 했습니다.
우선 Redis 를 캐시 저장소로 사용하기 위해 환경설정을 합니다.
1. RedisCacheConfiguration.java
@Configuration @EnableCaching @ComponentScan(basePackageClasses = UserRepository.class) // @PropertySource("classpath:redis.properties") public class RedisCacheConfiguration { @Autowired Environment env; @Bean public JedisShardInfo jedisShardInfo() { return new JedisShardInfo("localhost"); } @Bean public RedisConnectionFactory redisConnectionFactory() { return new JedisConnectionFactory(jedisShardInfo()); } @Bean public RedisTemplate redisTemplate() { RedisTemplate<String,Object> template = new RedisTemplate<String,Object>(); template.setConnectionFactory(redisConnectionFactory()); return template; } @Bean public RedisCacheManager redisCacheManager() { return new RedisCacheManager(redisTemplate(), 300); } } |
한가지 RedisCacheFactory를 생성할 때 주의할 점은 JedisShardInfo 로 생성해야지,
JedisPoolingConfig나 기본 생성자로 생성 시에 RedisTemplate 에서 connection을 제대로 생성 못하는 버그가 있더군요 ㅠ.ㅠ
이것 때문에 반나절을 허비...
2. RedisCacheManager.java
@Slf4j public class RedisCacheManager extends AbstractTransactionSupportingCacheManager { private RedisTemplate redisTemplate; private int expireSeconds; public RedisCacheManager(RedisTemplate redisTemplate) { this(redisTemplate, 300); } public RedisCacheManager(RedisTemplate redisTemplate, int expireSeconds) { Guard.shouldNotBeNull(redisTemplate, "redisTemplate"); this.redisTemplate = redisTemplate; this.expireSeconds = expireSeconds; } @Override protected Collection<? extends Cache> loadCaches() { Collection<Cache> caches = Lists.newArrayList(); for (String name : getCacheNames()) { caches.add(new RedisCache(name, redisTemplate, expireSeconds)); } return caches; } @Override public Cache getCache(String name) { synchronized (this) { Cache cache = super.getCache(name); if (cache == null) { cache = new RedisCache(name, redisTemplate, expireSeconds); addCache(cache); } return cache; } } } |
3. RedisCache.java
@Slf4j public class RedisCache implements Cache { @Getter private String name; @Getter private int expireSeconds; private RedisTemplate redisTemplate; public RedisCache(String name, RedisTemplate redisTemplate) { this(name, redisTemplate, 300); } public RedisCache(String name, RedisTemplate redisTemplate, int expireSeconds) { Guard.shouldNotBeEmpty(name, "name"); Guard.shouldNotBeNull(redisTemplate, "redisTemplate"); this.name = name; this.redisTemplate = redisTemplate; if (log.isDebugEnabled()) log.debug("MongoCache를 생성합니다. name=[{}], mongodb=[{}]", name, redisTemplate); } @Override public Object getNativeCache() { return redisTemplate; } public String getKey(Object key) { return name + ":" + key; } @Override public ValueWrapper get(Object key) { Guard.shouldNotBeNull(key, "key"); if (log.isDebugEnabled()) log.debug("캐시 키[{}] 값을 구합니다...", key); Object result = redisTemplate.opsForValue().get(getKey(key)); SimpleValueWrapper wrapper = null; if (result != null) { if (log.isDebugEnabled()) log.debug("캐시 값을 로드했습니다. key=[{}]", key); wrapper = new SimpleValueWrapper(result); } return wrapper; } @Override @SuppressWarnings("unchecked") public void put(Object key, Object value) { Guard.shouldNotBeNull(key, "key"); if (log.isDebugEnabled()) log.debug("캐시에 값을 저장합니다. key=[{}], value=[{}]", key, value); redisTemplate.opsForValue().set(getKey(key), value, expireSeconds); } @Override @SuppressWarnings("unchecked") public void evict(Object key) { Guard.shouldNotBeNull(key, "key"); if (log.isDebugEnabled()) log.debug("지정한 키[{}]의 캐시를 삭제합니다...", key); try { redisTemplate.delete(key); } catch (Exception e) { log.error("캐시 항목 삭제에 실패했습니다. key=" + key, e); } } @Override @SuppressWarnings("unchecked") public void clear() { if (log.isDebugEnabled()) log.debug("모든 캐시를 삭제합니다..."); try { redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.flushAll(); return null; } }); } catch (Exception e) { log.warn("모든 캐시를 삭제하는데 실패했습니다.", e); } } } |
RedisCache의 get / put 은 일반적으로 쓰는 opsForValue() 를 사용했습니다.
다른 것을 사용할 수도 있을텐데, 좀 더 공부한 다음에 다른 것으로 변경해 봐야 할 듯 합니다.
마지막에 clear() 메소드도 jedis 에는 flushDB(), flushAll() 메소드를 지원하는데,
RedisTemplate에서는 해당 메소드를 expose 하지 않아 코드와 같이 RedisCallback 을 구현했습니다.
4. RedisCacheTest.java
@Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RedisCacheConfiguration.class}) public class RedisCacheTest { @Autowired RedisCacheManager redisCacheManager; @Autowired UserRepository userRepository; @Test public void clearTest() { Assert.assertNotNull(redisCacheManager); Cache cache = redisCacheManager.getCache("user"); Assert.assertNotNull(cache); cache.clear(); Assert.assertNotNull(cache); } @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.getUsername(), user2.getUsername()); } @Test public void componentConfigurationTest() { Assert.assertNotNull(redisCacheManager); Cache cache = redisCacheManager.getCache("user"); Assert.assertNotNull(cache); cache.evict("debop"); Assert.assertNotNull(userRepository); } } |
UserRepository는 전에 쓴 Spring 3.1 + EhCache 등의 글과 같은 코드라 생략했습니다.
Redis 관련은 Windows 에서는 구 버전만 지원하고, 신 버전은 linux 만 가능하더군요...
그래도 성능은 정평이 나있으니, HA 구성 시에는 가장 먼저 고려되어야 할 캐시 저장소라 생각됩니다.
'IT_Programming > Dev Libs & Framework' 카테고리의 다른 글
vert.x는 무엇이 좋은가? (0) | 2015.08.19 |
---|---|
[펌][Node JS] 이미지 업로드 및 리사이즈 (0) | 2015.07.28 |
[Spring] Spring 3.1 + MongoDB 를 이용한 Cache (0) | 2015.07.09 |
[Spring] Spring 3.1 + Couchbase 를 이용하여 Cache 만들기 (0) | 2015.07.09 |
[Spring] Spring 3.1 + Memcached 를 이용한 Cache 관리 (0) | 2015.07.09 |