Redis 란?
Key-Value 형태를 띄고 있는 In Memory 기반의 NoSQL DBMS이다.
최근에 실무에서도 50만건의 데이터를 추출하기 위해 Redis를 사용할 일이 있었다.
기존 RDMS의 조회 시 7분 40초에서 레디시 캐시 등록 후 4초로 개선되는 센세이션을 보았다.
Redis를 사용해야 하는 이유
Redis 클러스터를 통해 메모리만 충분하다면 그리고 자주 갱신되지 않는 데이터라면 엄청난 속도 향상을 가져다 줌
캐시 사용법
package com.example.config;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/*
* 기본으로 redis에 cacheName::key 형태로 구분되어 저장됨. key를 생략할 경우, "SimpleKey []" 로 저장됨.
1. @Cacheable: 읽을때.
-예시: @Cacheput 참고.
2. @CachePut: 갱신
-예시: @Cacheable(value="photo", key="#file.fileID", condition="#file.fileName='test'", unless="#result == null", cacheManager="gsonCacheManager")
3. @CacheEvict: 삭제(cacheManager 지정하면 안됨!)
-예시: @CacheEvict(value="photo", key="#file.fileID")
4. @Caching: 한 메소드에 여러 어노테이션이 필요할때 그룹화 해줌.
-예시: @Caching( evict= { @CacheEvict(...), @CacheEvict(...) }, ... )
5. 어노테이션 외에 직접 캐시매니저를 통해 캐시 접근이 필요한 경우
-서비스 class에서 @Autowired private CacheManager cacheManager; 선언
-함수 안에서 cacheManager.getCache("cacheName").evict("key") 처럼 처리하면 됨.
*/
@Configuration
@EnableCaching public class RedisCacheConfig {
//************************
// 일반 객체용 캐시매니저
//************************
@Primary
@Bean(name = "cacheManager")
public RedisCacheManager cacheManager(RedisConnectionFactory cf, ResourceLoader rl) { // 기본 캐시매니저(객체 통째로 보관)
RedisCacheManagerBuilder builder= RedisCacheManagerBuilder.fromConnectionFactory(cf);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(rl.getClassLoader())
.disableCachingNullValues() // 널값 금지(캐싱시 unless("#result == null") 필수.)
.entryTtl(Duration.ofDays(1)) // 기본 캐시 1일 유지.
.computePrefixWith(CacheKeyPrefix.simple()) // name::key 처럼 key앞에 '::'를 삽입(redis-cli에서 get "name::key" 로 조회.)
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()));
// 캐시별로 유효시간 다르게 정하기.
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
// 3초
Duration d3s= Duration.ofSeconds(3);
cacheConfigurations.put("pageHot100Talk", configuration.entryTtl(d3s));
//
10분 Duration d10m= Duration.ofMinutes(10);
cacheConfigurations.put("listKeyword", configuration.entryTtl(d10m));
// 1시간
Duration d1h= Duration.ofHours(1);
cacheConfigurations.put("listExMain", configuration.entryTtl(d1h));
return builder.cacheDefaults(configuration).withInitialCacheConfigurations(cacheConfigurations).build();
}
//************************
// generic json용 캐시매니저
//************************
@Bean(name = "gsonCacheManager")
public RedisCacheManager gsonCacheManager(RedisConnectionFactory cf, ResourceLoader rl) { // json으로 값 보관(detache가 필요한 entity들- 회원정보, ... 등)
RedisCacheManagerBuilder builder= RedisCacheManagerBuilder.fromConnectionFactory(cf);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(rl.getClassLoader())
.disableCachingNullValues()
.entryTtl(Duration.ofDays(1)) // 기본 캐시 1일 유지.
.computePrefixWith(CacheKeyPrefix.simple())
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // json형식으로 value 저장.
// 캐시별로 유효시간 다르게 정하기.
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
// 1시간
Duration d1h= Duration.ofHours(1);
cacheConfigurations.put("listCategory", configuration.entryTtl(d1h));
return builder.cacheDefaults(configuration).withInitialCacheConfigurations(cacheConfigurations).build();
}
}
Redis 사용
@Component
@RequiredArgsConstructor
public class RedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
private final ModelMapper modelMapper;
public void put(String key, Object value, Long expirationTime){
if(expirationTime != null){
redisTemplate.opsForValue().set(key, value, expirationTime, TimeUnit.SECONDS);
}else{
redisTemplate.opsForValue().set(key, value);
}
}
public void delete(String key){
redisTemplate.delete(key);
}
public <T> T get(String key, Class<T> clazz){
Object o = redisTemplate.opsForValue().get(key);
if(o != null) {
if(o instanceof LinkedHashMap){
return modelMapper.map(o, clazz);
}else{
return clazz.cast(o);
}
}
return null;
}
public boolean isExists(String key){
return redisTemplate.hasKey(key);
}
public void setExpireTime(String key, long expirationTime){
redisTemplate.expire(key, expirationTime, TimeUnit.SECONDS);
}
public long getExpireTime(String key){
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
}
- redisTemplate.opsForValue().get(key) : key 값으로 value 를 가져온다
- redisTemplate.opsForValue().set(key, value, time, unit) : key값으로 value를 저장한다 만약 만료 시간을 지정하는 경우엔 time 과 단위를 함께 세팅한다
- redisTemplate.hasKey(key) : 키의 존재유무를 return 한다
- redisTemplate.delete(key) : key값에 대한 데이터를 삭제한다.
RedisTemplate, StringRedisTemplate
Redis는 여러 자료 구조를 가지고 있습니다. 이런 여러 종류의 자료구조를 대응하기 위해 Spring Data Redis는 opsFor[X](ex. opsForValue, opsForSet, opsForZSet 등)라는 메서드를 제공합니다. 해당 메서드를 사용하면 각 자료구조에 대해서 쉽게 Serialize 및 Deserialize 할 수 있습니다.
각 메서드에 대한 설명은 아래와 같습니다. (빨간색은 실무에서 많이 사용)
메서드 | 설명 |
opsForValue | Strings를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForList | List를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForSet | Set를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForZSet | ZSet를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForHash | Hash를 쉽게 Serialize / Deserialize 해주는 Interface |
Strings
Redis의 Strings 자료구조는 opsForValue 메서드를 사용합니다. 사용한 테스트 코드와 결과입니다.
@Autowired
StringRedisTemplate redisTemplate;
@Test
public void testStrings() {
final String key = "sabarada";
final ValueOperations<String, String> stringStringValueOperations = redisTemplate.opsForValue();
stringStringValueOperations.set(key, "1"); // redis set 명령어
final String result_1 = stringStringValueOperations.get(key); // redis get 명령어
System.out.println("result_1 = " + result_1);
stringStringValueOperations.increment(key); // redis incr 명령어
final String result_2 = stringStringValueOperations.get(key);
System.out.println("result_2 = " + result_2);
}
result_1 = 1
result_2 = 2
List
Redis의 List 자료구조는 opsForList 메서드를 통해서 쉽게 컨트롤할 수 있습니다.
@Autowired
StringRedisTemplate redisTemplate;
@Test
public void testList() {
final String key = "sabarada";
final ListOperations<String, String> stringStringListOperations = redisTemplate.opsForList();
stringStringListOperations.rightPush(key, "H");
stringStringListOperations.rightPush(key, "e");
stringStringListOperations.rightPush(key, "l");
stringStringListOperations.rightPush(key, "l");
stringStringListOperations.rightPush(key, "o");
stringStringListOperations.rightPushAll(key, " ", "s", "a", "b", "a");
final String character_1 = stringStringListOperations.index(key, 1);
System.out.println("character_1 = " + character_1);
final Long size = stringStringListOperations.size(key);
System.out.println("size = " + size);
final List<String> ResultRange = stringStringListOperations.range(key, 0, 9);
System.out.println("ResultRange = " + Arrays.toString(ResultRange.toArray()));
}
character_1 = e
size = 10
ResultRange = [H, e, l, l, o, , s, a, b, a]
Set
@Test
public void testSet() {
String key = "sabarada";
SetOperations<String, String> stringStringSetOperations = redisTemplate.opsForSet();
stringStringSetOperations.add(key, "H");
stringStringSetOperations.add(key, "e");
stringStringSetOperations.add(key, "l");
stringStringSetOperations.add(key, "l");
stringStringSetOperations.add(key, "o");
Set<String> sabarada = stringStringSetOperations.members(key);
System.out.println("members = " + Arrays.toString(sabarada.toArray()));
Long size = stringStringSetOperations.size(key);
System.out.println("size = " + size);
Cursor<String> cursor = stringStringSetOperations.scan(key, ScanOptions.scanOptions().match("*").count(3).build());
while(cursor.hasNext()) {
System.out.println("cursor = " + cursor.next());
}
}
members = [l, e, o, H]
size = 4
cursor = l
cursor = e
cursor = o
cursor = H
Sorted Set
@Test
public void testSortedSet() {
String key = "sabarada";
ZSetOperations<String, String> stringStringZSetOperations = redisTemplate.opsForZSet();
stringStringZSetOperations.add(key, "H", 1);
stringStringZSetOperations.add(key, "e", 5);
stringStringZSetOperations.add(key, "l", 10);
stringStringZSetOperations.add(key, "l", 15);
stringStringZSetOperations.add(key, "o", 20);
Set<String> range = stringStringZSetOperations.range(key, 0, 5);
System.out.println("range = " + Arrays.toString(range.toArray()));
Long size = stringStringZSetOperations.size(key);
System.out.println("size = " + size);
Set<String> scoreRange = stringStringZSetOperations.rangeByScore(key, 0, 13);
System.out.println("scoreRange = " + Arrays.toString(scoreRange.toArray()));
}
range = [H, e, l, o]
size = 4
scoreRange = [H, e
Hash
@Test
public void testHash() {
String key = "sabarada";
HashOperations<String, Object, Object> stringObjectObjectHashOperations = redisTemplate.opsForHash();
stringObjectObjectHashOperations.put(key, "Hello", "sabarada");
stringObjectObjectHashOperations.put(key, "Hello2", "sabarada2");
stringObjectObjectHashOperations.put(key, "Hello3", "sabarada3");
Object hello = stringObjectObjectHashOperations.get(key, "Hello");
System.out.println("hello = " + hello);
Map<Object, Object> entries = stringObjectObjectHashOperations.entries(key);s
System.out.println("entries = " + entries.get("Hello2"));
Long size = stringObjectObjectHashOperations.size(key);
System.out.println("size = " + size);
}
hello = sabarada
entries = sabarada2
size = 3
출처 :
https://cublip.tistory.com/339
https://gogo-jjm.tistory.com/35
'SPRING > 기본 상식' 카테고리의 다른 글
[SPRING] 스프링 배치 테이블 초기화 방법 (0) | 2021.12.15 |
---|---|
[SPRING] 스프링 배치 아키텍쳐 (0) | 2021.07.20 |
[SPRING] Checked Exception Guide (0) | 2021.06.14 |
[SPRING] Spring Boot Custom Annotation 만들기 (0) | 2021.06.09 |
[SPRING] Zuul이란? (0) | 2021.05.25 |