블로그 게시물 조회수기능을 Redis 사용을 통해 구현하기

이효곤·2023년 4월 15일

트러블슈팅

목록 보기
7/7

목표

1. 캐시를 사용하여 조회수가 매번 db에 접근하는 낭비를 막는다.

2. 똑같은 사용자가 같은 게시물이 접근해도 조회수가 중복으로 증가하는 문제 해결

Redis 설정



@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

다음 글을 참고하여 직렬화 방법을 선택하였습니다.
https://velog.io/@bagt/Redis-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94-%EA%B4%80%EB%A0%A8-%EC%97%90%EB%9F%AC-feat.-RedisSerializer

StringRedisSerializer()의 특징

  • 특정 클래스에 종속적이지 않음
  • 쓰레드간의 문제가 발생하지 않는다.
  • encoding, decoding을 해줘야한다는 단점

나중에 msc 확장할 생각이 있기 때문에, StringRedisSerializer를 선택했습니다.

	@GetMapping("/posts/{postId}")
    public ResponseEntity<ResponsePostOne> getPostOne(
            @PathVariable Long postId) {
        postService.addViewCntToRedis(postId);
        ResponsePostOne postOne = postService.getPostOne(postId);
        return ResponseEntity.status(HttpStatus.OK).body(postOne);
    }

다음과 같은 Controller Api를 등록했습니다.
Post 하나를 조회 시 , addViewCntToRedis 메서드를 이용하여 조회수를 1 증가시키는 코드를 작성했습니다. 코드는 다음과 같습니다.

public void addViewCntToRedis(Long postId) {
        String key = getKey(postId); //key생성 전략에 맞춰서 key값 얻는 메서드
        
        //캐시에 값이 없으면 레포지토리에서 조회 있으면 값을 증가시킨다.
        ValueOperations valueOperations = redisTemplate.opsForValue();
        if(valueOperations.get(key)==null){
            valueOperations.set( key,
                    String.valueOf(postRepository.findById(postId)
                    .orElseThrow(() -> new NotFoundException("게시물을 찾을 수 없습니다.")).getViews()),
                    Duration.ofMinutes(10));
            valueOperations.increment(key);
        }
        log.info("value:{}",valueOperations.get(key));
    }

캐시에 키에 해당하는 값이 없으면 db에서 조회한 뒤 캐시에 등록하고, 조회수를 1 증가 시켜주는 코드입니다.

Duration.ofMinutes와 같이 캐시에서 데이터가 존재할 수 있는 시간을 설정할 수 있습니다.
인메모리에 데이터가 존재하는 동안은, 계속 Post를 Get메서드로 다시 조회를 하더라도, 조회수가 증가되지 않으므로, 새로고침을 통한 조회수 중복 증가를 막을 수 있습니다.

다음과 같이 포스트를 조회 시,

Redis-cli.exe를 통해 캐시에 데이터가 등록된 것을 확인할 수 있습니다.

다음은 캐시에 등록된 수정된 조회 수를 db에 반영하기 위해 스케쥴러를 사용하는 코드 입니다.


메인 클래스에 @EnableScheduling을 등록하신 뒤 ,

 @Scheduled(cron = "0 0/1 * * * ?")
    public void deleteViewCntCacheFromRedis() {
        Set<String> redisKeys = redisTemplate.keys("post:*:views");
        Iterator<String> it = redisKeys.iterator();
        while (it.hasNext()) {
            String key = it.next();
            String numericPart = key.substring(key.indexOf(":") + 1, key.lastIndexOf(":"));
            Long postId = Long.parseLong(numericPart);
            String s = (String) redisTemplate.opsForValue().get(key);
            Long views = Long.parseLong(s);
            //
            postQueryRepository.addViewCntFromRedis(postId,views);
            redisTemplate.delete(key);
            redisTemplate.delete("post:"+postId+"views");
        }
    }

postService에 다음과 같이 1분마다 캐시를 db에 반영하는 스케쥴러를 이용한 db업데이트 코드입니다.

1분이 지난후 다시 검색을 해보면, 업데이트 쿼리가 날라가고 캐시가 비워진 것을 확인할 수 있습니다.

profile
develop

0개의 댓글