본문 바로가기
실습/리눅스 서버 + 스프링 부트

Redis In Java_자료구조

by 이민우 2022. 3. 20.
728x90
반응형

Redis

레디스는 인메모리 기반의 키-값 데이터 저장소이다.

인메모리의 특성상 아주 빠른 속도를 가지고 있고, 이에 따라 자주 사용하는 데이터 정도를 저장하는 간단한 데이터베이스 정도로 생각을 하고 있었다.

 

하지만 실무에서 본 Redis는 그 쓰임새가 생각보다 많았다.

기본적으로 키-값 데이터 저장소라는 이야기를 듣고 아, 그냥 캐시에다 Map을 저장하는 거구나 라고 생각을 했었다.

그리고 이 Map은 내 기준에서는 키와 값 두 개의 스트링으로 이루어진 데이터일 뿐이었다.

하지만 의외로 Value에는 다양한 자료구조가 탑재될 수 있었다.

 

물론 여기까지는 어느정도 Map을 생각했을 때 당연하다고 생각되는 부분이었지만,

보다 신기했던 기능은 Redis가 RabbitMQ, Kafka와 같은 메시지 큐로써의 기능도 탑재하고 있었다는 것이다.

 

아직 모르는 게 많다는 것을 깨달았고,

앞으로의 실무에 있어 Redis에 대한 보다 깊은 이해가 필요함을 느꼈다.

 

그래서 오늘은 자료구조에 대해 공부해보고,

다음주 주말에는 Message Queue로써의 사용법을 공부하고

이에 대해 공부한 내용을 기재해볼까 한다.

 

 

Redis 자료구조

 

Redis에서 지원하는 자료구조는 아래와 같다.

  • String
  • Lists
  • Set
  • Sorted Sets
  • Hashes
  • Bit arrays
  • HyperLogLogs
  • Streams

 

이 중 JAVA에서 함수로 지원하는 자료구조는 아래와 같다.

(아래 사진에서 볼 수 있듯 이게 전부는 아니고 그냥 밑의 자료구조만 공부해보았다.)

String ValueOperations
List ListOpertations
Set SetOperations
Sorted Sets ZSetOperations
Hashed HashOperations

RedisTemplate 안에 있는 자료구조들

 

테스트를 위해 간단한 Spring Boot 어플리케이션을 만들어보았다.

디펜던시는 JPA도 사용하지 않을 예정이기에 간단하게 Redis 만을 추가했다.

 

그 후 redis의 서버 정보를 입력해주었다.

 

프로젝트 생성 이후에는 굳이 별다른 작업을 해줄 필요는 없었다.

인터넷에 Spring - Redis간 연결을 검색하면 RedisTemplate을 선언해주는 예제들이 많은데,

Spring Boot의 최근 버전들은 application.properties에 적어주기면 해도 자동으로 연결이 되어

굳이 이런 작업을 해줄 필요는 없었다.

 

String

 

가장 먼저 테스트해볼 요소는 String이다.

즉, 그냥 Map 구조를 Redis에 저장하는 것이다.

클래스는 org.springframework.data.redis.core.ValueOperations 이고, 예제는 아래와 같다.

 

Test에 다음의 코드를 작성해보자.

@Resource(name="redisTemplate")
private ValueOperations<String, Object> stringOps;
	
@Test
void testValueOps() {
	stringOps.set("test_key1", "1234");
	stringOps.set("test_key2", "4567");
		
	logger.info("test_key1 : {}", stringOps.get("test_key1").toString());
	logger.info("test_key2 : {}", stringOps.get("test_key2").toString());
}

 

그러면 test_key1에는 1234, test_key2에는 4567이 저장되게 된다.

동일한 키에 value를 다시 set 할 경우, 원래의 데이터는 지워지고 새로운 데이터가 삽입되게 된다.

 

만약 이미 존재하는 key를 덮어씌우기 싫다면 set 대신 setIfAbsent를 사용할 수 있다.

 

더불어 무조건 덮어씌우고 싶다면 혹은 사전에 존재했는지 여부를 파악하고 싶다면 마찬가지로 set 대신 setIfPresent를 사용하면 된다.

 

추가로 임시로 저장하는 키의 경우 일정시간이 지나면 스스로 사라지도록 timeout을 걸 수 있다.

그래서 아래의 코드 삭제된 데이터를 불러오고자 했으니 null을 반환하고, null을 toString으로 불러오게 되어 에러가 발생한다.

@Test
void testValueOps() throws InterruptedException {
	stringOps.set("test_key_timeout", "12345", 1000, TimeUnit.MILLISECONDS); //1초 뒤 삭제
	
	Thread.sleep(1001);
	
	logger.info("test_key_timeout : {}", stringOps.get("test_key_timeout").toString());
}

 

get도 마찬가지로 그냥 불러오는 것 말고 어떠한 작업을 함께 수행할 수 있다.

getAndDelete, getAndExpire, getAndPersist, getAndSet이 그 종류인데, 모든 함수는 이름만 봐도 사용법을 이해할 수 있었다.

 

추가적으로 .expire(key, 60, TimeUnit.MINUTES) 를 통해 데이터의 생명주기를 제어할 수 있다. 이는 앞으로 나오는 모든 데이터 타입에 공통으로 적용할 수 있다.

 

 

List

 

List는 org.springframework.data.redis.core.ListOperations를 불러와 사용할 수 있다.

사실 이름은 리스트이지만 덱이라고 이해하면 쉬울 것 같다.

push, pop을 사용하며, 양쪽 끝에서 삽입과 삭제가 일어나기 때문이다.

 

사용법은 아래와 같다.

@Resource(name="redisTemplate")
private ListOperations<String, Object> listOps;
	
@Test
void listOpsTest() throws InterruptedException {
	listOps.rightPush("test_list", "1");
	listOps.rightPush("test_list", "2");
	listOps.leftPush("test_list", "3");
	
	// 3 - 1 - 2 순으로 리스트에 들어가게 되었다.
	
	logger.info(listOps.rightPop("test_list").toString()); //2가 출력된다.
	logger.info(listOps.leftPop("test_list").toString()); //3이 출력된다.
	logger.info(listOps.size("test_list").toString()); //마지막 남은 갯수인 1이 출력된다.
}

간단하게 설명을 하자면 오른쪽에서 1, 2가 순서대로 들어왔고 왼쪽에서 3이 들어왔다.

그 결과 리스트 안에는 3-2-1이라는 숫자가 누적되게 된다.

그리고 이를 rightPop, leftPop으로 원하는 위치의 데이터를 가져와 사용하면 된다.

 

 

Set & Sorted Sets

 

Set은 org.springframework.data.redis.core.SetOperations, Sorted Sets는 org.springframework.data.redis.core.ZSetOperations 클래스를 받아와 사용한다.

 

Set은 당연한 말이지만 중복을 허용하지 않는 List이다.

 

사용법은 기본적으로 add, pop을 사용하여 활용된다.

예제 코드는 아래와 같다.

@Resource(name="redisTemplate")
private SetOperations<String, Object> setOps;
	
@Test
void listOpsTest() throws InterruptedException {
	setOps.add("test_set", "4");
	setOps.add("test_set", "2");
	setOps.add("test_set", "1");
	setOps.add("test_set", "1");
	setOps.add("test_set", "3");
	
	Object popStr;
	while((popStr = setOps.pop("test_set")) != null) {
		logger.info(popStr.toString()); // 4321 출력
	}
	
}

 

위의 결과로 List라면 4-2-1-1-3 이 입력되어야겠지만,

중복을 허용하지 않기에 추후에 입력된 1은 제외되고

4-2-1-3이 들어가게 된다.

 

참고로 일반 SetOperations는 무질서한 집합이다.

그 말은 즉, 위의 코드를 여러 번 돌리게 되면 그때그때 다른 결과값이 출력될 수 있다는 말이다.

 

그래서 순서가 중요하다면 사용하는 것이 Sorted Sets이다.

Sorted Sets는 add 시에 score를 함께 입력하고, 이를 기반으로 저장을 수행하게 된다.

@Resource(name="redisTemplate")
private ZSetOperations<String, Object> zsetOps;
	
@Test
void listOpsTest() throws InterruptedException {
	zsetOps.add("test_zset", "4", 4);
	zsetOps.add("test_zset", "2", 3);
	zsetOps.add("test_zset", "1", 2);
	zsetOps.add("test_zset", "1", 6);
	zsetOps.add("test_zset", "3", 1);
	
	Object popStr;
	while((popStr = zsetOps.popMax("test_zset")) != null) {
		logger.info(popStr.toString());
	}
	
}

위의 경우 출력된 데이터는 아래 순서와 같다.

1(6) - 4(4) - 2(3) - 3(1)

 

추가로 popMax뿐만 아니라 popMin도 지원하며,

Score와 함께 저장하기 때문에 아래와 같이 score기반의 삭제도 가능하다.

@Test
void listOpsTest() throws InterruptedException {
	zsetOps.add("test_zset", "4", 4);
	zsetOps.add("test_zset", "2", 3);
	zsetOps.add("test_zset", "1", 6);
	zsetOps.add("test_zset", "3", 1);
	
	zsetOps.removeRangeByScore("test_zset", 1, 3);
	
	Object popStr;
	while((popStr = zsetOps.popMax("test_zset")) != null) {
		logger.info(popStr.toString());
	}
	
}

위의 코드의 경우 Score가 1~3인 2, 3의 데이터가 사라지게 된다.

 

참고로 위처럼 사용하면 튜플이 반환되어 {score = x, value = y} 처럼 반환되는데,

만약 value 혹은 score만 반환하고 싶다면 TypedTuple<Object>로 변환한 다음

getValue 혹은 getScore를 통해 불러오면 된다.

 

Sorted Sets는 경험상 접근횟수 저장 시 현재 시간을 score로 지정해놓고 시간이 지나 특정 시간 범위 내 데이터를 삭제해야 할 경우 유용하게 사용되었다.

 

 

Hash

 

마지막으로 살펴볼 자료구조는 Hash이다.

이전까지의 자료구조는 모두 key가 하나였다면, Hash는 키가 두 개인 것이 큰 차이점이다.

 

hash는 org.springframework.data.redis.core.HashOperations 를 불러와 사용 가능하며,

사용법은 아래와 같다. 

@Resource(name="redisTemplate")
private HashOperations<String, String, Object> hashOps;
	
@Test
void listOpsTest() throws InterruptedException {
	hashOps.put("firstKey", "secondKey1", "value1");
	hashOps.put("firstKey", "secondKey2", "value2");
	hashOps.put("firstKey", "secondKey3", "value3");
	hashOps.put("firstKey", "secondKey4", "value4");
	
	logger.info(hashOps.get("firstKey", "secondKey3"));
}
728x90
반응형

'실습 > 리눅스 서버 + 스프링 부트' 카테고리의 다른 글

Ehcache  (0) 2022.04.02
Redis In Java_메시지 큐  (0) 2022.03.20
Spring Cloud Config  (0) 2022.03.13
Spring Boot 캐시 사용  (0) 2022.03.11
httpd (아파치 웹 서버)  (0) 2022.03.06