야구 티켓 예매 시스템을 개발하는 프로젝트입니다.
- **2024-10 ~**
-
실제 예매 사이트 사용 경험
- 티켓 예매 사이트는 쇼핑몰 외에 가장 자주 이용하는 서비스입니다.
- 특히, 야구 경기를 자주 관람하며 대기열과 좌석 자동 예매의 불편함을 직접 경험하였고, 이를 직접 구현해 보고자 프로젝트를 시작했습니다.
-
대규모 트래픽 환경에 대한 학습
- 티켓팅 오픈 시 수만 명의 사용자가 동시에 몰리는 환경에서, 서버가 어떻게 동작하는지 원리를 이해하고 싶었습니다.
- 동시 접속자가 많을 때 발생하는 성능 문제를 해결하는 방법을 학습하고 적용해 보고자 하였습니다.
-
예매 시스템의 특수성
- 일반적인 이커머스 시스템과는 다르게, 예매 시스템은 실시간 대기열, 좌석 배정, 결제 시스템이 밀접하게 연관되어 있습니다.
- 이러한 특수한 기능을 직접 구현하며 개발 역량을 키우기 위해 프로젝트를 기획하였습니다.
- 대규모 트래픽을 고려한 대기열 처리
- 많은 사용자가 동시 접근할 수 있는 좌석 예매 로직
- 비동기 결제 시스템을 통한 성능 최적화
- Backend: Spring Boot, Java, JPA
- Database: MySQL
- Caching & Messaging: Redis, Kafka
- Containerization: Docker
- CI/CD 및 배포: Docker Compose, AWS (예정)
- 사용자가 티켓 예매 페이지에 접근하면 자동으로 대기열에 추가됨.
- 대기열은 120명 단위의 그룹으로 관리되며, 그룹별로 순차적으로 예매 시스템에 접근 가능.
- 대기열에서 벗어날 수 있는 상태가 되면 클라이언트가 이를 확인 가능.
- 사용자의 대기 상태를 확인하는 Polling API 제공.
- 대기열 상태는 Redis에 저장되며, 일정 시간이 지나면 자동으로 업데이트됨.
- 클라이언트는
/queue/status
API를 통해 주기적으로 상태를 확인하고, 대기열에서 벗어나면 예매 페이지로 리다이렉트됨.
- Redis 기반의 대기열 데이터 관리
List
자료구조를 활용하여 순차적으로 사용자 처리.- 대기열 그룹을 120명 단위로 나누어 저장하여 효율적인 관리 가능.
- AtomicInteger + Redis를 활용한 동기화
- 여러 사용자가 동시에 대기열에 진입할 때 현재 그룹을 동기화하여 데이터 일관성 유지.
- 여러 사용자가 동일한 대기열 그룹에 접근할 경우, Sharded Lock을 활용하여 충돌 방지.
acquireLockWithSharding
메서드를 통해 그룹별로 락을 부여하고, 순차적인 작업 수행을 보장.- Redis의 TTL(Time To Live) 설정을 통해 락이 자동 해제되도록 처리.
- 사용자가 대기열을 벗어나면 자동으로 좌석 선택 페이지로 이동할 수 있도록 상태 업데이트.
- Redis에 저장된 대기열 상태를 기반으로, 이동 가능하면 클라이언트에서
"/seats/sections"
URL을 반환받음.
- 사용자가 특정 좌석을 직접 선택 가능.
- 선택한 좌석이 이미 예약된 좌석인지 확인 후, 예약 가능하면 "RESERVED" 상태로 변경.
- 좌석 예약 데이터는 Redis와 MySQL을 동기화하여 관리.
- 사용자가 직접 좌석을 선택하지 않고, 자동 좌석 배정 기능 제공.
- 연속된 좌석을 우선 배정하며, 부족할 경우 같은 행 내 다른 좌석을 검색.
- 예약 가능한 좌석이 없으면 새로운 좌석을 생성하여 배정.
- 좌석 데이터는 Redis에서 우선 조회 후, 필요 시 DB에서 추가 조회하여 성능 최적화.
- 좌석 상태를 Redis에 캐싱하여 성능 최적화.
- 예약된 좌석은
seat:<scheduleId>:<seatId>
키로 Redis에 저장. - 좌석 데이터 조회 시 우선적으로 Redis에서 확인 후, 필요 시 DB에서 조회하여 성능 개선.
- **TTL(시간 제한)**을 적용하여 일정 시간이 지나면 좌석이 자동 해제되도록 설계.
- Redis 기반의 분산 락을 활용하여 동일 좌석이 동시에 예약되는 문제 방지.
acquireLock
메서드를 통해 좌석 선택 시 락을 획득하여 다른 사용자가 동일 좌석을 예약하지 못하도록 설정.- 좌석이 예약되면 **비동기 방식(CompletableFuture)**으로 상태 업데이트하여 성능 최적화.
- 사용자가 좌석을 예약한 후 결제를 요청하면, Kafka를 통해 비동기적으로 결제 프로세스 진행.
- 결제 요청 시, **결제 요청 ID(requestId)**를 생성하여 Redis에 저장하며, 초기 상태는
"PENDING"
으로 설정. - Kafka 이벤트가 처리되면서 결제 상태가
"COMPLETED"
또는"FAILED"
로 업데이트됨.
- Kafka를 활용하여 비동기 결제 시스템을 구현하여 성능 최적화.
- 결제 요청(Payment Requested) → Kafka Producer → Kafka Consumer → 결제 처리(Payment Processed) → 결제 완료(Payment Completed) 방식으로 처리.
- 결제 완료 이벤트는 이메일 알림 서비스로 전달되어 고객에게 결제 완료 이메일을 자동으로 발송.
- 결제 요청 상태를 Redis에 저장하여 Polling 방식으로 상태 확인 가능.
- 결제 요청이 들어오면
payment:status:<requestId>
키로 "PENDING" 상태 저장. - Kafka에서 결제 처리가 완료되면 결제 상태를
"COMPLETED"
또는"FAILED"
로 업데이트. - 사용자는
/payments/{requestId}/status
API를 호출하여 실시간으로 결제 상태 확인 가능.
- 결제 과정에서 오류 발생 시, 결제 상태를
"FAILED"
로 변경하고 예약된 좌석을 다시 “AVAILABLE”로 복구. - Redis에 저장된 결제 메시지도
"결제 실패: <오류 메시지>"
로 업데이트됨.
- 결제가 완료되면 Kafka 이벤트가 “payment-completed” 토픽으로 전송.
- 이메일 알림 서비스(
EmailNotificationEventListener
)가 이를 감지하고 사용자 이메일로 결제 완료 알림 발송. - 이메일 내용에는 결제 금액, 예약 번호 등의 정보 포함.