사전 조건

  1. AWS Elasticache 설정
    • Redis Cluster을 설정합니다. (기본적으로 3개의 node 설정)
  2. Redis-cli 접속 가이드
    • 간단한 명령어로 테스트 가능(get,set)
  3. redis-cli -h 호스트 -p 포트
  4. Java의 Redis 클라이언트
    • Redis 클라이언트로는 크게 Jedis, Lettuce가 존재합니다.
    • Lettuce : Netty 기반의 클라이언트로, 비동기로 요청 처리하여 고성능을 자랑함.
    • Jedis : 동기적으로 요청을 처리, 이전부터 자바의 표준 Redis Client)
    • Jedis보다는 Lettuce를 사용하는 것을 권장(https://jojoldu.tistory.com/418)
      • 스프링부트 2.0부터는 Lettuce가 기본 클라이언트가 되어서 Jedis를 사용하려면 제거해야만 함
    • Lettuce 공식문서

Lettuce Config

@Bean
fun redisConnectionFactory(): RedisConnectionFactory =
    LettuceConnectionFactory(RedisStandaloneConfiguration("server", 6379))

@Bean
fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, String> {
    val template = RedisTemplate<String, String>()
    template.setConnectionFactory(redisConnectionFactory)

    return template
}
  • server 위치에 레디스 서버 주소를 기입하여 RedisConnectionFactory를 만듭니다.
    • 추후 RedisTemplate에서 ConnectionFactory를 지정할 때 사용해 주어야 합니다.
  • AWS Elasticache를 사용하는 경우 “server” 위치에 reader가 아닌 기본 엔드포인트를 기재합니다.
    • read 목적인 경우 reader로 사용해도 되기는 하지만 write도 같이 사용하기 위해서는 기본 엔드포인트를 사용합니다

 

Simple Lettuce RW

@Repository
class StudentRedisRepository(
    private val redisTemplate: RedisTemplate<String, String>,
    private val objectMapper: ObjectMapper,
) {

    fun save(student: Student) {
        println("deserialize: ${objectMapper.writeValueAsString(student)}")

        redisTemplate.opsForValue().set(student.id, objectMapper.writeValueAsString(student))
    }

    fun getStudent(id: String): Student {
        val studentJson = redisTemplate.opsForValue().get(id)
            ?: throw IllegalArgumentException("존재하지 않는 학생 정보입니다.")

        // Serialize 하여 데이터 로딩
        return objectMapper.readValue<Student>(studentJson)
    }
}
  • 데이터를 저장할 때는 opsForValue()의 set, 불러올 때는 get 메소드 사용
    • opsForValue뿐 아니라 opsForList, opsForSet 등 다양한 연산 제공
  • Redis의 K-V 쌍에 Key에는 student의 id, Value에는 Student 객체를 저장
    • Object 타입이 아닌 String 타입이므로 객체를 Serialize하여 저장
    • {id: ?, name: ?, grade: ?}같은 형식으로 저장됩니다.
  • write시는 serialize, read시는 deserialize (직접 ObjectMapper을 사용합니다)
    • opsForValue().get() 메소드는 nullable하므로 따로 처리(존재하지 않는 경우에는 null 리턴)

 

조금 더 간편하게 사용해 보자(With. Kotlin)


Redis의 get, set에만 신경쓰고 싶은 경우(serialize, deserialize를 꼭 서비스 코드 상에서 해주어야 하나?)

  • redisTemplate 생성 시 defaultSerializer을 지정해 줍니다.
  • Kotlin의 경우, objectMapper.registerKotlinModule()로 코틀린 모듈을 등록시켜 줍니다.

Jackson-Kotlin

  • Kotlin Data Class에 대한 편의성을 제공해 줍니다.
  • 기본 생성자가 따로 필요하지 않습니다.
  • 데이터 클래스 생성자를 사용합니다, JSON 속성명은 kotlin의 런타임 타입 정보에 의해 추론될 수 있습니다.
  • https://www.baeldung.com/kotlin/jackson-kotlin
@Bean
fun objectMapper(): ObjectMapper = ObjectMapper()
    .registerKotlinModule()

@Bean
fun redisTemplate(objectMapper: ObjectMapper, redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, Student> {
    val serializer = Jackson2JsonRedisSerializer(objectMapper, Student::class.java)
    val template = RedisTemplate<String, Student>()
    template.setConnectionFactory(redisConnectionFactory)
    template.setDefaultSerializer(serializer)

    return template
}
@Repository
class StudentRedisRepository(
    private val redisTemplate: RedisTemplate<String, Student>,
) {

    fun save(student: Student) {
        redisTemplate.opsForValue().set(student.id, student)
    }

    fun getStudent(id: String): Student {
        val student = redisTemplate.opsForValue().get(id)
            ?: throw IllegalArgumentException("존재하지 않는 학생 정보입니다.")

        return student
    }
}

이전 코드와 비교해 보았을 때, objectMapper을 직접 사용해주지 않아도 deserialize/serialize 되어 간편하게 사용할 수 있습니다.

+ Recent posts