Redis 적용
Redis
Java의 Redis Client
Java에서 Redis를 사용할 때 대표적으로 두 가지 Redis Client가 많이 사용된다.
Jedis
Lettuce
Jedis → 사용이 간단한 전통적인 동기 방식 Redis Client
Lettuce → Netty 기반의 비동기 처리로 고성능을 제공하는 Redis Client
2가지 접근 방식
Spring Data Redis는 Redis에 2가지 접근 방식을 제공한다.
RedisTemplate, Redis Repository를 이용한 방식이다.
공통 setting
build.gradle
의존성 추가
1
2
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'
application.properties
host 와 port 를 설정한다.
1
2
spring.redis.host = host.docker.internal
spring.redis.port = 6379
나는 docker에서 redis를 실행시켰기 때문에 host를 위와 같이 작성했다.
6379는 기본값이기 때문에 만약 Redis를 6379 로 띄웠다면 따로 설정하지 않아도 연결이 된다.
코드에서 properties에 작성한 값을 로딩할 때는
@Value("${spring.redis.port}")와 같이 SpEL(Spring Expression Language)을 작성하면 된다.
application
1
2
3
4
5
@SpringBootApplication
@EnableRedisHttpSession //Redis에 세션 데이터를 저장
public class AdmeApplication { // chat/room
public static void main(String[] args) {
SpringApplication.run(AdmeApplication.class, args);
Config
Spring Boot의 RedisAutoConfiguration은 RedisTemplate과 StringRedisTemplate 두 가지 bean을 자동으로 생성하여 제공하고 있다.
따라서 만약 개별 설정을 하고자 하는 경우 아래와 같이 작성하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
/*
RedisTemplate 에 LettuceConnectionFactory 을 적용해주기 위해 설정
*/
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean //Redis 데이터에 쉽게 접근하기 위한 코드
public RedisTemplate<?, ?> redisTemplate() { //RedisTemplate 에 LettuceConnectionFactory 을 적용
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
//모든 Key, Value Serialization을 변경할 수 있음
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate() { //StringRedisTemplate은 문자열을 다룰 때 사용
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
return stringRedisTemplate;
}
}
StringRedisTemplate - docs.spring.io
RedisTemplate과 RedisTemplate의 문자열 중심 확장인 StringRedisTemplate 타입이 있다.
- RedisTemplate의 defaultSerializer : JdkSerializationRedisSerializer
- redisTemplate bean은 java Object를 redis에 저장하는 경우 사용
- StringRedisTemplate의 defaultSerializer : StringRedisSerializer
- stringRedisTemplate bean은 String 값을 key, value로 사용하는 경우 사용
public class StringRedisTemplate extends RedisTemplate<String,String>
StringRedisTemplate은 따로 Bean으로 안만들어도 쓸 수 있다.
RedisTemplate
Controller
1
2
3
4
5
6
7
8
9
10
11
12
@MessageMapping("/chat/addUser")
private final RedisService redisService;
private final Long hours = 10L;
public void addUser(@Payload ChatMessage chatMessage, @Header("Authorization") String token) {
String nickname = jwtTokenProvider.getNickname(token);
chatMessage.setSender(nickname);
redisService.setRedisTemplate(chatMessage, hours);
redisService.getRedisTemplate(chatMessage.getSender());
chatService.connectUser("Connect", chatMessage.getRoomId());
template.convertAndSend("/topic/public/" + chatMessage.getRoomId(), chatMessage);
}
Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Service
@RequiredArgsConstructor //
public class RedisService {
private final RedisTemplate<String, String> redisTemp;
private final StringRedisTemplate redisTemplate;
private final RedisRepository redisRepository;
// string (opsForValue)
public void setRedisTemplate(ChatMessage chatMessage, Long expirationTime){
redisTemplate.opsForValue().set(chatMessage.getSender(), chatMessage.getRoomId(), expirationTime, TimeUnit.HOURS);
// 키가 이미 있다면 마지막에 Set한 값으로 덮어씀
}
public String getRedisTemplate(String key){
return redisTemplate.opsForValue().get(key);
}
public void setRedisValue(ChatMessage chatMessage){
ValueOperations<String, String> values = redisTemp.opsForValue();
values.set(chatMessage.getSender(), chatMessage.getRoomId());
}
public String getRedisValue(String key){
ValueOperations<String, String> values = redisTemp.opsForValue();
return values.get(key);
}
}
String에 특화된 StringRedisTemplate이므로
String 만 다루려면 아래 서비스 사용하고, 그게 아니라면 RedisTemplate 빈 정의하여 사용하면 된다.
redisTemplate을 주입받은 후에 원하는 Key, Value 타입에 맞게 Operations 을 선언해서 사용할 수 있다.
ValueOperations, SetOperations, HashOperations은 각각 Strings, Set, Hash 자료구조에 대한 Operations이다.
ex) SetOperations<String, String> setOperations = redisTemp.opsForSet();
| method | 설명 |
|---|---|
| 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 |
Redis Repository
Dto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RedisResponseDto {
private String nickname;
private String roomId;
private Long hour;
public static RedisResponseDto of(Redis redis){
return RedisResponseDto.builder()
.nickname(redis.getNickname())
.roomId(redis.getRoomId())
.hour(redis.getExpiration())
.build();
}
}
Redis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Getter
@RedisHash("chatRoom")
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Redis {
@Id
private String id;
@Indexed // 필드 값으로 데이터 찾을 수 있게 하는 어노테이션(findByAccessToken)
private String nickname;
private String roomId;
@TimeToLive(unit = TimeUnit.HOURS)
private Long expiration;
@Builder
public Redis(String nickname, String roomId, Long hour){
this.nickname = nickname;
this.roomId = roomId;
this.expiration = hour;
}
}
Repository
1
2
3
public interface RedisRepository extends CrudRepository<Redis, String> {
Redis findByNickname(String nickname);
}
Service
1
2
3
4
5
6
7
8
9
10
11
12
public void addRedis(ChatMessage chatMessage, Long hours){
Redis redis = new Redis(chatMessage.getSender(), chatMessage.getRoomId(), hours);
Redis save = redisRepository.save(redis);
}
public String getRedis(String key){
Redis byNickname = redisRepository.findByNickname(key);
return RedisResponseDto.of(byNickname).getRoomId();
}
public void deleteRedis(String key){
redisTemplate.delete(key);
}
Docker Redis 설치
*docker desktop 활용
redis를 run한 다음에
Optional settings에서 container name과 port를 작성했다.
그 다음에 application을 실행시키면 정상적으로 동작한다.
1
$ docker ps
docker에 띄워진 컨테이너 확인
CONTAINER ID 값 복사
1
docker exec -it {Redis컨테이너이름} redis-cli
docker로 redis 컨테이너를 실행
*-it란 무엇일까?
-it를 붙여줘야 명령어를 실행한 후 계속 명령어를 적을 수 있다.
만약 -it 가 없다면 redis-cli를 열어주기만 했다가 바로 다시 밖으로 나와버린다. 따라서 -it를 반드시 적어준다.
02. Docker 통한 Redis CLI 접속 방법 + 기본 사용법
1
$ keys *
redis에 저장된 key들을 확인
$ flushall : 전체 키 삭제
Redis 오류
1. org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; *nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost/
처음엔 application.properties에 spring.redis.host = localhost 로 설정했다.
docker에 redis 설치후에 spring.redis.host = host.docker.internal로 변경했더니 실행이 잘 되었다.
도커에서 컨테이너를 띄우면 각 컨네이너와 localhost는 독립적이게 된다.
따라서 docker에서 실행중인 서버는 localhost로 접속을 하는 것이 아닌 docker host ip를 통해서 접속을 해야 한다.
redis 설치할 때 참고한 blog
window로 redis 설치 👉🏻 [REDIS] 📚 Window10 환경에 Redis 설치 & 설정
오류 해결 👉🏻 Docker Redis + Spring 연결
2. RedisConfig - bean이 2개
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Bean //Redis 데이터에 쉽게 접근하기 위한 코드
public RedisTemplate<String, String> redisTemplate() { //RedisTemplate 에 LettuceConnectionFactory 을 적용
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate() { //StringRedisTemplate은 문자열을 다룰 때 사용
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
return stringRedisTemplate;
}
StringRedisTemplate만 사용하려고 해서 굳이 bean등록을 안해도 되지만 한번 써보기 위해서 작성했다가 아래와 같이 에러가 떴다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.dalcho.adme.service.RedisService required a single bean, but 2 were found:
- redisTemplate: defined by method 'redisTemplate' in class path resource [com/dalcho/adme/config/RedisConfig.class]
- stringRedisTemplate: defined by method 'stringRedisTemplate' in class path resource [com/dalcho/adme/config/RedisConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
내 생각에 해당 오류가 뜨는 이유는 redisTemplate도 String, String 타입인데
StringRedisTemplate은 RedisTemplate의 <String, String> 타입이기 때문에 같은 bean이 2개잖아? 이래서 뜨는 것 같다.
위 redsiTemplate의 타입을 <?,?>로 수정하면 해결 완료
3. redis 값
controller에서 save 후에 get을 하면 값이 떴는데 controller와 service를 이용해서 get을 받아오면 null이 떴다.
아래 코드를 추가시켜서 null이 뜨지 않았다.
1
implementation 'org.springframework.session:spring-session-data-redis'
1
2
3
@EnableRedisHttpSession //Redis에 세션 데이터를 저장
public class AdmeApplication {
}
findBy~할 때 model의 ~ 부분에 @Indexed를 붙여줘야 한다.
그래야 값을 가져올 수 있다.
1
2
@Indexed // 필드 값으로 데이터 찾을 수 있게 하는 어노테이션(findByAccessToken)
private String nickname;
reference
Spring Boot에서 Redis 활용
[Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisTemplate 편
Spring Boot Data Redis 사용해보기


