본문 바로가기

Redis

[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에 입력한 요소의 역순위를 조회

Sorted Set 명령어 예제

명령어는 간단하게 이정도만 알아보도록 하고 이 명령어를 기반으로 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으로 저장된 것을 확인할 수 있었습니다.