엄범

 

 

Redis command ( api )

java RedisTemplate api도 hset 등으로 제공되고 있어 봐두는 것이 좋다.

 

redis 구조는 key --1:n-- field --1:1-- value

HSET / HGETHGETALL 을 보면 알 수 있는데

redis는 하나의 key에 여러개의 field-value가 연결될 수 있다.

```java

redis> HSET myhash field1 "Hello"
redis> HSET myhash field2 "World"

 

// in java https://lettuce.io/lettuce-4/release/api/com/lambdaworks/redis/api/async/RedisHashAsyncCommands.html

hmset(String key, Map<field, value> fields)

hset(K key, K field, V value)

```

그래서 hmset 사용 시 해당 key에 대해서, field명이 이미 있다면 overwrite, 없다면 해당 필드가 추가된다.

 

HSETNX는 not exist인 경우에만 동작하고 exist인 경우 no effect.

 

spring-boot-starter-data-redis-reactive

연동

build.gradle.kts

```kt

implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")

```

 

AutoConfiguration

```java

@ConfigurationProperties(prefix = "spring.redis")    // spring.redis 이하 properties들을 불러온다.

public class RedisProperties {

    private String host = "localhost";

    private int port = 6370;

    ...

```

  • RedisAutoConfiguration.java에서는 다음 두 가지 Bean을 미리 만들어서 제공하고 있음
    • ``kt StringRedisTemplate``
      • key, value, hashKey, hashValue의 Serializer로 ``kt RedisSerializer.string()`` 사용
    • ``kt RedisTemplate<Object, Object>``
      • key, value, hashKey, hashValue의 defaultSerializer로 `` JdkSerializationRedisSerializer`` 사용
      • 결국 Java Obj로 변환한다는 건데... 이는 여러모로 단점이 있음. ([Effective Java] 12장 직렬화 참고)
      • 그래서 Jackson을 Serializer로 사용하는 `` RestTemplate``을 따로 정의해서 사용하는 것이 좋아 보인다.

 

Auto Configuration Disable?
  • ``kt RedisReactiveAutoConfiguration::class``는 exclude 가능.
  • 반면 ``kt RedisAutoConfiguration::class``는 exclude하면 factory까지 생성이 안돼서 exclude 불가.

 

예제

 

더보기

```kt

@Configuration
class RedisConfig {
    @Bean
    fun reactiveRedisTemplate(factory: ReactiveRedisConnectionFactory): ReactiveRedisTemplate<String, Any> {


        val stringSerializer = StringRedisSerializer()
        val jsonSerializer = Jackson2JsonRedisSerializer(Any::class.java)
        jsonSerializer.setObjectMapper(jacksonObjectMapper())
        val context = RedisSerializationContext.newSerializationContext<String, Any>()
                .key(stringSerializer)
                .value(jsonSerializer)
                .hashKey(stringSerializer)
                .hashValue(jsonSerializer)
                .build()
        return ReactiveRedisTemplate(factory, context)
    }

    @Bean
    fun reactiveStringRedisTemplate(
            reactiveRedisConnectionFactory: ReactiveRedisConnectionFactory
    ): ReactiveStringRedisTemplate {


        return ReactiveStringRedisTemplate(reactiveRedisConnectionFactory)
    }

    /**
     * 방법 1. 이런 식으로 data class 마다 Bean을 직접 만든다. 완전 비추.
     */
    @Bean
    fun coffeeRedisOperations(factory: ReactiveRedisConnectionFactory): ReactiveRedisOperations<String, Coffee> {


        val builder = RedisSerializationContext.newSerializationContext<String, Coffee>(StringRedisSerializer())
        val context = builder
                .value(Jackson2JsonRedisSerializer(Coffee::class.java))
                .build()

        return ReactiveRedisTemplate(factory, context)
    }
}

/**
 * 방법 2. 각 서비스 클래스에서 getRedisTemplate을 호출해서 해당 타입의 RedisTemplate을 반환 받는 방법
 */
@Component
class RedisTemplateFactory(
        val connectionFactory: ReactiveRedisConnectionFactory
) {


    final inline fun <reified V> getRedisTemplate(): ReactiveRedisTemplate<String, V> {
        val stringSerializer = StringRedisSerializer()
        val jsonSerializer = Jackson2JsonRedisSerializer(V::class.java)
        jsonSerializer.setObjectMapper(jacksonObjectMapper())
        val context = RedisSerializationContext.newSerializationContext<String, V>()
                .key(stringSerializer)
                .value(jsonSerializer)
                .hashKey(stringSerializer)
                .hashValue(jsonSerializer)
                .build()
        return ReactiveRedisTemplate(connectionFactory, context)
    }
}

/**
 * 방법 3. RestTemplate과 비슷하게 사용하기 위해서...? 내부적으로는 Any로 처리하고 set, get해서 내려줄 때는 Casting해서 내려주는 방법
 */
@Component
class RedisUtil(val redisTemplate: ReactiveRedisTemplate<String, Any>) {


    fun <V> set(key: String, value: V): Mono<Boolean> {
        return redisTemplate.opsForValue().set(key, value as Any)
    }

    /**
     * 방법 3-1.
     * opsForValue().get()으로 Object를 받고 바깥에서 ObjectMapper.convertValue()를 사용해 V로 변환
     */
    fun <V> get(key: String, clazz: Class<V>): Mono<V> {
        return redisTemplate.opsForValue().get(key)
                .map { obj -> jacksonObjectMapper().convertValue(obj, clazz<V>) }
    }

    /**
     * 방법 3-2.
     * 아예 RedisTemplate 자체를 저수준부터 구현. (RestTemplate 처럼 메서드 기반으로.)
     */
}

```

 

Spring Data Redis API

  • low-level API는 `` RedisConnection`` 계열. binary 통신 제공
    • `` RedisClusterConnection``
  • 이를 고수준으로 추상화한 API는 `` RedisTemplate`` 계열. 객체 수준으로 다룰 수 있음
    • `` ReactiveRedisTemplate``
    • RedisTemplate은 클래스이고, 보통 주고 받을 때는 `` RedisOperations``라는 인터페이스를 사용한다
    • ``kt RedisOperations.opsForValue()`` 요런 식으로 있다고 보면 됨
  • (아래) Template = connection + serializer 정도로 생각하면 된다.

```kt

@Bean
fun redisOperations(factory: ReactiveRedisConnectionFactory): ReactiveRedisOperations<String, Coffee> {
    val builder = RedisSerializationContext.newSerializationContext<String, Coffee>(StringRedisSerializer())
    val context = builder.value(Jackson2JsonRedisSerializer(Coffee::class.java)).build()
    return ReactiveRedisTemplate(factory, context)
}

```

 

Redis Cluster

 

cli 명령어

```bash

redis/src/redis-server           # 서버 실행

redis/utils/install_server.sh    # 백그라운드 데몬으로 서버 실행

redis/src/redis-cli              # cli interaction

redis-cli -h {ip} -p {port}      # 원격지 접속

redis-cli -h {ip} -p {port} -c   # 클러스터에 접속할 때(타 클러스터에 있는 key-value도 조회 가능)

```

```bash

set {key} {value}

get {key}

keys {pattern}

FLUSHALL    # 변수 초기화

save

quit

shutdown    # redis-server terminate

```