동시성 문제
동일한 자원(data)에 대해 여러 스레드가 동시에 접근하면서 발생하는 동시성 문제는 상품의 재고 관리 등 프로세스의 여러 곳에서 발생할 수 있다. 내가 개발했던 게임 서비스에서는, 웹소켓을 통해 실시간으로 다수의 사용자에 대한 요청을 처리하기에 동시성 문제가 발생할 여지가 있었다.
나의 경우에는 게임 종료 시 해당 게임을 진행한 플레이어들의 점수에 따른 경험치와 레벨 반영을 종료 후 게임 종료 버튼이 눌렸을 때 요청을 받기로 했었다. 결과 화면과 게임 종료 버튼이 한 화면안에 있고, 결과 화면을 볼 수 있는 시간 20초 동안 게임 종료 버튼을 누를 수 있다. 20초가 지나면 자동으로 게임 종료가 된다.
여러 플레이어들이 20초가 끝나기 전에 게임 종료 요청을 여러번 보내게 되고, 그러면 매번 요청이 들어와서 경험치 반영이 중복으로 발생하게 되는 문제가 있었다.
이러한 동시성 문제를 해결하는 방법을 고민했고, 그 중 하나인 Redisson을 통한 분산 락을 통해 동시성 문제를 해결했다.
•
동시성 문제(Concurrency Issue) : 하나의 스레드가 데이터를 수정 중인 상황에서 다른 스레드에서 수정 전의 데이터를 조회하여 수정함으로써 데이터의 정합성(consistency)이 깨지는 문제
•
분산 락(Distributed Lock) : 경쟁 상황(race condition)에서 하나의 공유자원에 접근할 때, 데이터의 결함이 발생하지 않도록 원자성(atomic)을 보장하는 기법
프론트의 디바운스 및 쓰로틀링
동시성 문제에 대해 고민할 때, 최대한 게임 프로세스 개선을 통해 풀어보려 했지만 서비스 요구사항 특성상 불가능한 부분이었고, 다음 생각한 건 프론트쪽에서 다중 요청에 대한 부분을 미리 처리해서 보내주면 되는거 아닐까 하는 것이었다.
디바운스나 쓰로틀링을 적용하면 어떨까 했는데, 디바운스는 해당 부분의 연이은 호출을 그룹화하고, 특정 시간이 지난 후 가장 마지막의 호출만을 보내는 것이다. 쓰로틀링은 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것이다.
물론 허점은 있다. 위의 기술들은 더블 클릭과 같은 단일 브라우저 내에서의 동시 요청은 막을 수 있지만, 멀티 브라우저에서의 동시 요청은 판별할 수 없다.
나의 경우에는 소켓 통신을 통해 실시간으로 게임이 진행되었기에 멀티 브라우저가 애초에 불가능해서 아마 디바운스가 가장 적절한 선택이었던 것 같은데, 프론트 측 개발 시간에 대한 이슈가 있어서 서버 쪽에서 처리하기로 했다.
Synchronized
동시성 처리에 대한 대표적인 방법으로는 Java의 Synchronized 키워드가 있다. 어드민
Redisson
Redis에서는 분산 락을 구현하기 위해 다양한 구현체를 제공하는데, Redisson은 Redis를 Java에서 쉽게 사용할 수 있게 도와주는 클라이언트 라이브러리이다. 특히, 분산 락, 캐시, 세마포어, 큐, 맵 등 고수준 구조를 쉽게 구현할 수 있도록 도와준다.
Lettuce가 아닌 Redisson을 사용하는 이유 (락 획득 방식의 차이)
Spring Boot 2.0 부터는 Netty(비동기 이벤트 기반 고성능 네트워크 프레임워크) 기반의 Lettuce가 Redis의 기본 클라이언트로 사용되고 있다.
Lettuce