λκ·λͺ¨ νΈλν½ μν©μμ μμ€ν λ€μ΄μ λ°©μ§νκ³ , 곡μ ν μμ°¨ μ²λ¦¬λ₯Ό 보μ₯νκΈ° μν Redis κΈ°λ° λκΈ°μ΄ μμ€ν νμ΅ νλ‘μ νΈμ λλ€.
μ¬μ©μκ° μλΉμ€μ μ§μ μ§μ νλ λμ , Redis λκΈ°μ΄μ κ±°μ³ μμ°¨μ μΌλ‘ μ μ₯νλλ‘ μ μ΄ν©λλ€. ν°μΌν , μκ° μ μ², μ μ°©μ μ΄λ²€νΈ λ± μκ°μ μΈ νΈλν½ νμ£Όκ° μμλλ μλ리μ€λ₯Ό ν΄κ²°νλ λ° μ΄μ μ λ§μ·μ΅λλ€.
- Language: Java 25
- Framework: Spring Boot 4.0.6
- Reactive: Spring WebFlux, Spring Data Redis Reactive (queue-server)
- Web: Spring Web MVC, Thymeleaf (website)
- Database: Redis
- Test: JUnit 5, Reactor Test, Embedded Redis
Gradle λ©ν° λͺ¨λ ꡬ쑰λ₯Ό μ¬μ©νμ¬ κ΄μ¬μ¬λ₯Ό λΆλ¦¬νμ΅λλ€.
common: κ³΅ν΅ DTO, μμΈ ν΄λμ€ λ° κ³΅ν΅ μ νΈλ¦¬ν°queue-server: [WebFlux] λκΈ°μ΄ λ±λ‘, μλ² μ‘°ν, μ§μ νμ© μ²λ¦¬ λ° μ€μΌμ€λ¬ ν΅μ¬ λ‘μ§website: [MVC] μ€μ μλΉμ€κ° μ 곡λλ μΉ μ ν리μΌμ΄μ (λκΈ°μ΄ ν΅κ³Όμ μ μ©)
- μ§μ
μλ: μ¬μ©μκ°
websiteμ μ μ μΏ ν€μ λκΈ°μ΄ ν ν° νμΈ - λκΈ°μ€ λ¦¬λ€μ΄λ νΈ: μ ν¨ν ν ν°μ΄ μμ κ²½μ°
queue-serverμ λκΈ° νμ΄μ§λ‘ μ΄λ - μλ² ν΄λ§: λκΈ°μ€μμ λ³ΈμΈμ μλ²μ μ£ΌκΈ°μ μΌλ‘ μ‘°ν (Redis Sorted Set κΈ°λ°)
- μλ μ§μ
μ²λ¦¬:
queue-serverλ΄λΆ μ€μΌμ€λ¬κ° μ€μ λ μΈμλ§νΌ μμ°¨μ μΌλ‘ μ§μ νμ© μνλ‘ λ³κ²½ - ν ν° λ°κΈ λ° λ³΅κ·: μ§μ
μ΄ νμ©λλ©΄ ν ν°μ λ°κΈλ°μ μΏ ν€μ μ μ₯ν λ€ μλ
websiteλ‘ λ°λ‘ 볡κ·
Redisμ Sorted Setμ νμ©νμ¬ μ μ°©μ(FIFO) ꡬ쑰λ₯Ό ꡬννμ΅λλ€.
- Wait Queue:
users:queue:{name}:waitscore: λ±λ‘ μκ°(Timestamp) /member: μ¬μ©μ μλ³μ
- Proceed Queue:
users:queue:{name}:proceed- μ§μ μ΄ νμ©λ μ¬μ©μ μ 보 (ν ν° κ²μ¦ μ νμ©)
- λΉλκΈ° μ²λ¦¬: WebFluxμ Reactive Redisλ₯Ό μ¬μ©νμ¬ λμ©λ μμ²μ ν¨μ¨μ μΌλ‘ μ²λ¦¬
- μλ μ€μΌμ€λ§: μ΄μμμ κ°μ μμ΄ μ€μ μ λ°λΌ μλμΌλ‘ λκΈ° μΈμ μ§μ μ²λ¦¬
- μ€λ³΅ λ°©μ§: λμΌ μ¬μ©μμ μ€λ³΅ λκΈ°μ΄ λ±λ‘ μ°¨λ¨
- 격리λ ꡬ쑰: λκΈ°μ΄ λ‘μ§κ³Ό μ€μ μλΉμ€ λ‘μ§μ λͺ¨λλ‘ λΆλ¦¬νμ¬ μ μ°μ± ν보
sequenceDiagram
autonumber
participant User as μ¬μ©μ λΈλΌμ°μ
participant Website as Website μλ²
participant Queue as Queue Server
participant Redis as Redis
User->>Website: GET /?user_id={userId}
Website->>Website: μΏ ν€μμ user-queue-{queue}-token μ‘°ν
Website->>Queue: GET /api/v1/queue/allowed<br/>queue={queue}&user_id={userId}&token={token}
Queue->>Queue: ν ν° μ ν¨μ± κ²μ¦
Queue-->>Website: { allowed: true | false }
alt μ§μ
νμ©λ¨
Website-->>User: index.html λ°ν
else μ§μ
λΆκ°
Website-->>User: 302 Redirect<br/>/waiting-room?user_id={userId}&redirect_url={websiteUrl}
User->>Queue: GET /waiting-room
Queue->>Queue: μΏ ν€ ν ν° μ¬κ²μ¦
alt μ ν¨ν ν ν° μμ
Queue-->>User: 302 Redirect to Website
else μ ν¨ν ν ν° μμ
Queue->>Redis: ZADD users:queue:{queue}:wait<br/>member=userId, score=timestamp
Redis-->>Queue: λ±λ‘ κ²°κ³Ό
Queue->>Redis: ZRANK users:queue:{queue}:wait userId
Redis-->>Queue: νμ¬ μλ²
Queue-->>User: waiting-room.html λ°ν
end
loop 1μ΄λ§λ€ μλ² μ‘°ν
User->>Queue: GET /api/v1/queue/rank<br/>queue={queue}&user_id={userId}
Queue->>Redis: ZRANK users:queue:{queue}:wait userId
Redis-->>Queue: rank
Queue-->>User: { rank: number }
end
par μ€μΌμ€λ¬ μλ μ²λ¦¬
Queue->>Redis: ZPOPMIN users:queue:{queue}:wait
Redis-->>Queue: λκΈ°μ΄ μ μ¬μ©μ λͺ©λ‘
Queue->>Redis: ZADD users:queue:{queue}:proceed
end
User->>Queue: GET /api/v1/queue/rank
Queue->>Redis: ZRANK users:queue:{queue}:wait userId
Redis-->>Queue: -1
Queue-->>User: { rank: -1 }
User->>Queue: GET /api/v1/queue/touch<br/>queue={queue}&user_id={userId}
Queue->>Queue: SHA-256 ν ν° μμ±
Queue-->>User: Set-Cookie: user-queue-{queue}-token={token}
User->>Website: GET /?user_id={userId}
Website->>Queue: GET /api/v1/queue/allowed
Queue-->>Website: { allowed: true }
Website-->>User: index.html λ°ν
end