redis란 어떻게 보면 인메모리 경량 DB 같으면서도 NoSql 기반의 다양한 자료구조를 사용하기도 하기 때문에 실제 db와 service 사이에서 cache 같은 역할을 하는거 같다.
실제 db에서 조회하면되지 왜 redis까지 사용하면서 함? 이라고 물어본다면 예를 들어 무신사 스토어에 페이지를 구성할 때 사용자들이 가장 많이 접속하는 페이지는 정문 페이지일 것이다. 그 페이지에는 최근 가장 많이 팔린 옷, 신발, 악세서리 등의 데이터들 노출되어지는데 만약 사용자가 한번에 10,000명이 접속한다면 해당 데이터를 10,000번 select해서 10,000번 정렬해야한다... db가 해당 작업만으로도 터질거 같은데 이외에 결제, 문의 글 등록, 수정 등 다양한 작업을 처리해야한다. 그래서 이런 db 데이터들을 redis와 같은 cache 같은 용도로 사용하며 실제 db는 중요한 업무 처리만 담당할 수 있도록 해준다.
그리고 나는 사실 무신사, 네이버 같은 큰 규모가 아니지만 redis를 사용하려고 하는 곳은 로그인 처리 쪽이다. 로그인을 jwt를 사용하여 토큰을 발급해서 처리하는데 여기서 문제가 로그아웃이다. 로그아웃의 경우 cookie 값을 없애줘야하는데 api 서버 자체에서는 토큰을 발급만 하지 따로 session에 접근하거나 cookie를 삭제하는 행위를 할수가 없다. 그래서 대부분 사용하는 방법이 발급한 토큰을 db에 저장시켜두고 로그아웃이 되었을 경우에는 해당 토큰을 로그아웃 상태로 변경해주는 방법을 사용하는거 같았다. 그래서 redis를 실습해보려고 한다.
참고 블로그의 글을 토대로 실습을 진행해보려고 한다.
간단한 실습이므로 window 환경에서 진행했습니다.
docker hub에서 redis 이미지를 검색하여 확인한 후 설치해보자
docker pull redis
명령어를 통해 이미지를 다운받고
docker run --name some-redis -d redis
이미지를 실행시켜보자
컨테이너가 정상적으로 실행되었다.
docker exec -it some-redis /bin/bash
명령어로 redis docker container에 접속하고
redis-cli
redis에 접속한다.
그러면 mysql과 같은 db를 사용해보았다면 그런 비슷한 화면으로 전환된다.
set key1 "hello world"
명령어를 입력해보자
정상 실행되면 OK가 나오며
get key1
입력시 다음과 같은 결과를 확인할 수 있다.
이렇게 redis를 간단하게 사용해 볼 수 있었다.
append key1 "juno"
append를 통해
내용을 추가할 수도 있다.
del key1
key를 지우고 싶다면 다음과 같이 del을 통해 지울 수도 있다.
sadd
는 set 자료구조를 사용할 수 있다.
sadd test my
sadd test name
sadd test juno
를 등록하고
smembers test
smembers
로 입력하면 다음과 같이 결과가 순서 보장 없이 나오는 것을 확인할 수 있다. set인지 확인하기 위해 같은 내용을 한번 더 등록해보자
sadd test juno
이미 중복되는 내용이므로 추가해도 0이 반환되며
실제로 데이터를 조회해도 추가되지 않고 이전 그대로 나온다.
zadd rank 100 a
zadd rank 50 b
zadd rank 60 c
zadd rank 70 d
zadd
로 데이터를 추가하며 sortedSet은 가중치-값으로 지정할 수 있다.
zadd를 통해 값을 등록해주었다.
zrange rank 0 -1
zrange
를 통해 해당 데이터를 정렬하여 뽑아올 수 있는데 다음과 같이 입력은 0 ~ 전체 값을 검색하는 방법이다.
zrange rank 0 1
이렇게 입력하면 0 ~ 1 까지 데이터를 가져온다.
zrange rank 0 -1
의 결과는 다음과 같이 나온다.
zrange rank 0 -1 rev
만약 역순으로 출력하고자 한다면 다음과 같이 입력하면 된다.
역순으로 잘 출력되어진다.
zrange rank 0 -1 withscores rev
점수와 함께 출력하고 싶으면 withscores
옵션을 추가해주면 된다.
hset user name juno
hset user lastname choi
hset user location seongnam
과 같이 입력한 뒤
hget user name
hget user lastname
hget user location
hgetall user
다음과 같이 결과를 확인해 볼 수 있다.
이제 redis가 어떻게 데이터를 저장하고 가져오는지 대략 파악할 수 있었다. 이걸 Spring에서는 어떻게 사용하는지 확인해보자.
또한 이 외의 redis 문법이 궁금하다면 reids 공식 홈페이지 command에서 검색해보면 된다. 참고로 어마어마하게 많다.
백기선 강사님 spring boot 2.0 Redis 강의 내용을 토대로 진행했다.
아까 생성했던 컨테이너는 지운 뒤 다시 컨테이너를 생성해서 진행해야한다.
docker stop some-redis docker rm some-redis
으로 컨테이너를 지웠다.
docker run -p 6379:6379 --name redis -d redis
다음 명령어로 컨테이너를 실행한다. 아까랑 다른건 컨테이너 이름이랑 포트가 추가된것이다.
정상 실행을 확인하면 container가 정상 실행된거다.
<!-- <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.7</version>
</dependency>
redis를 해주면 끝이다.
spring:
redis:
host: localhost
port: 6379
마지막으로 설정을 추가해준다.
기본값으로 localhost:6379 이기 때문에 따로 설정을 안해도 잘 작동하겠지만 ip와 port를 설정해야한다면 다음과 같이 yml 파일에 추가해주면 된다.
@Getter
@Setter
@RedisHash("Meetings")
public class Meeting {
@Id
private String id; //redis는 id를 string으로 사용한다고 함.
private String title;
private Date startAt;
}
객체를 하나 정의한다.
여기서 @RedisHash()
로 data의 key값을 정의해주는 것이다. 그리고 @Id는 spring.data에서 제공하는 Id 어노테이션을 사용하면 된다.
public interface MeetingRepository extends CrudRepository<Meeting, String> {
}
repository를 일반 db와 동일하게 사용해주고
public interface MeetingService {
String save(); //String type id 반환
}
@Service
@RequiredArgsConstructor
public class MeetingServiceImpl implements MeetingService{
private final MeetingRepository meetingRepository;
@Override
public String save() {
Meeting meeting = new Meeting();
meeting.setTitle("juno");
meeting.setStartAt(new Date());
Meeting save = meetingRepository.save(meeting);
return save.getId();
}
}
builder 패턴이나 생성자 패턴으로 객체를 생성하는 것을 추천하는데 강의 따라서 하다보니 그냥 했다...ㅎ builder나 생성자의 경우 id 값을 안넣고 만들면 된다.
@SpringBootTest
class MeetingServiceImplTest {
@Autowired
private MeetingService meetingService;
@Test
void redis_save_테스트(){
//given
//when
String save = meetingService.save();
//then
Assertions.assertThat(save).isNotEmpty();
}
}
실제로 데이터가 잘 들어가는지 Test를 위해 통합테스트로 구성해서 실행했고
결과는 성공으로 나왔다. 정말 성공했는지 redis에 접속하여 확인해보자.
공식문서의 cli로 바로 붙는 명령어가 있는데 network 설정이 없으면 안돼서 위에 내가 진행했던 방법으로 붙었다.
keys *
으로 검색하면
내가 저장했던 Meetings 데이터가 나오며 두번째 "Meetings:cdf30501-4968-4492-b038-3411e5dbe41a"
는 :id
값으로 저장된 것이다.
해당 객체 값을 검색해보는 방법은
hget Meetings:cdf30501-4968-4492-b038-3411e5dbe41a title
다음과 같이 검색하면
결과가 나오며 전체 내용을 보고 싶으면
hgetall Meetings:cdf30501-4968-4492-b038-3411e5dbe41a
과 같이 검색해볼 수 있다.
@Configuration
@EnableRedisRepositories
@RequiredArgsConstructor
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
@Bean
public RedisTemplate<String, Object> redisTemplate(){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer()); //key serializer
redisTemplate.setValueSerializer(new StringRedisSerializer()); //value serializer
return redisTemplate;
}
}
redisTemplate을 bean으로 등록해주고
redis client는 Jedis 와 Lettuce 두가지가 존재하는데 jedis는 최근 멀티 쓰레드, 비동기 방식 등 여러 최신 트렌드와 맞지 않는 부분이 많아 사용을 하지 않는 추세이고 Lettuce가 최신 트렌드와 맞게 잘 적용되어져 있어 해당 client가 spring boot에서도 default로 설정되어 있다.
public interface RedisTemplateService {
void save();
}
@Service
@RequiredArgsConstructor
public class RedisTemplateServiceImpl implements RedisTemplateService{
private final RedisTemplate<String, Object> redisTemplate;
@Override
public void save() {
ValueOperations<String, Object> value = redisTemplate.opsForValue();
value.set("testKey", "testValue");
}
}
위 코드와 같이 testKey라는 String key를 testValue라는String value 값으로 저장해보자.
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
@SpringBootTest
class RedisTemplateServiceImplTest {
@Autowired
RedisTemplateService redisTemplateService;
@Test
void redisTemplate_테스트(){
redisTemplateService.save();
}
}
테스트 코드를 작성하고
성공적으로 반환한 것을 확인할 수 있다.
redis를 확인해보면 testKey가 들어가 있을것을 확인할 수 있고
value도 잘 저장된것을 확인할 수 있다.