CodeFarmer 技術週報 #12 - 系統設計面試:設計一個購票系統 (1)
剛好前陣子在準備全端職缺的系統設計面試時有遇到「設計一個搶票系統」這題,今天就參考 Hello Interview 的解說來做個筆記
Hi,歡迎閱讀第 12 期的 CodeFarmer 技術週報!
剛好前陣子在準備全端職缺的系統設計面試時有遇到「設計一個搶票系統」這題,今天就參考 Hello Interview 的解說來做個筆記 (參考連結統一附在最下方)。
另外先說聲拍謝,最近生活上雜事實在有點多待處理,加上這篇不知不覺寫的有點長,就容我先拆成兩篇先!(
參加過 4 屆鐵人賽完賽必備技能?)
題目
雖然說遇到的題目是「設計一個搶票系統」,但網路上找得到的 mock interview 大多是「設計一個購票網站」,認為前置的設計應該算是大同小異,只有差在 deep dives 優化方向會不一樣,搶票系統會更頃向著重在應付高流量迸發的問題,而購票網站會更像是一個產品類系統設計題,可能需要多處理活動列表、搜尋、訂單資訊等功能,因此建議在前期釐清需求時需要問清楚。
Step 0. 框架大綱
參考 Hello Interview 的「設計一個 TicketMaster 系統」這個影片來做筆記,先簡述下以下採用的面試框架:
釐清需求
列出核心資料庫實體 (Entity)
設計 API 介面
提出高階設計
deep dives
補充:TicketMaster 應該算是國外較大的購票系統,可能就可以類比為像是台灣的 KKTIX 或是演唱會搶票最有名的拓元售票。
Step 1. 釐清需求
因為購票系統可能有多種使用情境,因此會需要透過一些問題來收斂需要去實作的「功能需求」與「非功能性需求 (也就是系統品質)」。
1-1. 模擬問答:
Q:會期待這個系統是要像 KKTIX、拓元、TicketMaster 這樣的綜合型購票系統,或是一個單純的活動頁面可以進行搶票?
A:對,可以參考這樣購票平台來設計
Q:以我所知道的購票平台,除了核心的購票功能外,是否會需要活動列表、搜尋活動、購買後的買家訂單列表
A:除了訂單列表外其他都需要實作
Q:這個購票系統,在訂票時應該會需要可以選不同價位的座位?
A:對
Q:在購票功能的部份,使用者是否需登入才能購票?登入服務是否能串接既有認證系統?
A:登入功能可以當作有現成的服務可以串接
Q:在付款功能上,應可以直接串接第三方付款服務像是 Stripe?
A:對
Q:活動是否需要支援不同類型的活動,像是演唱會、展覽、研討會、球賽等等?
A:需要
Q:會需要設計座位圖選票的功能嗎,或限量票的規格都是一致的?
A:需要設計座位圖
Q:對購票功能來說,因為需要確保同一個座位不會坐兩個人,所以這功能在設計上應該會更重視高一致性
A:合理
Q:對一個搶票系統而言,想像中使用者平常大多是只有查看活動資訊,只有在特定時間點有熱門演唱會開賣時,會需要處理搶票的高流量?
A:對,假設大約有一千萬的人要搶一千張票
透過上面的問題釐清需求後,可以整理如下:
功能性需求
訂票功能:要能支援高流量的搶票、座位圖選位與付款
搜尋活動
活動列表
非功能性需求
低延遲的搜尋
針對購票功能,要能確保同一張票沒有重複訂購問題,因此會更注重高一致性
針對搜尋與活動列表,可以更注重高可用性
對搶票系統而言,讀寫比例可能會是讀的比例遠高於寫,假設實際購票的轉換率是 1%,那讀寫比例就會是 100:1
系統要有能力在熱門搶票活動上提升擴展性來應付短期高流量問題
不在此次設計範圍 (Out of scope)
GDPR compliance (個資合規性)
購買後的訂單功能
容錯性
作者提到有個好習慣,列完上面的需求後,可以再詢問面試官是否有哪裡會需要調整實作的優先順序。
1-2. 補充:關於系統設計面試中的非功能需求
在影片中作者提到有些面試者在進行模擬面試時有個通病是會在非功能性需求像背書一樣單純列出系統會需要可用性、擴展性、一致性、可靠性、容錯能力等這些點,雖然不能說錯但有點本末倒置,因為幾乎所有系統有這些特性的話都會是個有品質的系統。
作者認為非功能需求的本質在於面試者是否能講出他認為這個系統中獨特、有趣、有挑戰性的部份,方便後面在 deep dive 時能夠有個方向。
舉個例子,像這題可以先從 CAP 理論的方向切入,去思考這個系統若未來需要擴展的話,應更注重高一致性還是高可用性,再來可以考慮產品的讀寫比例特性、是突發的高流量或穩定的龐大活躍使用者來歸納出核心的非功能需求。
Step 2. 列出核心實體 (Entity)
為了要能在下一步更容易設計 API 介面,可以先簡單規劃一下資料庫中的實體
Event:活動資訊
Venue:活動場地
Performer:活動主辦方
Ticket:票務資訊
這裡不需要把每個實體的欄位都列出來,只是讓雙方可以快速理解為了實現核心功能會切分成哪些實體。
Step 3. 設計 API 介面
在這一步中可以參考功能性需求來列出需要的 API 格式,設計風格可以使用 RESTful 或 GraphQL,這裡採用前者。
3-1. 活動詳細資訊
GET /event/:eventId
-> Res: Event、Venue、Performer、Ticket[]
3-2. 搜尋活動
GET /search
-> Query: term、location、type、date
-> Res: Partial<Event>[]
3-3. 訂票功能
訂票的部份因為會需要選座位與付款,所以可以先定義使用者流程,可以拆成以下步驟:
活動資訊頁上會有開賣倒數,在開賣前按鈕呈現 disabled 狀態
開賣後,購票按鈕可按,使用者點擊後進入購票頁面
為防止重複購票,此時會鎖住某一張票的 id 並提供給使用者在一段時間內,像是 10 分鐘,若超時則需要重新訂購
填寫付款資訊後經第三方支付服務驗證並完成購票
上面這樣的流程中,會需要設計 2 個 API:
進入購票頁面後鎖著某張票 10 分鐘
填完付款資料後確認下訂
// 進入購票頁面
POST /booking/reserve
header JWT or sessionToken
body: {
ticketId
}
// 填完付款資料後確認下訂
PUT /booking/confirm
header JWT or sessionToken
body: {
ticketId,
paymentDetails
}
這裡確認下訂的 /booking/confirm API 使用 PUT 會比 POST 更精準,因為這個操作並沒有創建新資源,而是對已存在的 ticketId 進行狀態更新。另外使用 PUT 也符合 RESTful 中冪等性(idempotent)的語意,也就是多次執行相同請求時都會產生相同結果。
以上就是針對功能性需求所列出來的 4 個 API,下一步就可以開始進行比較有趣的高階設計畫圖了。
Step 4. 提出高階設計並取得認可
在這個設計中會嘗試採用微服務架構的方式,為了保留未來可以針對特定服務 (像是訂票功能) 做擴展。
初期先從相對單純的活動列表功能著手,去畫出最基本的 client-server 結構:
如上圖可以看到,前端透過 API Gateway 存取後端各個服務,中間的這層 API Gateway 可以拿來做為提供認證、限流、導流至各個服務的功能。
在資料儲存方面,會想選擇使用 RDBMS 來儲存資料,主要原因是考量到購票系統更注重高一致性 (Strong Consistency) 需求,像是前面提到的,多位使用者同時搶票時,系統需保證同一張票不會被多次訂購。
儘管部分 NoSQL 資料庫(如 MongoDB、Cassandra)也開始支援類似 ACID 的功能,但它們多數仍採用 BASE 模型(Basically Available、Soft state、Eventually consistent),偏向在高可用性與擴展性之間取捨,較適合資料一致性需求沒那麼嚴格的應用場景。(一查之下才發現之前誤以為 ACID 是 RDBMS 獨有的特性…)
接著再來畫出剩下的搜尋服務及訂票服務,訂票的部份因為會牽扯到金流,這裡打算可以串接第三方金流服務像是 Stripe 來驗證付款資訊,而通常第三方金流會提供 callback 的機制,用於在付款驗證完成後回傳結果給我們的 Booking Services,以確認是否成功完成訂單。
今天先寫到這,下一篇將繼續把剩下的高階設計與 deep dives 做完。
參考資源與延伸閱讀
另外忍不住一提,雖然之前發現 Google NotebookLM 可以直接丟給它 Youtube 影片後請它直接摘要內容,不過為了學習的肌肉記憶,這篇筆記是純手工紀錄的,之前發現有些部落格會放上 Not By AI 標籤也是為了避免讀者誤會現在看的是 AI 生成文章,或成為一種寫文章的趨勢 😆
以上若內容有什麼錯誤、問題、討論也都歡迎透過以下管道與我交流,或直接留言與回覆這封電子信我也能收到:
Email:codefarmer.tw@gmail.com