Architecture Journal
JavaSpringBootDistributedSystems
Lucky Draw System
Lucky Draw System 是一套面向電商活動的分散式轉盤抽獎平台。系統把登入、活動、抽獎與庫存拆成獨立服務,讓流量控制、抽獎邏輯與庫存真相各自演進,也能在高併發下維持一致的抽獎結果。
系統架構
平台由四個服務組成,每個服務只操作自己的資料域,跨服務則透過 API 或事件溝通:
- API Gateway:統一處理 JWT 驗證、限流、冪等 Header 檢查與路由。
- Auth Service:負責註冊、登入、RS256 JWT 簽發,以及 token 白名單與登出撤銷。
- Campaign Service:管理活動與獎品,執行權重抽獎、批次抽獎、冪等重播與 Redis 預扣。
- Inventory Service:作為庫存唯一真相,處理條件扣減、失敗補償與定期帳目校對。
Campaign Service 會透過 RabbitMQ 發送庫存扣減事件,Inventory Service 非同步消費並完成最終扣庫存。這樣可以把抽獎請求的即時回應與後端庫存協作拆開,降低服務之間的直接耦合。
核心設計
Redis 加速,資料庫定案
抽獎流程採用兩段式庫存處理:
- Redis Lua script 以原子操作檢查並預扣庫存,讓高併發請求可以快速完成庫存判定。
- Inventory Service 使用資料庫條件更新,只在
stock >= qty時扣減,避免庫存變成負數。 - 如果最終扣減失敗,系統會建立補償結果、發出告警,並透過對帳讓 Redis 與資料庫重新收斂。
Redis 負責低延遲與併發控制,資料庫則保留最終真相。當庫存不足時,抽獎直接降級為銘謝惠顧,不重新抽取其他獎品。
三層冪等防線
每次抽獎都必須帶上 Idempotency-Key。同一個請求會依序經過:
- Redis
SETNX冪等鎖,擋住短時間內的重複請求。 - 資料庫唯一鍵,確保多個服務實例同時處理時仍不會重複寫入。
- Replay 機制,重送相同請求時回傳原本的抽獎結果,不重抽、不重扣庫存。
事件驅動的庫存協作
Campaign Service 會發布兩種事件:inventory-commit 用於中獎扣減,prize-stock-configured 用於同步獎品數量變更。事件帶有可重複處理的識別鍵,讓消費端可以安全重試,也方便日後替換訊息基礎設施。
技術棧
- 語言與框架:Java 21、Spring Boot 3.3、Spring Cloud Gateway、Spring Security
- 資料庫:開發環境使用 H2,正式環境使用 PostgreSQL 與 Flyway
- 快取與併發控制:Redis、Lua、冪等鎖、限流計數與 token 白名單
- 訊息傳遞:Spring Cloud Stream + RabbitMQ,保留切換 Kafka 的彈性
- 測試與契約:JUnit 5、Testcontainers、OpenAPI 3.0、MapStruct