Redis 를 구현하기 위해서 spring-data-redis 와 jedis 라이브러리를 사용했습니다.
jedis 만으로도 구현할 수 있지만, 편하게 spring-data-redis 의 RedisTemplate 를 사용하기로 했습니다.
우선 Redis 를 캐시 저장소로 사용하기 위해 환경설정을 합니다.
1. RedisCacheConfiguration.java
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) | |
// @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
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 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
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 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
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 = {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 구성 시에는 가장 먼저 고려되어야 할 캐시 저장소라 생각됩니다.
저는 앞으로 hibernate 2nd cache provider for redis, hibernate-ogm-redis 를 만들어 볼 예정입니다.
댓글 없음:
댓글 쓰기