Home Redis 적용
Post
Cancel

Redis 적용

Redis

Java의 Redis Client

Java 의 Redis Client 는 크게 두 가지가 있다. → Jedis 와 Lettuce

원래 Jedis 를 많이 사용했으나 여러 가지 단점 (멀티 쓰레드 불안정, Pool 한계 등등..) 과

Lettuce 의 장점 (Netty 기반이라 비동기 지원 가능) 때문에 Lettuce 로 추세가 넘어가고 있었다.

그러다 결국 Spring Boot 2.0 부터 Jedis 가 기본 클라이언트에서 deprecated 되고 Lettuce 가 탑재되었다.

관련 글
Jedis 보다 Lettuce 를 쓰자




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이고

StringRedisTemplate의 defaultSerializer는 StringRedisSerializer이다.

stringRedisTemplate bean은 일반적인 String 값을 key, value로 사용하는 경우 사용하면 된다.

redisTemplate bean은 java Object를 redis에 저장하는 경우 사용하면 된다.




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설명
opsForValueStrings를 쉽게 Serialize / Deserialize 해주는 Interface
opsForListList를 쉽게 Serialize / Deserialize 해주는 Interface
opsForSetSet를 쉽게 Serialize / Deserialize 해주는 Interface
opsForZSetZSet를 쉽게 Serialize / Deserialize 해주는 Interface
opsForHashHash를 쉽게 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 오류”에 첨부한 링크 참고하면 된다.

image

redis를 run한 다음에

image

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들을 확인

[Redis] docker로 설치한 redis 접근


$ flushall : 전체 키 삭제

image




Redis 오류

1. org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost/<unresolved>:6379

처음엔 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 설치 👉🏻 Springboot에서 Docker 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 사용하기
스프링부트 Redis 연습 1 - get/set 구현
Spring Boot에서 Redis 활용
Spring Boot Data Redis 사용해보기
[Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisTemplate 편
5분 안에 구축하는 Redis(레디스)

This post is licensed under CC BY 4.0 by the author.