spring boot redis 활용하기
Redis
cache 상태를 저장하기 위해서 서버 자체에서 cache 를 구성해서 사용하는 경우도 있지만 오픈소스인 redis 를 이용해서 cache 환경을 구성하는 경우도 많다.
흔히 말해 redis를 사용하면서 얻을 수 있는 장점으로 여러가지 것들을 얘기하지만, 예를 들어 다양한 데이터 구조 혹은 캐싱기능(빠른 계산 결과를 위한) 메세지 브로커 기능 등 사실 이것들을 꼭 redis를 사용해서 구현해야 이득이라고 생각하지는 않는다.
실제로 다양한 데이터 구조는 커스터마이징하게 cache를 구현하는것이 좀더 이득인 경우도 있고 메세지 브로커또한 redis 를 사용할 필요는 없다. 심지어 캐싱기능을 위해서 redis를 사용한다는 것은 말도 안되는 이야기라고 생각한다.
내가 생각하는 redis 의 주 사용 이유는 (물론 더 많은 경험이 있으면 바뀔수도 있겠지만) 다중 서버 환경에서 일관성을 유지하기 위함이다.
구성 환경
Docker 내부 Redis 최신 서버 설치
Spring Boot 3.2
Java 17
https://github.com/Eno1993/redis
GitHub - Eno1993/redis
Contribute to Eno1993/redis development by creating an account on GitHub.
github.com
활용
RedisConfig
@Configuration
public class redisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));
return redisTemplate;
}
}
RedisTemplate<String, Object> 형태의 템플릿을 정의한다.
String 타입의 key 를 통해서 여러 자료구조들을 정의하고 활용한다. key 를 통해 List<Object>, Set<Object> 등의 자료구조에 접근하게 된다.
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public void deleteCache(String redisKey){
redisTemplate.delete(redisKey);
}
public boolean isContainCache(String redisKey){
return redisTemplate.hasKey(redisKey);
}
}
String 타입의 key 를 이용해 사용중인 자료구조를 확인하거나 제거 할 수 있다.
List<Object>
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public void addWithList(String redisKey, String data){
redisTemplate.opsForList().rightPush(redisKey, data);
}
public void removeWithList(String redisKey, String data){
redisTemplate.opsForList().remove(redisKey, 0, data);
}
public List<String> getList(String redisKey){
List<Object> objectList = redisTemplate.opsForList().range(redisKey, 0, -1);
List<String> res = new ArrayList<>();
for(Object o : objectList){
res.add((String) o);
}
return res;
}
}
기타 메서드
- leftPush(K key, V value): 리스트의 왼쪽에 요소를 추가.
- leftPop(K key): 리스트의 왼쪽에서 요소를 꺼냄.
- rightPush(K key, V value): 리스트의 오른쪽에 요소를 추가.
- rightPop(K key): 리스트의 오른쪽에서 요소를 꺼냄.
- range(K key, long start, long end): 리스트의 지정된 범위 내의 요소를 반환.
- trim(K key, long start, long end): 리스트를 지정된 범위 내의 요소로 잘라냄.
- size(K key): 리스트의 길이를 반환.
- index(K key, long index): 리스트의 지정된 인덱스 위치의 요소를 반환.
- set(K key, long index, V value): 리스트의 지정된 인덱스 위치의 요소를 새 값으로 설정.
- remove(K key, long count, Object value): 리스트에서 count만큼 value와 동일한 요소를 제거.
[count==0 : 모든 요소 삭제, count<0 : 오른쪽부터 count 개수 만큼 삭제, count>0 : 왼쪽부터 count 개수 만큼 삭제]
- leftPushAll(K key, V... values): 리스트의 왼쪽에 여러 요소를 추가.
- rightPushAll(K key, V... values): 리스트의 오른쪽에 여러 요소를 추가.
Set<Object>
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public void addWithSet(String redisKey, String data){
redisTemplate.opsForSet().add(redisKey, data);
}
public void removeWithSet(String redisKey, String data){
redisTemplate.opsForSet().remove(redisKey, data);
}
public boolean isContainWithSet(String redisKey, String data){
return redisTemplate.opsForSet().isMember(redisKey, data);
}
public Set<String> getSet(String redisKey){
Set<Object> objectSet = redisTemplate.opsForSet().members(redisKey);
Set<String> res = new HashSet<>();
for(Object o : objectSet){
res.add((String) o);
}
return res;
}
public Set<String> getSetWithScan(String redisKey){
ScanOptions scanOptions = ScanOptions.scanOptions().build();
Cursor<Object> cursor = redisTemplate.opsForSet().scan(redisKey, scanOptions);
Set<String> res = new HashSet<>();
while (cursor.hasNext()) {
res.add((String) cursor.next());
}
return res;
}
}
기타 메서드
- add(K key, V... values): Set에 하나 이상의 멤버를 추가.
- remove(K key, Object... values): Set에서 하나 이상의 멤버를 제거.
- isMember(K key, Object o): Set에 특정 멤버가 있는지 확인.
- members(K key): Set의 모든 멤버를 반환.
- size(K key): Set의 크기(멤버 수)를 반환.
- difference(K key, K otherKey): 두 Set 간의 차집합을 반환.
- union(K key, K otherKey): 두 Set 간의 합집합을 반환.
- intersect(K key, K otherKey): 두 Set 간의 교집합을 반환.
- randomMember(K key): Set에서 무작위로 하나의 멤버를 반환.
- pop(K key): Set에서 무작위로 하나의 멤버를 제거하고 반환.
- move(K key, V value, K destKey): Set에서 특정 멤버를 다른 Set으로 이동.
- scan(K key, ScanOptions options): Set을 스캔하여 멤버를 검색.
SortedSet<Object>
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public void addWithSortedSet(String redisKey, String data){
redisTemplate.opsForZSet().add(redisKey, data, System.currentTimeMillis());
}
public void removeWithSortedSet(String redisKey, String data){
redisTemplate.opsForZSet().remove(redisKey, data);
}
public boolean isContainWithSortedSet(String redisKey, String data){
Double score = redisTemplate.opsForZSet().score(redisKey, data);
return score!=null;
}
public long orderWithSortedSet(String redisKey, String data){
Long rank = redisTemplate.opsForZSet().rank(redisKey, data);
if(rank!=null){
return rank;
}
return -1l;
}
public Set<String> getSortedSet(String redisKey){
Set<Object> objectSet = redisTemplate.opsForZSet().range(redisKey, 0, -1);
// objectSet = redisTemplate.opsForZSet().reverseRange(redisKey, 0, -1); //역순 정렬
Set<String> res = new HashSet<>();
for(Object o : objectSet){
res.add((String) o);
}
return res;
}
}
기타 메서드
- add(K key, V value, double score): Sorted Set에 멤버를 추가.
- remove(K key, Object... values): Sorted Set에서 하나 이상의 멤버를 제거.
- removeRange(K key, long start, long end): Sorted Set에서 지정된 범위의 멤버들을 제거.
- range(K key, long start, long end): Sorted Set에서 지정된 범위의 멤버들을 가져옴.
- rangeByScore(K key, double min, double max): Sorted Set에서 score가 주어진 범위 내에 있는 멤버들을 가져옴.
- rank(K key, Object value): Sorted Set에서 특정 멤버의 순위를 가져옴.
- score(K key, Object value): Sorted Set에서 특정 멤버의 score 값을 가져옴.
- size(K key): Sorted Set의 크기(멤버 수)를 반환.
Map<Object, Object>
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public void addWithHash(String redisKey, String key, String value){
redisTemplate.opsForHash().put(redisKey, key, value);
}
public void removeWithHash(String redisKey, String key){
redisTemplate.opsForHash().delete(redisKey, key);
}
public boolean isContainWithHash(String redisKey, String key){
return redisTemplate.opsForHash().hasKey(redisKey, key);
}
public String getWithHash(String redisKey, String key){
Object object = redisTemplate.opsForHash().get(redisKey, key);
if(object!=null){
return (String) object;
}
return "null";
}
public Map<String, String> getWithHashAll(String redisKey){
Map<Object, Object> objectMap = redisTemplate.opsForHash().entries(redisKey);
Map<String, String> res = new HashMap<>();
for(Object o : objectMap.keySet()){
String key = (String) o;
String value = (String) objectMap.get(o);
res.put(key, value);
}
return res;
}
}
기타 메서드
- put(K key, HK hashKey, HV value): Hash에 특정 키(Key)의 특정 필드(Field)와 값을 저장.
- putAll(K key, Map<? extends HK, ? extends HV> m): Hash에 여러 필드와 값을 일괄 저장.
- get(K key, Object hashKey): Hash에서 특정 키(Key)의 특정 필드(Field)의 값을 가져옴.
- entries(K key): Hash의 모든 필드와 값을 가져옴.
- delete(K key, Object... hashKeys): Hash에서 특정 키(Key)의 하나 이상의 필드를 삭제.
- hasKey(K key, Object hashKey): Hash에 특정 키(Key)의 특정 필드(Field)가 존재하는지 확인.
- keys(K key): Hash의 모든 필드를 가져옴.
- values(K key): Hash의 모든 값을 가져옴.
- size(K key): Hash의 크기(필드 수)를 반환.
- increment(K key, HK hashKey, long delta): Hash의 특정 필드의 값을 증가.
- expire(K key, long timeout, TimeUnit unit): Hash에 대한 만료 시간을 설정.
- persist(K key): Hash의 만료 시간을 제거하여 영속적으로 저장.