這篇定義四個範例系統:短網址、售票搶購、電商 checkout 與交易撮合,供幾篇沿著具體系統的請求路徑逐段分析的文章共用,目前有 9.0 請求路徑上的負載分析 的舉例、9.0.1 售票搶購的負載路徑、9.0.2 交易撮合的負載路徑、4.0.2 以監控鏈盤點單機短網址服務 、4.0.3 以監控鏈盤點 Kubernetes 上的電商 checkout 與 4.0.4 以監控鏈盤點交易撮合系統。那幾篇引用的請求量、票數這類規模數字是本篇的假設,用來讓例子有具體的量,不是任何真實服務的數字;4.26 單機容器服務的流量與資源監控 到 4.28 單台主機的監控盲區與失效域以外的觀察者 也用短網址服務當例子,那幾篇的量測數字來自實際的開發環境,不在本篇的假設之內。

同一個元件在不同需求下的設計不同。售票系統可以讓大多數請求在進資料庫之前就收到「售完」,交易撮合每一筆被接受的委託都要處理;剛建立的短網址轉址讀到幾秒前的舊資料沒有影響,checkout 的付款狀態讀到舊資料會讓使用者重複付款。每個系統都有一件必須一次只處理一筆的事,它的互斥範圍(哪些請求彼此必須輪流處理,定義在 9.0 請求路徑上的負載分析 的〈一次一筆的處理與它的互斥範圍〉)各自不同:售票是同一張票(座位)的認領與訂單狀態,撮合是同一個交易對的整本委託簿,checkout 是同一筆訂單的付款狀態,短網址是同一個代碼的分配。

四個系統共用幾個部署上的前提:都在一個雲端帳號的一個區域裡(短網址是一台主機),有人值班接告警;售票的等候室自建、一次只有一個場次開賣;撮合的用戶端以程式為主,市場是連續撮合。前提不同時,各篇裡依賴它的那幾段會標出來。

短網址服務

短網址服務把一個長網址換成一段短代碼,使用者打開短網址時,服務回 301 或 302 轉址到原本的長網址。請求有三種:轉址(以代碼查出長網址,不需要登入)、建立與管理短網址(登入之後建立、修改、停用自己的短網址),以及管理者的後台操作(停用被檢舉的短網址、查看帳號)。轉址的量遠大於其他兩種,示意的比例是轉址與建立一百比一。

不能讓步的需求:轉址的目標必須是短網址目前登記的那一個長網址;寫錯的轉址會把使用者送到錯的網站,而主機、容器與請求的監控數字都正常。代碼不能重複分配給兩個長網址。帳號只能修改自己的短網址。管理者停用一個被檢舉的短網址之後,要在幾分鐘內(示意)停止轉址,所以轉址回應的快取期限有上限。這個範例允許建立者修改目的地,修改之後要重新做惡意網址檢查並保留修改紀錄;目的地是否允許修改、撤回要多快生效,是短網址服務要先定案的需求決定,見 0.23 短網址服務的需求定義 的〈目的地修改的功能需求與風險〉與 0.24 短網址服務的實作 的〈各層快取與撤回所需的時間〉。

可以讓步的需求:剛建立的短網址在幾秒內轉址失敗,影響的是一個剛建好、還沒散出去的連結;轉址的延遲多個幾十毫秒,使用者感覺不到。點擊次數的統計可以延遲、可以少算幾筆。

規模假設(示意):每秒數百次轉址,尖峰是平常的數倍(一則被大量轉貼的連結);短網址總數在百萬筆的量級。

起點架構:一台主機上以 Docker Compose 跑入口 Nginx、後端程式、PostgreSQL 與 Valkey,各在一個容器裡;Valkey 快取代碼到長網址的對應。監控的分層以它為例,見 4.0 服務端監控的分層,實際的盤點見 4.0.2 以監控鏈盤點單機短網址服務。

售票搶購

售票系統在指定的開賣時間讓使用者選場次、選座位或票種、下單並在期限內付款。請求有四種:讀取場次與座位圖、排隊或取得購票資格、扣庫存下單、付款與付款結果的回呼。開賣前幾秒到開賣後幾分鐘的請求量是平常的數百倍,票賣完之後請求量迅速回落。

不能讓步的需求:不超賣,賣出的票數不能超過場次的票數,同一個座位不能賣給兩個人。付款成功的訂單一定要有票。付款期限(示意:十分鐘)到期而未付款的票要放回可售,放回與付款不能同時成立。

可以讓步的需求:使用者可以接受排隊幾分鐘,等候期間看到自己的位置即可;座位圖上的剩餘數量可以落後幾秒,因為最後的判斷在扣庫存那一步;票在開賣後的前幾秒沒有全部賣出(少賣),只要之後回收得回來,是可以接受的短暫狀態。誰先買到票的順序依業務決定(先到先得或抽籤),而不論哪一種,規則要事先公布並照做。

成功請求的總數有上限:成功的購票總數不會超過票數。十萬個帳號搶五千張票、每個帳號限購一張時(示意),票賣完那一刻,其餘九萬五千人的購票請求結果已經確定是售完(直到付款期限到期、有票被放回為止)。

規模假設(示意):一個場次五千張票,開賣時十萬人同時在線;開賣後第一分鐘各種請求加起來每秒數萬個,其中扣庫存的請求受排隊放行的速率限制。

起點架構:CDN 與入口、無狀態的 API 服務(可水平擴充)、排隊服務、關聯式資料庫存放每張票一列與訂單、第三方金流處理付款、排程工作處理付款期限。站內的對照案例有 9.C15 拓元 Tixcraft:售票搶購的瞬間爆量架構 與 9.C16 SeatGeek:DynamoDB + Lambda 打造的虛擬等候室;負載路徑的分析,包括什麼條件下才在扣庫存前面加一道 Redis 閘門,見 9.0.1 售票搶購的負載路徑。

電商 checkout

電商的 checkout 讓使用者把購物車裡的商品下單、付款,之後由下游寄送確認信、更新庫存與報表。請求有:讀取商品與價格、建立訂單、送出付款、接收金流業者的付款結果回呼;訂單建立之後的通知、出貨與報表由訊息佇列的消費端非同步處理。這是 Backend 教材的閱讀路線 裡貫穿各模組的 checkout 流程。

不能讓步的需求:同一筆訂單只能扣款一次,使用者重按付款或用戶端重試都不能多扣;付款成功的訂單不能遺失;訂單金額必須等於使用者確認時看到的金額。

可以讓步的需求:確認信晚幾分鐘寄出、報表晚一小時更新,使用者與營運都能接受;商品頁的價格快取可以落後,只要下單時以資料庫的價格重新計算並在金額不同時請使用者確認。

成功請求的總數沒有固定上限:庫存充足時,每一個下單請求都可能成交,擋掉一個就是少一筆營收。

規模假設(示意):平日每秒數十筆訂單,促銷活動時是平常的十倍;商品頁的讀取是下單的數百倍。

起點架構:Kubernetes 上的商品、訂單與付款服務,PostgreSQL 存放訂單,Redis 快取商品與價格,訊息佇列連接訂單服務與通知、出貨、報表的消費端,付款服務呼叫第三方金流並接收回呼。監控的盤點見 4.0.3 以監控鏈盤點 Kubernetes 上的電商 checkout。

交易撮合

交易撮合系統接收買方與賣方的委託,在委託簿裡配對成交。交易對(trading pair)是一組可以互相交易的資產,例如 BTC 對 USD;委託簿(order book)是一個交易對上還沒成交的買賣委託,依價格與時間排成的清單;撮合依價格與時間的優先順序(price-time priority:價格較好的先成交,同價格時先到的先成交;CME 的文件稱這條規則為 FIFO)把新進的委託與簿上的對手配對。成交之後,系統送出成交回報給雙方,更新雙方的部位(position,帳戶持有的各項資產數量),並把行情(最新成交價、各價位的委託量)送給訂閱者。請求有:送出委託、取消委託、查詢帳戶與委託狀態、訂閱行情。

不能讓步的需求:每一筆被接受的委託都要處理,處理不了的要在接受之前明確拒絕;處理的順序必須與到達撮合引擎的順序一致,因為順序決定誰成交;同一筆委託不能執行兩次;已回應「已接受」的委託不能遺失。

可以讓步的需求:行情送給處理不及的訂閱者時,可以合併中間的報價更新只送最新的狀態;帳戶的歷史查詢可以讀稍舊的副本。

延遲改變請求的價值:在連續撮合的市場裡(委託一到就與簿上的對手配對),一筆委託在佇列裡多等一秒,送進委託簿時市場已經不是使用者下單時的市場。市價單會以偏離預期的價格成交;限價單不會比限價差,代價是掛著的價格已經過時、在市場變動後被以不利的時機成交。

規模假設(示意):一個熱門交易對每秒數萬筆委託與取消,市場劇烈波動時是平常的十倍以上;撮合的延遲目標在毫秒以下。

起點架構:入口閘道(驗證身分、檢查餘額與帳戶的速率限制)、依交易對分開的撮合引擎、委託先寫入依序編號的日誌再撮合、行情發布服務、帳戶與歷史資料存放在資料庫。站內的對照案例有 9.C3 Coinbase International Exchange:超低延遲交易的逆向容量設計,負載路徑的分析見 9.0.2 交易撮合的負載路徑,監控的盤點見 4.0.4 以監控鏈盤點交易撮合系統。

四個系統在請求性質上的差別

四個系統在下面三個性質上的位置。第一欄只對要經過那件一次一筆處理的請求有意義(購票、下單、委託、建立短網址),轉址、查商品這類讀取不在其中:

系統成功請求的總數延遲是否改變請求的價值同一個請求被處理兩次的後果
短網址無上限,每一次建立都要成功幾十毫秒內不改變轉址重複無害;建立重複會多一個代碼
售票搶購有上限:票數;售完之後的請求結果已確定不劃位、固定票價時不改變:排隊幾分鐘之後買到的仍是同一種票、同一個價格;排隊決定的是誰買到;對號入座時改變的是剩下哪些座位,動態定價時連價格也變同一人買到兩張或扣兩次款
電商 checkout庫存充足時無上限,每一筆都可能成交幾秒內不改變,太久使用者放棄重複扣款
交易撮合無上限,每一筆被接受的委託都要處理改變:毫秒等級就影響成交價格或成交時機重複成交,帳戶部位錯誤

三個性質怎麼用來判斷負載保護的方向(第一欄只是可提早回應的比例的一個來源,拍賣就是有上限而比例低的反例;第二欄要以「改變、所以要拒絕」判讀,還要請求處理時不再經使用者確認),在 9.0 請求路徑上的負載分析。延遲改變請求價值的系統不只連續撮合的交易所。廣告的即時競價要在交易平台設定的期限內回出價(期限以毫秒計,各平台不同),晚到的出價直接作廢;叫車的媒合晚了幾秒,司機與乘客的位置都變了。同一個請求被處理兩次有後果的系統,從用戶端到資料庫的每一段都要能辨認重送的請求(Idempotency)。可提早回應的比例與延遲這兩個性質,加上互斥範圍能縮小到什麼程度,如何決定負載保護的方向,是 9.0.1 售票搶購的負載路徑 與 9.0.2 交易撮合的負載路徑 對照的主題:售票的比例高(成功請求的總數有上限,售完之後的請求結果已確定)、延遲不改變價值,撮合兩者都相反。