[Redis] Sorted Set을 활용한 랭킹 서비스 구현 예제
Redis에는 여러가지의 데이터 타입이 존재하는데, 그 중 중복이 허용되지 않고, 자동으로 정렬기능을 제공해주는 Sorted Set을 알아보려고 합니다. 참고로 Sorted Set을 알아본 뒤, 이 자료구조를 활용하여 간단한 랭킹 서비스를 만드는 예제도 공부해보려고 합니다.
Sorted Set 명령어
- ZADD - key에 score와 value를 저장
- ZRANGE - score로 정렬된 데이터를 start, end 인덱스를 입력하여 범위에 포함되는 데이터를 조회. score값이 오름차순으로 정렬된다.
- ZREVRANGE - score로 정렬된 데이터를 start, end 인덱스를 입력하여 범위에 포함되는 데이터를 조회. score값이 내림차순으로 정렬된다.
- ZRANK - key에 입력한 요소의 순위를 조회
- ZREVRANK - key에 입력한 요소의 역순위를 조회
명령어는 간단하게 이정도만 알아보도록 하고 이 명령어를 기반으로 JAVA에서 Redis를 활용하여 랭킹서비스를 간단하게 구현해도록 하겠습니다.
랭킹 서비스 구현예제
랭킹 서비스 구현예제는 영화예매를 기반으로 구현해 보았습니다.
혼자 공부용으로 간단하게 구현한거라 예제코드 작성 후 아래서 설명을 남기도록 하겠습니다.
[build.gradle]
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.session:spring-session-data-redis'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
[application.yml]
spring:
redis:
host: localhost
port: 6379
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: -1
[Controller]
@RestController
public class redisRankingController {
@Autowired
private redisRankingService redisRankingService;
@PostMapping("/ranking")
public ResponseEntity<?> rankingController(String movie) {
Map map = new HashMap();
String result = redisRankingService.addRanking(movie);
map.put("result", result);
return ResponseEntity.ok(map);
}
@GetMapping("/ranking/list")
public ResponseEntity<?> rankingListController() {
List<String> list = redisRankingService.list();
return ResponseEntity.ok(list);
}
}
[service]
@Service
public class redisRankingService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public String addRanking(String movie) {
String rst = "success";
ZSetOperations<String, String> stringStringZSetOperations = stringRedisTemplate.opsForZSet();
Long result = stringStringZSetOperations.reverseRank("movie", movie);
if(result == null) {
stringStringZSetOperations.add("movie", movie, 1);
System.out.println("init => [count : "+count()+"] [reverseRank : "+reverseRank(movie)+"] [movie : "+movie+"]");
} else {
stringStringZSetOperations.incrementScore("movie", movie, 1);
System.out.println("exist => [count : "+count()+"] [reverseRank : "+reverseRank(movie)+"] [movie : "+movie+"]");
}
return rst;
}
public List<String> list() {
ZSetOperations<String, String> stringStringZSetOperations = stringRedisTemplate.opsForZSet();
Set<String> movie = stringStringZSetOperations.reverseRange("movie", 0, 4);
return new ArrayList<>(movie);
}
private Long count() {
ZSetOperations<String, String> stringStringZSetOperations = stringRedisTemplate.opsForZSet();
return stringStringZSetOperations.size("movie");
}
private Long reverseRank(String movie) {
ZSetOperations<String, String> stringStringZSetOperations = stringRedisTemplate.opsForZSet();
return stringStringZSetOperations.reverseRank("movie", movie);
}
}
Controller에는 크게 2개의 API를 구성하였습니다.
하나는 GET방식의 조회 API이고, 다른 하나는 POST방식의 API입니다.
컨트롤러에서 Service의 addRanking이라는 메소드를 호출하면, 파라미터로 넘겨준 movie라는 값을 가지고 redis에서 조회를 합니다. 그 결과값이 null이면 Sorted Set에 데이터를 추가해주고 score은 1로 세팅해줍니다.
만약 그 결과값이 null이 아니면, 해당 요소의 score 값을 +1시켜줍니다.
그러면 이 과정을 PostMan을 통하여 통신을 해보도록 하겠습니다.
dragon1이라는 영화를 2번, dragon2이라는 영화를 3번 예매해보도록 하겠습니다.
![]() |
![]() |
PostMan을 통해 5번의 통신을 하고 난뒤, console창에는 System.out.println로 찍힌 정보가 남았고, redis cli에서 명령어를 통해 조회를해보니 dragon1은 score가 2, dragon2는 score가 3으로 저장된 것을 확인할 수 있었습니다.