redis
- 기본 포트는 6379
- Java Redis Client는 다음 두 가지
- Jedis
- Lettuce
- SpringBoot 2.0부터 lettuce가 기본 Client
- Lettuce is a fully non-blocking Redis client built with netty providing Reactive, Asynchronous and Synchronous Data Access .
- Redis의 LRU eviction docs
- https://github.com/lettuce-io/lettuce-core/wiki/Connection-Pooling
- default expire time은 없다. 그래서 웬만하면 설정해 주는 것이 좋다.
- redis의 자료형(List, Set, ...)
Redis command ( api )
java RedisTemplate api도 hset 등으로 제공되고 있어 봐두는 것이 좋다.
redis 구조는 key --1:n-- field --1:1-- value
HSET / HGET과 HGETALL 을 보면 알 수 있는데
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``을 따로 정의해서 사용하는 것이 좋아 보인다.
- ``kt StringRedisTemplate``
Auto Configuration Disable?
- ``kt RedisReactiveAutoConfiguration::class``는 exclude 가능.
- 반면 ``kt RedisAutoConfiguration::class``는 exclude하면 factory까지 생성이 안돼서 exclude 불가.
예제
- https://spring.io/guides/gs/spring-data-reactive-redis/
- 테스트
- https://daddyprogrammer.org/post/4056/reactive-redis/
- 비동기 처리를 하기 때문에 StepVerifier를 사용해야 함. `` reactor-test`` 의존성 추가 필요.
```kt
@Configuration
class RedisConfig { // 방법 1, 3 공통
@Bean
fun reactiveRedisTemplate(factory: ReactiveRedisConnectionFactory, objectMapper: ObjectMapper): ReactiveRedisTemplate<String, Any> {
val stringSerializer = StringRedisSerializer()
val jsonSerializer = Jackson2JsonRedisSerializer(Any::class.java)
jsonSerializer.setObjectMapper(objectMapper)
val context = RedisSerializationContext.newSerializationContext<String, Any>()
.key(stringSerializer)
.value(jsonSerializer)
.hashKey(stringSerializer)
.hashValue(jsonSerializer)
.build()
return ReactiveRedisTemplate(factory, context)
}
// StringTemplate은 안해도 된다. RedisReactiveAutoConfiguration.java 에서 설정하고 있음.
@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, val objectMapper: ObjectMapper
) {
final inline fun <reified V> getRedisTemplate(): ReactiveRedisTemplate<String, V> {
val stringSerializer = StringRedisSerializer()
val jsonSerializer = Jackson2JsonRedisSerializer(V::class.java)
jsonSerializer.setObjectMapper(objectMapper)
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해서 내려주는 방법.
* 단점은 set, get 뿐만 아니라 다양한 메서드(keys, expire 등)를 모두 wrapping 해주어야 한다는 점.
*/
@Component
class RedisComponent(val redisTemplate: ReactiveRedisTemplate<String, Any>, val objectMapper: ObjectMapper) {
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 -> objectMapper.convertValue(obj, clazz) }
}
/**
* 방법 3-2.
* 아예 RedisTemplate 자체를 저수준부터 구현. (RestTemplate 처럼 메서드 기반으로.)
*/
}
```
방법2가 제일 괜찮아 보임.
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
- Redis 여러 인스턴스를 묶어서 트래픽 분산, High Performance 제공
- Master-Slave 구조로 구성하면 장애 시 복구 가능
- https://daddyprogrammer.org/post/1601/redis-cluster/
- https://lettuce.io/core/release/reference/#redis-cluster.connection-count
- https://lettuce.io/core/release/reference/#clientoptions.request-queue-size-and-cluster
Redis도 DB처럼 Mapper 사용해서 CRUD하기?
class 이름 변경, 패키지 변경 시 매우 주의!! redis 해시에 _class 필드로 해당 클래스 이름(+패키지 경로)가 들어가기 때문!!
마찬가지로 필드명 변경도 주의해야 한다.
- https://www.baeldung.com/spring-data-redis-tutorial
- 단, 기본 클라이언트가 Jedis에서 Lettuce로 변경되었으니 이를 사용하는 것 권장
- https://basketdeveloper.tistory.com/77
- https://docs.spring.io/spring-data/data-redis/docs/2.6.3/reference/html/#redis.repositories
- 역시 제일 좋은건 공식 docs
- @Indexed 같은 애너테이션에 대한 설명도 잘 나와 있다.
```kt
@Repository
interface AsyncWithdrawItemRepository: CrudRepository<AsyncWithdrawItem, String>
// 이런 구조의 장점은, mocking 하기 쉽다는 것이다.
class MockAsyncWithdrawItemRepository: AsyncWithdrawItemRepository {...}
```
@RedisHash & Repository 사용하는 방법 VS 방법2-RedisTemplateFactory 비교
- reactive가 필요하거나, json serialize/deserialize 가 필요한 경우 : `` RedisTemplateFactory``
- 메타 데이터나 설정 데이터 같은, 클래스는 존재하지만 개념적으로 해당 객체가 유니크한 경우(1:1) : `` RedisTemplateFactory``
- Student 클래스 같이, 여러 인스턴스가 존재할 수 있으며 각각이 고유한 id 별로 식별되어야 하는 경우(1:n) : `` @RedisHash & Repository``
- `` @RedisHash & Repository``는 redis 타입 hash, `` RedisTemplateFactory``는 json이므로 redis 타입 string
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
```
docker 실행 명령어
```bash
$ docker run -p 6379:6379 -v redis-volume:/data --name my-redis -d --restart always redis redis-server --save 60 1 --loglevel warning --appendonly yes
```
- redis-volume에 persistence 파일 저장
- RDB (save 옵션) : 60초 마다, 최소 1번의 쓰기가 발생했을 때 저장
- AOF (appendonly 옵션) : 활성화
- 종료 시 자동으로 재시작 restart 옵션
'Data Store' 카테고리의 다른 글
PostgreSQL 설치 (0) | 2020.12.04 |
---|---|
DB 이중화 / 클러스터링 (0) | 2020.09.23 |
redis (0) | 2020.03.10 |
[DB] 성능 최적화 (0) | 2019.11.28 |
[DB] 분산 DB, 파티셔닝 ( partitioning ), 샤딩 (sharding) (4) | 2019.08.25 |
[Transaction] lost update problem (isolation level, deadlock, update lock) (0) | 2019.07.23 |
댓글
이 글 공유하기
다른 글
-
PostgreSQL 설치
PostgreSQL 설치
2020.12.04 -
DB 이중화 / 클러스터링
DB 이중화 / 클러스터링
2020.09.23 -
[DB] 성능 최적화
[DB] 성능 최적화
2019.11.28 -
[DB] 분산 DB, 파티셔닝 ( partitioning ), 샤딩 (sharding)
[DB] 분산 DB, 파티셔닝 ( partitioning ), 샤딩 (sharding)
2019.08.25