리액티브 레디스 — Template·Redisson·Serializer

2026-05-03확률과 통계 마스터 노트

리액티브 레디스 마스터 노트 시리즈 2편. ReactiveRedisTemplate의 5가지 Operations(Value/Hash/List/Set/ZSet), Redisson의 분산 락·고수준 API, 두 클라이언트의 결정적 차이와 선택 기준, RedisSerializer 4종(String·JdkSerialization·Jackson2Json·GenericJackson2Json), Custom Serializer 만들기, 직렬화 함정까지.

이 글은 리액티브 레디스 마스터 노트 시리즈의 두 번째 편입니다. 1편(기초)에서 ReactiveRedisTemplate의 등장 배경을 봤다면, 이번엔 그 실제 사용법 — Operations·Redisson·Serializer.

ReactiveRedisTemplate vs Redisson 선택은 시스템 결정. 직렬화는 운영의 함정 — Jackson2Json 한 줄 잘못 쓰면 데이터 폭발. 디테일을 손에 잡는 것이 이번 편.

처음 Template·Redisson이 어렵게 느껴지는 이유

처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, 둘이 어떻게 다른지가 막연합니다. 같은 Redis인데 왜 두 클라이언트가 필요? 둘째, Serializer 종류가 너무 많습니다. String·JdkSerialization·Jackson2Json·GenericJackson2Json — 어느 게 표준?

해결법은 한 가지예요. "Template = 기본기 / Redisson = 분산 시스템 도구" 한 줄. Template으로 거의 다 해결, Redisson은 분산 락 등 특수 기능. 기본은 Template 먼저.

ReactiveRedisTemplate — 5 Operations

@Autowired
ReactiveRedisTemplate<String, String> redisTemplate;

// 5종
redisTemplate.opsForValue();    // String
redisTemplate.opsForHash();     // Hash
redisTemplate.opsForList();     // List
redisTemplate.opsForSet();      // Set
redisTemplate.opsForZSet();     // Sorted Set

각 Operations는 자료구조별 명령. 자세한 자료구조는 3편.

opsForValue — String 명령

ReactiveValueOperations<String, String> ops = redisTemplate.opsForValue();

// SET / GET
ops.set("name", "Alice");
ops.get("name").subscribe();

// SET with TTL
ops.set("session:abc", "user-1", Duration.ofMinutes(30));

// SETNX (없을 때만 SET)
ops.setIfAbsent("lock", "owner");

// INCR (원자적 증가)
ops.increment("counter");

// MSET / MGET (여러 키 한 번에)
ops.multiSet(Map.of("a", "1", "b", "2"));
ops.multiGet(List.of("a", "b"));

Mono/Flux 반환

모든 Operations 메서드 = Mono 또는 Flux 반환. 구독해야 실행.

ops.set("key", "value");      // Mono<Boolean> — 구독 X면 실행 X
ops.set("key", "value")
   .subscribe();              // 이제 실행

여기서 정말 중요한 시험 함정 — Reactive는 lazy. subscribe() 또는 다른 Mono와 합성·반환 안 하면 아무 일도 안 일어남. WebFlux Controller가 반환 시 자동 구독.

Custom Serializer 설정

@Configuration
public class RedisConfig {

    @Bean
    public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
        ReactiveRedisConnectionFactory factory) {

        var serializer = new GenericJackson2JsonRedisSerializer();
        var serializationContext = RedisSerializationContext
            .<String, Object>newSerializationContext(new StringRedisSerializer())
            .value(serializer)
            .hashValue(serializer)
            .build();

        return new ReactiveRedisTemplate<>(factory, serializationContext);
    }
}

<String, Object> — Key는 String, Value는 임의 객체.

RedisSerializer 4종

Serializer 형식 호환성 사용처
StringRedisSerializer UTF-8 텍스트 모든 언어 Key 표준
JdkSerializationRedisSerializer Java 직렬화 Java 전용 비권장
Jackson2JsonRedisSerializer JSON 모든 언어 단일 타입
GenericJackson2JsonRedisSerializer JSON + 클래스 정보 모든 언어 다형성 권장

Jackson2 vs GenericJackson2

// Jackson2 — 단일 타입
new Jackson2JsonRedisSerializer<>(User.class);
// Redis 안: {"id":"1","name":"Alice"}

// GenericJackson2 — 클래스 정보 포함
new GenericJackson2JsonRedisSerializer();
// Redis 안: {"@class":"com.example.User","id":"1","name":"Alice"}

여기서 정말 중요한 시험 함정 — GenericJackson2는 클래스 정보 박힘. 패키지 변경 시 deserialize 실패. 또한 보안 위험 (RCE) → trustedPackages 설정 필수.

ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(
    BasicPolymorphicTypeValidator.builder()
        .allowIfSubType("com.example.")  // 신뢰 패키지만
        .build(),
    ObjectMapper.DefaultTyping.NON_FINAL
);
new GenericJackson2JsonRedisSerializer(mapper);

JdkSerialization 함정

Redis 안: 바이너리 (사람이 못 읽음)
Java 외 시스템 = 못 읽음
클래스 변경 = SerialVersionUID 깨짐

운영 권장 X. JSON 또는 Avro/Protobuf.

Redisson — 분산 시스템 도구

의존성

implementation 'org.redisson:redisson-spring-boot-starter:3.x'

Reactive 사용

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonReactiveClient redissonReactive(RedissonClient redisson) {
        return redisson.reactive();
    }
}

@Service
public class DistributedLockService {
    @Autowired RedissonReactiveClient redisson;

    public Mono<Void> doWithLock(String key) {
        RLockReactive lock = redisson.getLock("lock:" + key);
        return lock.lock(10, TimeUnit.SECONDS)
            .then(performTask())
            .doFinally(sig -> lock.unlock().subscribe());
    }
}

Redisson 핵심 기능

  • 분산 락 (RLock) — 여러 인스턴스 사이 락
  • 분산 카운터·리스트·맵 — 자료구조 추상화
  • 분산 큐 + Delayed Queue
  • 분산 스케줄러
  • Pub/Sub 추상화

Template vs Redisson 선택

상황 선택
단순 Redis 명령 Template
단일 인스턴스 캐시 Template
분산 락 Redisson
복잡한 분산 자료구조 Redisson
가벼움 우선 Template

여기서 정말 중요한 시험 함정 — 둘 다 사용도 OK. 캐시 = Template, 분산 락 = Redisson 같이 혼합. Redisson만 쓰면 학습 곡선 + 의존성 부담. Template + 필요할 때 Redisson.

ReactiveStringRedisTemplate — String 전용 단축

@Autowired
ReactiveStringRedisTemplate stringTemplate;
// = ReactiveRedisTemplate<String, String> 단축

Key, Value 모두 String 일 때 자동 구성. 별도 설정 X.

Mono 합성 패턴

캐시 조회 + 미스 시 DB

public Mono<User> getUser(String id) {
    String key = "user:" + id;

    return redisTemplate.opsForValue().get(key)
        .switchIfEmpty(
            userRepo.findById(id)
                .flatMap(user ->
                    redisTemplate.opsForValue()
                        .set(key, user, Duration.ofMinutes(5))
                        .thenReturn(user)
                )
        );
}

핵심 — switchIfEmpty 캐시 미스 처리.

여기서 시험 함정이 하나 있어요. switchIfEmpty는 Mono 비어 있을 때만. error는 onErrorResume. 둘 구분.

트랜잭션 (MULTI/EXEC) — 주의

redisTemplate.execute(action -> {
    // 트랜잭션 안 명령
});

여기서 정말 중요한 시험 함정 — Reactive Redis 트랜잭션은 제한적. 명령 큐만 보내고 실제 실행은 EXEC 시점. 자세한 건 7편(고급).

Connection Pool 설정

spring:
  data:
    redis:
      lettuce:
        pool:
          enabled: true
          max-active: 8     # 동시 연결 수
          max-idle: 8
          min-idle: 0
          max-wait: 1s

여기서 시험 함정이 하나 있어요. Lettuce는 단일 연결로 멀티플렉싱. Pool이 사실상 X (대부분의 경우). Pool 설정은 특수 시나리오만.

TTL 활용 패턴

// 세션
opsForValue().set("session:" + id, data, Duration.ofMinutes(30));

// 분산 락 (간단한 형태)
opsForValue().setIfAbsent("lock:" + key, "owner", Duration.ofSeconds(10));

// Rate Limit
opsForValue().increment("rate:" + userId)
    .flatMap(count -> {
        if (count == 1L) {
            // 첫 호출 → TTL 설정
            return redisTemplate.expire("rate:" + userId, Duration.ofMinutes(1))
                .thenReturn(count);
        }
        return Mono.just(count);
    })
    .filter(count -> count <= 100);

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 2편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • ReactiveRedisTemplate = 5 Operations
  • opsForValue·opsForHash·opsForList·opsForSet·opsForZSet
  • 모든 메서드 = Mono/Flux 반환 (lazy)
  • subscribe() 또는 합성 안 하면 실행 X
  • WebFlux Controller가 자동 구독
  • Custom Template — RedisSerializationContext로 Key/Value 분리
  • Serializer 4종 — StringRedisSerializer(Key 표준)·JdkSerialization·Jackson2Json·GenericJackson2Json
  • JdkSerialization 운영 X (Java 전용·바이너리·버전 깨짐)
  • Jackson2 = 단일 타입
  • GenericJackson2 = 다형성 + 클래스 정보 박힘
  • GenericJackson2 + trustedPackages = 보안 필수 (RCE 방어)
  • Redisson = 분산 시스템 도구
  • 분산 락(RLock)·분산 자료구조·분산 스케줄러
  • Reactive — RedissonReactiveClient + RLockReactive
  • 선택 — Template(단순)·Redisson(분산)·혼합도 OK
  • ReactiveStringRedisTemplate = String 전용 단축
  • 캐시 패턴 — switchIfEmpty로 캐시 미스 → DB 조회
  • switchIfEmpty (비어 있을 때) vs onErrorResume (에러)
  • Reactive 트랜잭션 제한적 (7편)
  • Lettuce는 단일 연결 멀티플렉싱 — Pool 설정 사실상 X
  • TTL 활용 — 세션·분산 락(간단)·Rate Limit

시리즈 다른 편

공식 문서: Spring Data Redis Reactive / Redisson Documentation 에서 더 깊이.

다음 글(3편)에서는 Redis 5대 자료구조 — String·Hash·List·Set·ZSet의 핵심 명령과 사용 패턴까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!