728x90

RedissonClient는 데이터베이스, 캐시, 메시지 브로커 등 일반적으로 사용되는 오픈 소스 인메모리 데이터 구조인 Redis에 사용할 수 있는 자바 클라이언트 라이브러리입니다. RedisClient는 Redis와 함께 사용하는데 도움을 주는 고급 인터페이스를 제공하는데, 분산 잠금, 스케줄링 등 기능을 제공합니다.

저는 RedisClient의 분산락 기능을 활용하여 해당 문제를 해결하였습니다.

 

먼저, Spring에서 RedisClient를 사용하기 위해서는 해당 라이브러리 의존성을 주입하고 config 설정을 해야 합니다.

 

implementation 'org.redisson:redisson-spring-boot-starter:3.17.7'
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6381");
        return Redisson.create();
    }
}

 

 

RedissonClient에서 분산락 설정을 위해 사용한 기능은 아래와 같습니다.

 

RLock: RLock은 여러 스레드 또는 프로세스 간에 공유 리소스에 대한 액세스를 동기화하는 방법을 제공하는 Redisson 분산락 기능입니다. 

 

lock.tryLock(): RLock의 tryLock() 메서드는 즉시 잠금을 획득하려고 시도하여 잠금을 획득한 경우, true를 반환하고, 잠금을 획득하지 않은 경우 false를 반환합니다.

 

lock.unlock(): 해당 메소드는 이전에 획득한 잠금을 해제합니다. 보통 try-catch-finally의 finally에 작성하여 해당 락이 종료되도록 하여 데드락이 발생하는 것을 방지할 수 있습니다.

 

lock.isHoldByCurrentThread(): 잠금이 현재 스레드에 의해 유지되고 있는지 확인하여, 맞다면 true, 아니라면 false를 반환합니다.

 

// 좋아요 버튼을 눌렀을 때
@PostMapping("/posts/{postId}/like")
public ResponseEntity<?> like(@PathVariable("postId") Long postId, Authentication authentication) {

    // Redis 분산 락을 postId에 기반하여 생성
    RLock lock = redissonClient.getLock("post-like-lock:" + postId);

    try {
        // 락을 5초 동안 시도하고, 락을 얻으면 3초 후 자동 해제
        boolean isLocked = lock.tryLock(5, 3, TimeUnit.SECONDS);
        if (isLocked) {
            if (authentication != null && authentication.isAuthenticated()) {
                String username = authentication.getName();
                Optional<User> userOptional = userService.findByUserName(username);

                if (userOptional.isPresent()) {
                    User user = userOptional.get();
                    Optional<Post> postOptional = postService.getPostById(postId);

                    if (postOptional.isPresent()) {
                        Post post = postOptional.get();
                        boolean liked = likeService.like(post, user);
                        Long likeCount = likeService.countByPostId(postId);

                        String postOwnerUsername = postService.getPostOwnerUsername(postId);
                        if (!username.equals(postOwnerUsername)) {
                            notificationService.createNotification(postOwnerUsername, username + "님이 게시글에 좋아요를 눌렀습니다.");
                        }
                        return ResponseEntity.ok().body(new LikeResponse(liked, likeCount));
                    }
                }
            }
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("사용자를 찾을 수 없습니다.");
        } else {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("잠금 획득에 실패했습니다. 잠시 후 다시 시도해 주세요.");
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("잠금 중 오류가 발생했습니다.");
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

 

 

 

 

 

 

728x90

+ Recent posts