Skip to main content

Command Palette

Search for a command to run...

2.Redis를 사용한 동시성 제어

Published
3 min readView as Markdown
T

안녕하세요! Web개발을 공부하고 있는 윤종일 입니다. 현재는 Java 백엔드 개발을 깊게 파고들고 있어요! 제 궁극적인 목표는 풀스택 개발자가 되는 것입니다. 프론트엔드와 백엔드 모두를 자유롭게 넘나들며, 사용자에게 가치를 전달할 수 있는 완성도 높은 애플리케이션을 만드는 것이 목표입니다.

GitHub 링크

동시성 제어란?

  • 동시성 제어는 여러 사용자 또는 프로세스가 동시에 데이터에 접근할 때 데이터의 일관성과 무결성을 유지하기 위한 기술이다.

분산 락을 이용한 동시성 제어

  1. 게시글 CRUD 를 간단하게 구현하고 게시글에 좋아요 기능을 추가

  2. parallel() 메서드를 사용하여 병렬 처리를 통해 좋아요 기능 수행.

  3. 각 좋아요 클릭 이벤트가 게시글에 영향을 주기 전에 분산 락을 획득하여 데이터 일관성 유지.

  4. 처리가 끝난 후 락 해제하여 다음 이벤트 처리 가능하도록 함.

사용한 라이브러리

  • redissonClient

실습코드

Post Entity

@Entity
@Getter
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String content;
    private int likeCount;
    @CreatedDate
    private LocalDateTime createdAt;

    public Post(PostRequest postRequest) {
        this.title = postRequest.getTitle();
        this.content = postRequest.getContent();
        this.likeCount = 0;
    }
    public void updatePost(PostRequest postRequest){
        this.title = postRequest.getTitle();
        this.content = postRequest.getContent();
    }
    public void setCount(int likeCount){
        this.likeCount = likeCount;
    }

}

Post Service(좋아요 관련 기능)

   //좋아요 초기화
    @Transactional
    public void reset(){
        Post post = postRepository.findById(1L).orElseThrow(()->new IllegalArgumentException("게시글이 없습니다."));
        post.setCount(0);
        postRepository.save(post);
    }
    //레디스 락을 이용한 좋아요 증가
    //분산 락을 획득하는 메서드
    public PostResponse updateLikeUsingLock(Long id) {
        //락 키
        RLock lock = redissonClient.getFairLock(LOCK_KEY);
        boolean isLocked = false;
        try {
            // 락 획득 시도
            isLocked = lock.tryLock(10, 60, TimeUnit.SECONDS);
            if (isLocked) {
                // 락 획득에 성공하면, 트랜잭션을 포함하는 데이터베이스 작업을 수행
                return postLikeService.updateLikeCount(id);
            } else {
                // 락 획득에 실패하면 예외 발생
                throw new IllegalStateException("잠금을 획득하지 못했습니다.");
            }
        } catch (InterruptedException e) {
            // 현재 스레드가 잠금을 획득하는 동안 인터럽트 발생. 스레드에 인터럽트 상태를 설정하고 예외를 던짐.
            Thread.currentThread().interrupt();
            throw new IllegalStateException("잠금 획득 중단", e);
        } finally {
            if (isLocked) {
                // 반드시 락 해제
                lock.unlock();
            }
        }
    }
    //레디스 캐시 데이터 삭제
    public void deleteCache(Long postKey) {
        String Key = "post: " + postKey;
        stringRedisTemplate.delete(Key);
    }
    //좋아요 출력 코드
    public void printCount() {
        System.out.println("\n\n\n====================\n");
        System.out.println(postRepository.findById(1L).orElseThrow().getLikeCount());
        System.out.println("\n====================\n\n\n");
    }

테스트 코드

@SpringBootTest
public class PostServiceTest {
    private PostRequest postRequest;
    @Autowired
    private PostService postService;
    @BeforeEach
    void set() {
        postRequest = new PostRequest("test", "content");
        postService.create(postRequest);
    }
    @AfterEach
    void reset() {
        postService.reset();
    }

    @Test
    @DisplayName("단순 좋아요 증가 코드")
    void test() {
        IntStream.range(0, 100).parallel().forEach(i -> postService.updateLike(1L));
        System.out.println("\n\n\n\n[테스트]");
        postService.printCount();
    }

    @Test
    @DisplayName("동시성 제어")
    void test2() {
        IntStream.range(0, 100).parallel().forEach(i -> postService.updateLikeUsingLock(1L));
        System.out.println("\n\n\n\n[테스트]");
        postService.printCount();
    }
}

결과

단순 좋아요 증가 코드 결과

  • 예상 좋아요 수: 100

  • 실제 좋아요 수: X (17)

  • 분석: 동시성 제어가 없을 경우 X회만큼의 좋아요가 정상 반영됨. 이는 병렬 처리 중 발생하는 경쟁 상황에 의해 좋아요 수가 손실됨을 알수있음.

스크린샷 2024-04-02 오전 3 57 14

동시성 제어 좋아요 증가 코드 결과

  • 예상 좋아요 수: 100

  • 실제 좋아요 수: Y (100)

  • 분석: 분산 락을 사용한 동시성 제어가 올바르게 작동하여, 모든 병렬 처리가 정확하게 반영되어 Y회의 좋아요가 정상 반영됨.

스크린샷 2024-04-02 오전 4 05 48

개발 중 직면한 문제점

  • 결과 값이 100이 나와야 하는데 50이 나오는 문제 발생

해결책

  • 기존에는 락 획득과 데이터 처리 로직이 함께 있어 동시성 문제가 발생하여 결과가 50이 나왔다.

  • 데이터 처리 로직을 별도의 서비스 클래스로 분리하고 트랜잭션을 적용하여 동시성 제어와 데이터 일관성을 보장함으로써 문제를 해결했다.

@Service
@RequiredArgsConstructor
public class PostLikeService {

    private final PostRepository postRepository;

    @Transactional
    public PostResponse updateLikeCount(Long id) {
        Post post = postRepository.findById(id)
            .orElseThrow(() -> new IllegalStateException("Post not found with id: " + id));
        post.setCount(post.getLikeCount() + 1);
        postRepository.save(post);
        return new PostResponse(post);
    }
}

More from this blog

서브넷 이해하기

서브넷 IPv4 IPv4로 보자면 데이터 전송 방식은 유니캐스트 멀티캐스트 브로드 캐스트다. 32bit 숫자로 구성되어 있다. 일반적으로 4개의 10진수로 표현된다. 데이터 전송 방식 데이터 전송 방식설명 유니캐스트한 대의 컴퓨터가 다른 한 대의 컴퓨터에게 데이터를 보내는 방식 멀티캐스트한 대의 컴ㅂ퓨터가 특정 그룹에 속한 여러 컴퓨터에게 ㄷ이터를 보내는 방식 브로드캐스트한 대의 컴퓨터가 같은 네트워크에 있는 모든 컴퓨터...

Aug 5, 20242 min read3
서브넷 이해하기

네트워크 계층

네트워크 계층 네트워크는 원하는 정보를 원하는 수신자 또는 기기에 정확하게 전송하기 위한 기반 인프라이다. OSI 7계층 (지겹지만 반복) OSI 7계층은 국제 표준화 기구인 ISO(International Standardization Organization)에서 개발한 컴퓨터 네트워크 프로토콜 디자인과 통신을 계층으로 나누어 설명한 개방형 시스템 상호 연결 모델이다. 각 계층은 서로 독립적으로 구성되어 있고, 각 계층은 하위 계층의 기...

Jul 29, 20243 min read3
네트워크 계층

메모리 단편화

메모리 단편화 메모리 단편화란 분할된 주기억장치에 프로세스를 할당, 반납 과정에서 사용되지 못하고 낭비되는 기억장치가 발생하는 현상이다. 유형으로는 내부 단편화와 외부 단편화가 있다. 내부 단편화 내부 단편화 개념 내부 단편화는 분할된 공간에 프로세스를 적재한 후 남은 공간이다. 내부 단편화는 고정 분할 할당 방식 또는 페이징 기법 사용 시 발생하는 메모리 단편화다. 해결 방안설명 슬랩 할당자 (Slab Allocator)페...

Jul 20, 20241 min read

2024.07.13 애플리케이션 테스트 수행

애플리케이션 테스트 수행 IDE 도구 개념 IDE(Integrated Development Environment) 도구는 코딩, 디버그, 컴파일, 배포 등 프로그램 개발과 관련된 모든 작업을 하나의 프로그램 안에서 처리하는 환경을 제공하는 소프트웨어이다. 기존에는 컴파일러, 텍스트 편집기, 디버거를 따로 사용했으나 통합 개발환경을 통해 하나로 묶은 대화형 인터페이스를 제공한다. IDE 도구의 기능 IDE 도구는 다양한 기능을 통합한 형태...

Jul 12, 20242 min read2
2024.07.13 애플리케이션 테스트 수행
J

Java 백엔드 및 풀스택 개발

111 posts

안녕하세요! Web개발을 공부하고 있는 윤종일 입니다. 현재는 Java 백엔드 개발을 깊게 파고들고 있어요! 제 궁극적인 목표는 풀스택 개발자가 되는 것입니다. 프론트엔드와 백엔드 모두를 자유롭게 넘나들며, 사용자에게 가치를 전달할 수 있는 완성도 높은 애플리케이션을 만드는 것이 목표입니다.

E-Mail : ji4971@naver.com