Redis(Remote Dictionary Server)
- NoSQL 방식으로 동작
- 메모리에 저장되어 응답속도가 빠름
- 휘발성. 메모리에 저장되는 데이터는 보존 X, 다만 RDB 스냅샷, AOF 로그의 방법이 있고 보통 혼용해서 사용함. AOF 로그란 모든 변경사항을 로그에 기록하고 서버 재시작 시 그 변경사항들을 순차적으로 재실행함
Redis 데이터 타입
String, Bitmaps, Lists, Hashes, Sets, Sorted Sets, HyperLogLogs, Streams
Redis는 어디에 사용해야하는걸까?
- 캐싱(Caching) : 자주 조회되는 데이터를 미리 저장해두고 빠르게 제공하기 위해 사용 ex) 인기 상품 목록을 Redis에 저장하여 DB 부하 감소
- 세션 관리(Session Management) : 웹 애플리케이션에서 로그인 세션 정보를 빠르게 저장하고 조회, 사용자의 로그인 상태를 Redis에 저장(분산 황경에서 여러 서버가 동일한 세션 정보를 공유할 때 활용)
- 메시지 큐(Message Queue) : 비동기 처리 및 대량의 데이터를 빠르게 처리하기 위해 사용 ex) Redis Pub/Sub 을 이용한 실시간 채팅 시스템
- 실시간 데이터 처리 : 빠른 읽기/쓰기가 필요한 실시간 데이터 저장소로 활용 ex) 실시간 랭킹 시스템, 소셜 네트워크의 “좋아요” 또는 “팔로우” 기능 구현
- 분산 락(Distributed Lock) : 여러 서버에서 동시에 같은 리소스에 접근할 때 충돌을 방지 ex) Redlock 알고리즘을 이용한 동시성 제어
Redis 복구 방법
RDB Save : redis.conf에서 자동 save 옵션, 수동으로는 BGSAVE 커맨드를 통한 RDB파일 저장
AOF : redis.conf파일에서 auto-aof-rewrite-percentage 옵션
사용하지 않는 것이 좋은 경우는?
- 대량의 영구 저장이 필요한 경우
- 트랜잭션이 중요한 경우
- 복잡한 쿼리가 필요한 경우(JOIN, GORUP BY, WHERE 같은 복잡한 쿼리를 지원하지 않음)
Spring Boot 프로젝트에 Redis 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'com.fasterxml.jackson.core:jackson-databind'
}
RedisConfig.java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
redis를 쓰는 곳 예시(이해를 위해 프로젝트 했던 것 위주)
뉴스피드 기준
- 뉴스피드 캐싱 : 사용자가 뉴스피드를 요청하면 먼저 Redis에 캐싱된 데이터가 있는지 확인 → 없으면 DB에서 가져와 Redis에 저장(Cache-aside 패턴) / 뉴스피드가 업데이트될 때만 Redis 데이터를 갱신하여 불필요한 DB 조회를 최소화
- 메시징 시스템(채팅) : Redis의 Pub/Sub 기능을 활용하여 메시지를 실시간으로 전달
- 좋아요 및 댓글 카운트 : INCR(증가), DECR(감소) 연산을 사용하여 개수를 즉시 반영
- 실시간 알림 시스템 : Redis의 Sorted Set을 이용하여 사용자의 알림을 시간순으로 정렬, 새로운 알림이 오면 Redis에 저장하고 사용자가 확인하면 제거
- 비정상적인 로그인 감지 : 사용자의 로그인 시도를 Redis에 저장, 일정 횟수 넘어가면 Redis에서 차단 처리 → ex) "failed_login:user123" 또는 "failed_login:192.168.1.10" 같은 키를 생성하여 실패 횟수를 저장
- 비디오 및 이미지 처리 : Redis를 사용해 CDN과 연동하며 이미지/비디오 URL을 캐싱, 인코딩된 썸네일이나 미리보기 이미지를 Redis에 저장하여 빠른 로딩 제공
배달앱 기준 예시
- 메뉴 정보
//가게의 메뉴 얻어오기
String menuKey = "menu:" + storeId;
List<Menu> menuList = (List<Menu>) redisTemplate.opsForValue().get(key);
if(menuList != null) {
return menuList;
}
menuList = menuRepository.findByStoreId(storeId);
redisTemplate.opsForValue().set(key, menuList,{만료시간}, TimeUnit.SECONDS);
return menuList;
- 음식점 목록 검색(Redis의 GeoSpatial 기능 사용)
- 주문 상태
//주문 상태 업데이트
String orderKey = "order:" + orderId;
Map<String, String> orderStatus = new HashMap<>();
orderStatus.put("상태", status);
redisTemplate.opsForValue().set(orderKey, orderStatus, {만료시간}, TimeUnit.SECONDS);
- 인기 메뉴 (Sorted Set) 맨 처음 1개만… 일단 주문이 들어올때마다 횟수가 1 증가하는 것을 기준으로 하고, 기존 DB에 저장하는 방법이 그때마다 하는 방법 << 뭔가 비효율적. 배치처리(주기적 업데이트) << 매일?매시간? 근데 이렇게하면 일간, 주간 이런식의 순위 매김이 쉬울듯. 검색어 순위도 이런거 이용할려나? Spring에는 @Scheduled 어노테이션을 사용해서 배치 작업 자동화가 가능하다고함
//인기 메뉴 업데이트
String key = "store:" + stroeId + ":popular:menu";
Map<String, Integer> popularMenus = (Map<String, Integer>) redisTemplate.opsForValue().get(key);
if (popularMenus = null) {
popularMenus = new HashMap<>();
List<Menu> menuList = menuRepository.findByStoreId(storeId);
for(Menu menu : menuList) {
popularMenus.put(menu.getName(), 0);
}
}
popularMenus.put(menuName, popularMenus.getOrDefault(menuNmae, 0) +1);
redisTemplate.opsForValue().set(key, popularMenus, {만료시간}, TimeUnit.SECONDS);
//인기 메뉴 조회
return (Map<String, Integer>) redisTemplate.opsForValue().get(key);
- 지역별 음식점 목록
- 프로모션 정보 → 할인쿠폰 뿌리기도 가능
- 쿠폰 수량을 100으로 가정했을 때 “discount:coupons”로 키를 설정하고 값으로 100을 저장
- 요청이 들어올때마다 쿠폰 수량 감소, 쿠폰 발급 여부 처리
- 쿠폰 발급 내역을 RDB에 저장, (유저아이디, 쿠폰 종류, 만료 시간 등의 구성으로 가정)