
분산 환경이란?
여러 애플리케이션 서버가 하나의 Redis 서버를 공유하는 환경입니다.
서버 A (애플리케이션) ─┐
├──→ Redis 서버
서버 B (애플리케이션) ─┘
분산 캐시 환경을 구축하려면 아래와 같은 고려사항들을 염두하여 구축해야 합니다.
문제: 서버 A에서 데이터를 업데이트하고 캐시를 삭제했지만, 서버 B의 캐시는 아직 이전 데이터를 가지고 있을 수 있습니다.
해결 방법:
// 버전 관리 예시
String cacheKey = "user:profile:v2:" + id; // 버전 포함
// 데이터 업데이트 시 버전 증가 → 자동으로 이전 캐시 무효화
문제: 캐시가 만료되면 모든 서버에서 동시에 DB 조회가 발생할 수 있습니다.
해결 방법:
@Scheduled(fixedRate = 300000) // 5분마다 실행
public void refreshPopularData() {
// 인기 데이터를 미리 갱신하여 만료 방지
List<Long> popularUserIds = getPopularUserIds();
popularUserIds.forEach(id -> {
Optional<User> user = userRepository.findById(id);
user.ifPresent(u -> redisTemplate.opsForValue().set(
"user:profile:" + id, u, 3600, TimeUnit.SECONDS
));
});
}
문제: 단일 Redis 서버가 다운되면 모든 캐시가 사라집니다.
해결 방법:
Redis 복제 (Replication) 이해하기:
Redis 복제는 하나의 마스터(Master) 서버와 하나 이상의 슬레이브(Slave) 서버로 구성됩니다. 마스터 서버의 모든 데이터 변경사항이 슬레이브 서버에 자동으로 복제됩니다.
복제 구조:
마스터 서버 (Master)
├── 데이터 읽기/쓰기
├── 데이터 변경사항을 슬레이브에 전송
└── 슬레이브 서버들 (Slave)
├── 데이터 읽기만 가능
├── 마스터로부터 데이터 복제
└── 마스터 다운 시 승격 가능
복제의 장점:
복제 설정 예시:
# 슬레이브 서버 설정 (redis.conf)
replicaof 127.0.0.1 6379 # 마스터 서버 주소
replica-read-only yes # 읽기 전용 모드
복제 지연 (Replication Lag):
마스터에서 슬레이브로 데이터를 복제하는 데 시간이 걸리므로, 슬레이브는 마스터보다 약간 오래된 데이터를 가질 수 있습니다. 이를 복제 지연이라고 합니다. 일반적으로 밀리초 단위의 지연이 발생하며, 대부분의 경우 문제가 되지 않습니다.
Redis Sentinel 이해하기:
Redis Sentinel은 Redis 서버의 모니터링 및 자동 장애 조치를 담당하는 시스템입니다. 마스터 서버가 다운되면 자동으로 슬레이브를 마스터로 승격시킵니다.
Sentinel 구조:
Sentinel 1 ─┐
Sentinel 2 ─┼──→ 마스터 서버 모니터링
Sentinel 3 ─┘
│
├── 마스터 다운 감지
├── 슬레이브 승격
└── 클라이언트에 새 마스터 주소 알림
Sentinel 설정 예시:
# Redis Sentinel 설정 예시
spring:
data:
redis:
sentinel:
master: mymaster
nodes:
- localhost:26379
- localhost:26380
- localhost:26381
password: redis-password # Master 비밀번호 (설정된 경우)
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
Sentinel 모드에서 RedisTemplate 동작 방식:
RedisTemplate을 사용하면 자동으로 전체 Redis 서버에 데이터 조회가 가능합니다. 구체적인 동작 방식은 다음과 같습니다:
쓰기 작업 (set, delete 등):
읽기 작업 (get 등):
자동 Failover:
실제 코드 예시:
@Service
public class UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Optional<User> getUserProfile(Long id) {
String cacheKey = "user:profile:" + id;
// Sentinel 모드에서는 자동으로:
// 1. Master/Slave 중 적절한 서버에 연결
// 2. Master 다운 시 자동으로 새로운 Master에 재연결
// 3. 코드 변경 없이 자동으로 처리됨
Object cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return Optional.of((User) cached);
}
// DB 조회 및 캐시 저장
Optional<User> user = userRepository.findById(id);
if (user.isPresent()) {
// 쓰기는 자동으로 Master에 저장됨
redisTemplate.opsForValue().set(
cacheKey,
user.get(),
3600,
TimeUnit.SECONDS
);
}
return user;
}
}
중요한 점:
Redis Cluster 이해하기:
Redis Cluster는 데이터를 여러 노드에 분산 저장하여 수평 확장을 가능하게 합니다. 각 노드는 전체 데이터의 일부만 저장하며, 자동으로 데이터를 분산합니다.
Cluster 구조:
노드 1 (슬롯 0-5460) ─┐
노드 2 (슬롯 5461-10922) ├──→ 전체 데이터 분산
노드 3 (슬롯 10923-16383) ┘
Cluster의 장점:
Cluster 설정 예시:
# Redis Cluster 설정 예시
spring:
data:
redis:
cluster:
nodes:
- localhost:7000 # Master 1
- localhost:7001 # Master 2
- localhost:7002 # Master 3
- localhost:7003 # Slave 1 (Master 1의 복제본)
- localhost:7004 # Slave 2 (Master 2의 복제본)
- localhost:7005 # Slave 3 (Master 3의 복제본)
max-redirects: 3 # MOVED/ASK 응답 처리 최대 횟수
password: redis-password # Cluster 비밀번호 (설정된 경우)
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
Cluster 모드에서 RedisTemplate 동작 방식:
RedisTemplate을 사용하면 자동으로 전체 클러스터에 데이터 조회가 가능합니다. 구체적인 동작 방식은 다음과 같습니다:
키 기반 자동 라우팅:
자동 클러스터 토폴로지 발견:
MOVED/ASK 응답 자동 처리:
자동 Failover:
실제 코드 예시:
@Service
public class UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Optional<User> getUserProfile(Long id) {
String cacheKey = "user:profile:" + id;
// Cluster 모드에서는 자동으로:
// 1. 키의 해시 값을 계산하여 적절한 노드 결정
// 2. 해당 노드에 연결하여 데이터 조회
// 3. 노드 다운 시 자동으로 Slave 승격 및 재연결
// 4. MOVED/ASK 응답 자동 처리
// 코드 변경 없이 자동으로 처리됨
Object cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return Optional.of((User) cached);
}
// DB 조회 및 캐시 저장
Optional<User> user = userRepository.findById(id);
if (user.isPresent()) {
// 키에 따라 자동으로 적절한 노드에 저장됨
redisTemplate.opsForValue().set(
cacheKey,
user.get(),
3600,
TimeUnit.SECONDS
);
}
return user;
}
}
키 분산 예시:
키: "user:profile:1"
→ CRC16 해시: 1234
→ 슬롯: 1234
→ 노드: Master 1 (슬롯 0-5460 담당)
키: "user:profile:2"
→ CRC16 해시: 8000
→ 슬롯: 8000
→ 노드: Master 2 (슬롯 5461-10922 담당)
키: "user:profile:3"
→ CRC16 해시: 15000
→ 슬롯: 15000
→ 노드: Master 3 (슬롯 10923-16383 담당)
중요한 점:
고가용성 전략 선택 가이드:
| 상황 | 권장 방식 | 이유 |
|---|---|---|
| 단일 서버, 읽기 성능 향상 | 복제 (Replication) | 구현 간단, 읽기 분산 |
| 자동 장애 조치 필요 | Sentinel | 자동 Failover |
| 대용량 데이터, 수평 확장 | Cluster | 데이터 분산, 확장성 |
| 소규모 서비스 | 단일 서버 + 백업 | 간단한 구조 |
주의사항:
캐시 용도로 Redis를 사용하는 경우, 데이터 손실이 허용되므로 복제나 Sentinel만으로도 충분할 수 있습니다. 하지만 대용량 트래픽을 처리해야 하는 경우 Cluster를 고려해야 합니다.