기존 RDBMS 방식을 탈피한 데이터베이스를 의미한다. 기존의 관계형 DB가 가지고 있는 특성뿐만 아니라 다른 특성들을 부가적으로 지원한다. RDBMS가 가지고 있는 한계를 극복하기 위한 데이터 저장소의 새로운 형태로, 수평적 확장성을 가지고 있다. 문서, 그래프, 키 값, 인 메모리, 검색을 포함해 다양한 데이터 모델을 사용한다.
Remote Dictionary Server의 약자로, 원격 Dictinary 자료구조 서버.
레디스는 세계에서 가장 인기있는 Key-Value Store 중 하나입니다.
Key로 올 수 있는 자료형은 기본적으로 String이지만, Value는 다양한 타입을 지원합니다.
둘 다 key-value 기반이고, 메모리 베이스며, 원하는 value를 제가 원하는 표현방식으로 넣을 수 있습니다.
서버가 1대 있다는 가정에선 Redis의 장점이 크게 보이지 않지만, 분산 환경을 대입하면 장점이 보입니다.
분산 환경에서의 장점
유저 요청이 크게 늘어나 서버를 몇 대 증설하였지만, 동일한 해쉬맵 데이터를 참조해야할 상황이 있다고 가정합니다.
이 때 원격 프로세스간에 동일한 해쉬맵 데이터를 참조해야 할 때, 분산환경에선 원격 프로세스간 데이터를 동기화 하기 어렵습니다. 이 때 별도의 레디스 서버를 구성하고, 해당 레디스에서 값을 꺼내 쓴다면 메모리 기반 데이터 구조의 빠른 응답성을 확보함과 동시에 데이터 불일치 문제를 해결할 수 있습니다.
DBMS로서의 장점
또한 어플리케이션을 종료하면 휘발되어 사라져버리는 HashMap과 달리, Redis는 다양한 영속성(디스크에 백업) 옵션을 제공합니다.
Java 의 Redis Client 는 크게 두 가지가 있다.
Jedis 를 많이 사용했으나 여러 가지 단점 (멀티 쓰레드 불안정, Pool 한계 등등..) 과 Lettuce 의 장점 (Netty 기반이라 비동기 지원 가능) 때문에 Lettuce 의 사용이 많아지는 중입니다.
-> Spring Boot 2.0 부터 Jedis 가 기본 클라이언트에서 deprecated 되고 Lettuce 가 탑재되었습니다.
Spring Boot 에서 Redis 를 사용하는 방법 두가지.
공통 세팅
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
spring:
redis:
host: localhost
port: 6379
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
}
Spring Data Redis 의 Redis Repository 를 이용하면 간단하게 Domain Entity 를 Redis Hash 로 만들 수 있습니다.
하지만, 트랜잭션을 지원하지 않기 때문에 만약 트랜잭션을 적용해야 한다면, RedisTemplate 을 사용해야 합니다.
Entity
@Getter
@RedisHash(value = "people", timeToLive = 30)
public class Person {
@Id
private String id;
private String name;
private Integer age;
private LocalDateTime createdAt;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
this.createdAt = LocalDateTime.now();
}
}
Repository
public interface PersonRedisRepository extends CrudRepository<Person, String> {
}
Example
@SpringBootTest
public class RedisRepositoryTest {
@Autowired
private PersonRedisRepository repo;
@Test
void test() {
Person person = new Person("Park", 20);
// 저장
repo.save(person);
// `keyspace:id` 값을 가져옴
repo.findById(person.getId());
// Person Entity 의 @RedisHash 에 정의되어 있는 keyspace (people) 에 속한 키의 갯수를 구함
repo.count();
// 삭제
repo.delete(person);
}
}
- RedisTemplate 을 사용하면 특정 Entity 뿐만 아니라 여러가지 원하는 타입을 넣을 수 있습니다.
- template 을 선언한 후 원하는 타입에 맞는 Operations 을 꺼내서 사용합니다.
config 설정 추가
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
Example
@SpringBootTest
public class RedisTemplateTest {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
void testStrings() {
// given
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
String key = "stringKey";
// when
valueOperations.set(key, "hello");
// then
String value = valueOperations.get(key);
assertThat(value).isEqualTo("hello");
}
@Test
void testSet() {
// given
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
String key = "setKey";
// when
setOperations.add(key, "h", "e", "l", "l", "o");
// then
Set<String> members = setOperations.members(key);
Long size = setOperations.size(key);
assertThat(members).containsOnly("h", "e", "l", "o");
assertThat(size).isEqualTo(4);
}
@Test
void testHash() {
// given
HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
String key = "hashKey";
// when
hashOperations.put(key, "hello", "world");
// then
Object value = hashOperations.get(key, "hello");
assertThat(value).isEqualTo("world");
Map<Object, Object> entries = hashOperations.entries(key);
assertThat(entries.keySet()).containsExactly("hello");
assertThat(entries.values()).containsExactly("world");
Long size = hashOperations.size(key);
assertThat(size).isEqualTo(entries.size());
}
}