리액티브 레디스 마스터 노트 시리즈 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(비어 있을 때) vsonErrorResume(에러)- Reactive 트랜잭션 제한적 (7편)
- Lettuce는 단일 연결 멀티플렉싱 — Pool 설정 사실상 X
- TTL 활용 — 세션·분산 락(간단)·Rate Limit
시리즈 다른 편
- 1편 — Redis 기본·Spring Boot 연동
- 2편 — Template·Redisson·Serializer (현재 글)
- 3편 — 자료구조 5종
- 4편 — WebFlux 캐싱
- 5편 — 성능
- 6편 — Pub/Sub·WebSocket
- 7편 — 고급 (Transaction·Persistence·GeoSpatial·ACL)
공식 문서: Spring Data Redis Reactive / Redisson Documentation 에서 더 깊이.
다음 글(3편)에서는 Redis 5대 자료구조 — String·Hash·List·Set·ZSet의 핵심 명령과 사용 패턴까지 풀어 갑니다.