超賣怎麼防?1 個名額 1000 人搶,從選課系統段子看懂 Kafka 排隊、資料庫條件更新與冪等

恆遠數位編輯團隊約 22 分鐘閱讀
高併發搶名額防超收文章封面:1 個名額 1000 人搶,怎麼不超收?Kafka 排隊、條件更新與冪等
複製引文

最近 Threads 上有一則從台大匿名板流出來的段子,工程師圈轉得很兇。內容是這樣的:資工系畢業專題,題目要做一套「十萬人同時操作、永不當機的選課系統」。有一組學生端出 3 台 app server、Redis、Kafka、Load Balancer,再加上資料庫 transaction,組長很有自信地說:「我們可以做到 100% 可用。」教授只問了一題:「一門課剩 1 個名額,1000 人同時搶,你們怎麼保證不超收?」組長回答:「全部先進 Kafka,非同步依序處理,避免衝擊資料庫。」

先說清楚,這是匿名板帳號貼出來的段子,真假無從查證,我們把它當成一道題目來拆。我們團隊在做預約、檔期、名額分配這類系統時,客戶最常問的也剛好就是教授這一題,所以想把答案講白:把請求丟進 Kafka 排隊,只能讓資料庫不要一次被擠爆;要保證不超收,關鍵在「檢查還有沒有名額」和「扣掉名額」必須在同一個不可分割的動作裡完成,常見做法是資料庫條件更新、Redis 原子扣減,再加上唯一約束兜底。

這題看起來像資工系的考試,其實每家公司都會遇到。活動報名只收 200 人、限量 500 張的優惠券、診所一個時段只能約 3 位、器材同一天只能租給一組人,全部都是「名額有限、很多人同時搶」的問題。超收一次,輕則道歉退費,重則上新聞。

這篇用零基礎也看得懂的方式寫,專有名詞第一次出現都會附一個生活比喻。如果你想先搞懂訊息佇列本身是什麼,可以先看Message Queue 入門;想了解 Redis 在公司系統裡平常拿來做什麼,可以看Redis 快取完整介紹。這篇只專心回答一件事:名額怎麼才不會被多賣。

1 個名額 1000 人搶:超收到底是怎麼發生的

一句話:超收發生在「先查、再扣」中間那幾毫秒的空檔。兩個人同時查到「還剩 1 個」,兩個人都以為自己搶到了,系統就把同一個名額給了兩次。

工程師把這種狀況叫做競態條件(race condition)。用便利商店比喻:架上只剩最後一個便當,兩位店員同時被兩位客人問「還有嗎?」,兩人回頭看一眼架子都說「有」,結帳時才發現只有一個。問題出在「看架子」和「把便當拿下來」是分開的兩個動作,中間誰都可能插隊。

先給一張全文速覽表,把段子裡那組學生用到的每個元件,和真正能防超收的做法放在一起看:

做法

它解決什麼

它保證不了什麼

一句話判斷

Load Balancer+多台 app server

把流量分給好幾台主機,單台壞了還有別台

名額正確,反而讓「同時查同時扣」更容易發生

管流量,名額要另外管

Kafka 排隊(削峰)

尖峰請求先排隊,資料庫照自己的速度處理

重送時同一人被處理兩次;有其他入口繞過佇列

管秩序,名額要另外管

資料庫 transaction(照預設寫法)

一組動作要嘛全做、要嘛全不做

「先 SELECT 再 UPDATE」在預設隔離等級下照樣超收

用法錯了一樣會多賣

資料庫條件更新

扣名額時順便檢查還有沒有,一個指令完成

要自己看「影響幾筆」並回應使用者

多數公司這招就夠

Redis 原子扣減

在記憶體裡扣,扛得住很高的瞬間流量

Redis 和資料庫兩邊數字可能對不上

流量真的很大才需要

唯一約束(學生+課程)

同一個人同一門課只會有一筆

名額總數本身

一定要加的保險

再把剛剛的便當換成選課。學生 A 和學生 B 幾乎同一瞬間送出選課,系統的程式碼如果長這樣:先查剩餘名額,大於 0 就把名額減 1、寫入選課紀錄。兩個請求都在對方還沒寫回去之前完成「查」,於是都看到 1,都扣成 0,選課紀錄卻多了兩筆。

圖表載入中…

這時候常有人說:「我有包 transaction 啊。」Transaction(交易)可以想成「打包成一張訂單」:裡面的步驟要嘛全部成功,要嘛全部取消。但它只保證「不會做一半」,沒有保證「別人不會同時看到同一份資料」。隔離等級可以想成「辦公桌之間的隔板有多高」,隔板越高越看不到別人正在改什麼,但大家也越容易互相等。主流資料庫預設的隔離等級多半是 READ COMMITTED,兩個 transaction 各自先 SELECT 再用程式算好的數字 UPDATE,照樣會多收。PostgreSQL 的官方文件寫得很清楚,要嘛把條件寫進 UPDATE 本身,要嘛換更嚴格的隔離等級並處理重試。

這類「同一份資源不能同時給兩個人」的問題,我們在影視器材租賃與檔期系統裡處理過:同一批攝影器材哪天被借走、送修、外調,可租數量要每天重算,兩筆預訂撞期時系統要自動抓出來。名額換成器材、日期換成課程,底層就是同一題。

Kafka 是排隊叫號機:它管秩序,名額要另外管

先講結論:Kafka 可以讓 1000 個請求排好隊、一個一個處理,這對保護資料庫很有用。但「排好隊」和「不超收」中間還隔著好幾個坑,組長的回答只講了前半段。

Kafka 是一套訊息佇列系統,最好懂的比喻是銀行的排隊叫號機:客人一進門先抽號碼牌,櫃台照號碼一個一個叫。一次湧進 1000 人,大廳會很擠,但櫃台不會被擠垮。Kafka 裡的 partition(分區)就像好幾個各自叫號的櫃台,每個櫃台內部照順序叫,不同櫃台之間就沒有先後可言。Key(分流鍵)則是決定你去哪個櫃台的規則,例如「同一門課的人都去 3 號櫃台」。

Kafka 的官方說明講得很直接:同一個 key 的事件會寫進同一個 partition,而讀取同一個 partition 的 consumer(消費者,也就是叫號後實際辦事的櫃員),會照寫入的順序讀到事件。所以如果要靠 Kafka 讓「同一門課」依序處理,至少要做到兩件事:

  • 同一門課要用同一個 key(例如課程代碼),確保搶同一門課的請求全部進同一個 partition。
  • 同一個 partition 同時只能有一個 consumer 在處理,而且這個 consumer 自己不能再開多執行緒平行亂扣。傳統的 consumer group(同一組輪班的櫃員)會把一個 partition 只分給一個成員,正好符合這個要求。

做到這兩點,理論上同一門課的 1000 個請求會被排成一列處理,第 1 個人扣到 0 之後,後面 999 個人都應該收到「額滿」。聽起來好像組長答對了?問題出在下面這些細節,每一個都會在正式上線後咬你一口。

坑一:Kafka 預設是「至少送一次」,同一個請求可能被處理兩次

Kafka 的設計文件明講:預設保證是 at-least-once(至少送達一次)。比喻成快遞:包裹保證一定送到,但如果司機不確定你有沒有簽收,可能會再送一次。consumer 處理完一筆選課、還來不及回報「這筆我處理好了」就當機,接手的 consumer 會把同一筆再處理一次。

所以處理端必須做到冪等(idempotent):同一個動作做一次和做十次,結果都一樣。像按電梯按鈕,按一下和連按十下,電梯都只來一次。實作上最常見的做法是每個請求帶一個唯一編號,處理前先看這個編號做過沒有,或直接靠資料庫唯一約束擋住第二筆。Kafka 自己有 idempotent producer,能避免「送進佇列」這一段重複,但 consumer 端寫資料庫那一段,還是得自己顧。

坑二:有別的入口不經過 Kafka

選課系統很少只有一個入口。教務處後台人工加簽、加退選另一支 API、批次匯入名單,只要有一條路直接改資料庫、沒有排進同一個佇列,「依序處理」就破功。名額的真相終究在資料庫裡,防線也得放在資料庫。

坑三:擴充與熱門課

partition 數量之後若要加大,key 對應到哪個 partition 的計算結果會變,同一門課可能跑到新的 partition,換手期間順序就不保證。另外,熱門課全擠在同一個 partition,那個 partition 只有一個 consumer 在處理,速度上限就卡在那裡。Kafka 4 系列新增的 share group(共享群組)讓同一個 partition 可以分給多個 consumer 一起消化,吞吐量上去了,但也就不再照順序處理,這時候更不能指望佇列幫你防超收。

你想要的

Kafka 給得了嗎

要另外補什麼

尖峰時保護資料庫不被打爆

可以,這是它最擅長的事

監控佇列堆積,避免使用者等太久

同一門課依序處理

可以,前提是同 key、單一 consumer

部署規範寫清楚,擴 partition 要有計畫

同一請求只處理一次

預設不行(至少一次)

冪等鍵或資料庫唯一約束

名額絕對不超收

不行,它不知道名額

資料庫條件更新或原子扣減

讓使用者馬上知道結果

不行,它是非同步

「處理中」狀態頁與結果通知

所以教授想聽的答案大概是:「Kafka 負責削峰,消費端用條件更新扣名額、用唯一約束防重複,結果再非同步通知學生。」少了後半句,Kafka 只是把超收延後幾秒發生。想看 RabbitMQ、Kafka、SQS 各自適合什麼情境,可以參考Message Queue 入門那篇的選型表。

真正防超收的三招:條件更新、原子扣減、唯一約束

一句話:讓「檢查」和「扣除」變成同一個動作,再用唯一約束當最後保險。三招可以單獨用,也可以疊著用。

第一招:資料庫條件更新,看影響筆數

最簡單、也最常被忽略的一招,就是把「還有名額嗎」直接寫進扣名額的那一行指令:

SQL
UPDATE courses
SET seats = seats - 1
WHERE id = :course_id
  AND seats > 0;
-- 影響筆數 = 1 → 搶到了,接著寫入選課紀錄
-- 影響筆數 = 0 → 額滿,回覆使用者「已額滿」

比喻成櫃台蓋章:櫃員只有在「抽屜裡還有號碼牌」時才會蓋章,蓋章和拿牌是同一個動作,沒有「先看一眼再回頭拿」的空檔。資料庫在執行這行 UPDATE 時會鎖住這一列,兩個請求同時到,第二個會等第一個做完,再用最新的數字重新檢查 seats > 0。PostgreSQL 文件把這個行為叫做重新評估 WHERE 條件,第二個人看到的已經是 0,影響筆數就會是 0。

程式端要做的事只有一件:看影響筆數。很多超賣事故的程式碼其實寫對了 UPDATE,卻沒有檢查它到底有沒有改到東西,照樣告訴使用者「報名成功」。記得把 UPDATE 和寫入選課紀錄包在同一個 transaction,選課紀錄寫失敗時名額要一起退回。

這招的代價是熱門那一列會變成大家排隊搶的熱點,每秒能處理的扣減數量有上限。對大多數公司的報名、預約系統來說,這個上限已經遠超過實際流量。

第二招:Redis 原子扣減,以及它和資料庫的一致性問題

Redis 是一套放在記憶體裡的資料庫,可以想成「貼在櫃台旁邊的計數白板」,讀寫都非常快。Redis 的 DECR 指令是原子操作:一千個人同時對同一個數字減 1,每個人拿到的結果都不一樣,不會有兩個人同時拿到 0。常見寫法是 DECR 之後如果結果小於 0,代表搶輸了,再 INCR 補回去。

更嚴謹的做法是用 Lua 腳本把「檢查」和「扣減」寫成一段,Redis 官方文件保證腳本執行期間不會被其他指令插隊:

LUA
-- KEYS[1] = 課程名額 key,ARGV[1] = 要扣的數量
local left = tonumber(redis.call('GET', KEYS[1]) or '0')
if left >= tonumber(ARGV[1]) then
  return redis.call('DECRBY', KEYS[1], ARGV[1])
end
return -1  -- 額滿

Redis 擋得住瞬間流量,麻煩在後面:名額在 Redis 扣了,選課紀錄卻寫在資料庫。這兩個系統沒辦法包成同一個 transaction,中間只要有一邊失敗,數字就對不上。Shopify 工程團隊 2026 年 5 月發表的庫存預留改版文把這件事講得很坦白:他們原本用 Redis 的 DECR 做結帳時的庫存預留,Redis 本身處理併發沒問題,但預留在 Redis、庫存帳本在 MySQL,付款成功後「扣帳本」和「清 Redis」無法在同一個原子步驟完成,順序一錯就可能超賣或少賣。後來他們把預留搬回 MySQL,用一列代表一件庫存,再搭配 SKIP LOCKED(某一列被別人鎖住就跳過、改拿下一列)分散熱點,在 2025 年黑色星期五每分鐘 510 萬美元營業額的尖峰下扛住了。

連 Shopify 這種規模都選擇把正確性收回資料庫,中小企業更該想清楚:真的需要 Redis 擋名額嗎?如果真的要用,至少要有對帳機制,例如定時比對 Redis 剩餘數和資料庫實際選課筆數,以及失敗時把名額補回 Redis 的補償流程。

第三招:唯一約束,同一個人只能有一筆

唯一約束(unique constraint)是資料庫層的規則,比喻成「同一張身分證只能領一次」:不管程式寫得多亂、請求重送幾次,同一位學生同一門課的紀錄只會存在一筆,第二筆會直接被資料庫拒絕。

SQL
ALTER TABLE enrollments
  ADD CONSTRAINT uq_student_course UNIQUE (student_id, course_id);

它防的是「同一人重複報名」,剛好補上 Kafka 至少送一次、使用者狂點按鈕、網路逾時自動重送這些情境。它不管總名額,所以要和第一招或第二招搭配。限量優惠券也是一樣的道理:「會員+活動」唯一,一人就只能領一張。

比較項目

資料庫條件更新

Redis 原子扣減

唯一約束

防什麼

總名額不超收

總名額不超收

同一人重複報名

扛瞬間流量

中等,熱門列會排隊

很高

跟著資料庫

正確性

高,和選課紀錄在同一個 transaction

要另外處理和資料庫對帳

高,資料庫強制

開發與維運複雜度

低

中到高(多一套系統+補償流程)

很低

建議

預設首選

流量真的超過資料庫能扛的才加

每個名額系統都該加

如果你手上的系統是用 AI 工具快速生出來的,這三招是最值得先請工程師看一眼的地方,Vibe Coding 做出來的 App 能直接上線嗎那篇整理了更多上線前常漏的檢查點。資料庫要選關聯式還是 NoSQL,可以參考SQL vs NoSQL 怎麼選,名額這類需要強一致的資料,關聯式資料庫通常比較省事。

非同步選課的使用者體驗:讓人看到「處理中」,比假裝秒成功好

先講結論:一旦用了佇列,使用者按下送出的那一刻,系統其實還不知道結果。這時最糟的做法是先顯示「選課成功」,幾分鐘後再偷偷取消;比較好的做法是誠實地顯示「已收到,處理中」,結果出來再通知。

圖表載入中…

每個狀態要給使用者看的字,最好在開發前就和業務單位一起定好:

狀態

使用者看到什麼

系統背後在做什麼

常見錯誤

已收到

已收到你的申請,目前排隊中

請求寫進佇列,產生唯一編號

直接寫「成功」,之後又取消

處理中

處理中,可以先離開,結果會通知你

consumer 扣名額、寫紀錄

沒有進度提示,使用者狂按重送

成功

已選上,並列出課程與時間

選課紀錄已寫入,發送通知

通知寄兩次(沒做冪等)

額滿

這門課已額滿,可登記候補

影響筆數為 0,不寫紀錄

只寫「失敗」,使用者以為系統壞了

錯誤

系統忙碌,名額沒有被扣,請稍後再試

transaction 回滾,名額退回

名額扣了紀錄沒寫,變成幽靈名額

還有一個常被忽略的選項:乾脆不要讓大家搶。中山醫學大學的選課手冊寫得很清楚,初選階段系統會給每筆選課一個隨機亂數,超過人數上限時用亂數篩選,不看誰先按;初選之後的加退選才是先選先上。把「搶」改成「先登記、再抽籤」,尖峰流量直接消失,公平性也比較好說明。活動報名、限量商品、熱門門診時段,都可以考慮這個設計,它省下的工程成本常常比任何架構都多。

「100% 可用」不存在:99.99% 一年也只能停 52 分鐘

一句話:任何系統都會停,差別只在一年停多久、停的時候怎麼辦。聽到廠商說「保證永不當機」,你該追問的是「你們的 SLA 寫幾個 9?沒達到賠什麼?」

SLA(服務等級協議)是廠商和客戶之間的白紙黑字承諾,可以想成餐廳寫在菜單上的「30 分鐘沒上菜就送飲料」。它寫的是「沒做到要怎麼補償」,從來不是「保證做到」。Google 的 SRE 手冊算給大家看過:可用性目標 99.99% 的系統,一年最多可以停 52.56 分鐘。把各級距換算成停機時間,差距一目了然:

可用性

一年最多停機

一個月最多停機

大概需要什麼

99%

約 3.65 天

約 7.3 小時

單台主機、定期備份

99.9%

約 8.76 小時

約 43.8 分鐘

主機與資料庫都有備援、有監控告警

99.95%

約 4.38 小時

約 21.9 分鐘

跨可用區部署、自動切換

99.99%

約 52.6 分鐘

約 4.4 分鐘

全鏈路無單點、自動化部署與回滾、值班制度

99.999%

約 5.3 分鐘

約 26 秒

多區域架構、專職團隊,成本陡增

雲端大廠自己也只承諾到這裡。AWS 現行的 EC2 服務等級協議寫的是:部署在同一區域兩個以上可用區時,區域層級月可用率至少 99.99%;沒達到,客戶可以拿到帳單 10% 的服務抵用金。連底層雲端都只寫四個 9、而且賠的是抵用金,蓋在上面的選課系統要宣稱 100%,邏輯上就站不住。

回到段子裡那套架構。3 台 app server 看起來有備援,但我們數一下單點故障(single point of failure),也就是「只有一座橋,橋斷了全村就出不去」的那種元件:

  • 資料庫只有一台:app server 再多台,資料庫一掛全部停擺。
  • Redis、Kafka 沒寫幾個節點:單節點就是單點,叢集也要設定好複製與選主。
  • Load Balancer 本身:它是所有流量的入口,自己也需要備援。
  • 部署流程:一次更新全部主機、出錯沒辦法快速回滾,人為失誤是最常見的停機原因之一。
  • 第三方服務:登入用的學校 SSO、寄通知的郵件服務,任何一個掛了都會讓「選課成功」卡住。

停機的代價也很具體。ITIC 2024 年針對全球 1,000 多家企業的停機成本調查顯示,超過九成的中大型企業每停機一小時損失超過 30 萬美元,41% 的企業估計在 100 萬到 500 萬美元以上。中小企業的金額沒那麼大,但開賣、開報名那一小時的停機,損失的是整檔活動的營收和口碑。2022 年 Taylor Swift 巡演開賣時,Ticketmaster 就因為需求量遠超系統負荷,取消了原訂的公開販售,這是很多工程師至今還會拿來討論的歷史案例。

要預先知道系統撐不撐得住,正確的做法是上線前做壓力測試,弱點掃描、滲透測試、壓力測試差在哪那篇有完整說明;上線後則要有監控,看得到真實的可用率,可以參考SLA 監控儀表板怎麼挑。真的出事時能多快復原,就回到備份與災難復原的 RPO、RTO。

CAP 在選課場景怎麼選:寧可慢一點,也不能多收一個人

先講結論:名額這種資料,網路出問題時寧可讓部分人暫時選不了課,也不能讓兩邊各自賣出最後一個名額。課程介紹、課表這類資料則可以接受稍微舊一點,換來大家都看得到。

CAP 定理講的是分散式系統的三個願望:一致性(Consistency,大家看到的資料都一樣)、可用性(Availability,每個請求都有回應)、分區容錯(Partition tolerance,機器之間網路斷了系統還能運作)。比喻成一家有兩間分店的店:兩間分店的電話線突然斷了,最後一件商品要不要繼續賣?繼續賣就可能兩邊都賣掉(犧牲一致性);暫停賣就有客人買不到(犧牲可用性)。

CAP 的提出者 Eric Brewer 在 2012 年的回顧文章特別澄清,「三選二」是過度簡化的說法:網路沒斷的時候兩者都可以兼顧,真正要做選擇的只有網路分區發生的那段時間,而且可以針對不同資料、不同操作分別決定。這對選課系統是很實用的提醒:

資料

網路出狀況時優先保什麼

原因

做法

剩餘名額、選課紀錄

一致性

多收一人就要人工處理、甚至違反規定

只認主資料庫,連不上就回「稍後再試」

課程介紹、教師、教室

可用性

晚幾分鐘更新沒人受傷

快取或 CDN(放在各地的就近副本),可以讀舊資料

列表上顯示的「剩餘 N 名」

可用性

只是參考值,真正扣的時候會再檢查

快取幾秒鐘,畫面註明以實際結果為準

付款、退費

一致性

金流錯了就是錢的問題

交易紀錄以資料庫為準,搭配冪等鍵

我們的立場很明確:名額和錢,一致性優先,其他資料大方地用快取。很多系統效能出問題,是因為把所有資料都當成名額一樣嚴格處理,每次看課表都去查主資料庫;也有系統出事是反過來,把名額當成課表一樣快取,畫面顯示還有位置,按下去才說額滿,甚至直接多收。分清楚哪些資料必須準、哪些可以慢一點,比選哪套工具重要得多。

換成企業場景:活動報名、限量優惠、預約名額都是同一題

一句話:只要你的業務有「數量有限」加上「很多人同時要」,就得面對防超收。好消息是,大部分中小企業的流量用第一招加第三招就夠了,根本用不到 Kafka。

場景

「名額」是什麼

超收的代價

建議做法

活動、講座報名

座位數

現場塞不下、臨時退費道歉

條件更新+「活動+Email」唯一

限量優惠券、首購禮

券的張數

多發的券要自己吸收成本

條件更新+「會員+活動」唯一

診所、美業預約

每個時段的人數

客人到了沒位子,評論區炸開

時段一列一筆,條件更新

限量商品搶購

庫存件數

取消訂單、平台評分下降

預留庫存+付款逾時釋放

器材、場地檔期

某日期區間的可用量

兩組客人同一天到現場

日期區間重疊檢查+鎖定

招生、抽籤名額

各園所、各班名額

家長申訴、公部門稽核

登記+抽籤,避開搶的尖峰

招生名額分配我們也做過,高雄市幼兒園招生系統把原本人工作業的公立與非營利幼兒園招生,做成家長、園所、教育局三端的線上管理系統。這類系統在意的除了名額不能出錯,還有每個環節都要留下紀錄、經得起稽核。想看售票、報名這類系統的開發決策與報價區間,可以讀客製化售票與活動報名系統開發指南;如果你的情境偏向時段預約,客製化排程預約系統開發指南會更貼近。

我們不認同「流量會變大,所以一開始就上 Kafka、Redis、微服務」這種做法。一個每天幾百人報名的系統,多一套佇列和快取,就多兩個會壞的元件、多一份維運成本,也多了兩邊資料對不上的風險。比較穩的路線是:先用資料庫條件更新和唯一約束把正確性做紮實,做壓力測試找出真正的上限,等流量確實逼近上限,再加佇列削峰。

找廠商做搶名額系統前,先問這 8 個問題

先講結論:你不需要懂程式,只要問對問題,聽對方怎麼回答,就能分辨他有沒有真的處理過併發。把下面這張表帶去開會,聽到右邊那欄的回答就要多問幾句。

要問的問題

比較好的回答長怎樣

要小心的回答

最後 1 個名額、很多人同時搶,怎麼保證不超收?

扣名額用條件更新或原子操作,並檢查影響筆數

「我們有用 Kafka/Redis」,講不出扣名額那一步

同一個人連按三次送出,會不會報名三次?

資料庫有唯一約束,請求帶冪等鍵

「前端會把按鈕鎖住」(擋不住重送與 API 直打)

名額有幾個入口可以改?後台人工加簽走哪條路?

所有入口共用同一套扣名額邏輯

「後台直接改資料庫就好」

送出之後,使用者多久知道結果?看到什麼畫面?

有明確的處理中、成功、額滿、錯誤四種狀態與通知

「很快啦,幾乎是即時」

如果用了 Redis,它和資料庫數字對不上怎麼辦?

有定時對帳和補償流程,以資料庫為準

「Redis 很穩,不會對不上」

你們的 SLA 寫幾個 9?沒達到怎麼補償?

寫明可用率、量測方式、維護時段與補償條款

「保證不會當機」

系統裡哪些元件只有一台?掛了會怎樣?

能列出單點故障清單與對應的備援或降級方式

答不出來,或說「雲端不會掛」

上線前會做壓力測試嗎?用多少人模擬?

依實際尖峰設定情境,交付測試報告

「上線後再看看」

這些問題建議直接寫進需求規格和驗收條件,口頭答應的東西最容易在交付時走樣。規格書怎麼寫才有約束力,可以參考客製化系統發包前的 SOW、規格書與合約指南;系統之間要串接時,API 是什麼那篇可以幫你先跟上工程師的用語。

看到這裡,如果你手上剛好有報名、預約或限量活動的系統要做,或是現有系統一到尖峰就出狀況,我們很樂意聽你聊聊現在的情況。名額怎麼扣、哪些地方真的需要佇列、哪些可以先不做,我們會直接告訴你。系統已經做好、想先確認有沒有超收風險,也可以從程式健檢開始。

ℹ️我們做過這件事

這篇講的名額與檔期問題,是我們在 40+ 企業客製案裡反覆處理的日常。例如我們做過的影視器材租賃與檔期系統,核心就是即時可用量計算與撞期自動偵測,千筆預訂數秒載入。如果你也在想「我們的報名或預約系統,尖峰時到底安不安全」,我們很樂意陪你一起看看現況,從最痛的那個環節開始。

ℹ️我們怎麼看

接下來幾年,企業系統的流量曲線會越來越尖:限時活動、社群導流、LINE 推播一發出去,幾千人同一分鐘湧進來會變成常態。但我們的判斷是,多數中小企業缺的是把正確性寫進資料庫的基本功,大型架構反而排在後面。我們的選擇是先用條件更新、唯一約束和壓力測試把底打穩,流量真的到了再加佇列與快取,不為了架構圖好看而堆元件。給你一個判斷工具:拿上面那 8 個問題去問現在的廠商或內部工程師,答不出前兩題,系統就有超收風險;答不出第六、七題,就別相信任何「永不當機」的承諾。

QKafka 可以保證不超賣嗎?

單靠 Kafka 不行。Kafka 能讓同一個 key 的訊息在同一個 partition 內依序處理,幫資料庫擋住尖峰,但它預設是至少送達一次,同一請求可能被處理兩次,而且它不知道名額還剩多少。防超賣要在消費端用資料庫條件更新或原子扣減,再加唯一約束防重複。

Q什麼是超賣、超收?跟當機有什麼不同?

超賣或超收是系統賣出、收進的數量超過實際名額,例如只剩 1 個位子卻收了 2 個人。當機是系統停止服務。超賣更麻煩的地方在於系統看起來運作正常,問題要等到客人到場或對帳時才爆出來。

QUPDATE seats = seats - 1 WHERE seats > 0 為什麼可以防超收?

因為檢查和扣除寫在同一行指令裡,資料庫執行時會鎖住那一列,同時到的第二個請求會等第一個做完,再用最新的數字重新檢查 seats > 0。名額已經是 0 時,影響筆數就是 0,程式看到 0 就回覆額滿。

Q小型活動報名系統需要用到 Redis 或 Kafka 嗎?

大多數不需要。每天幾百、幾千人報名的系統,用關聯式資料庫的條件更新加上唯一約束就能正確處理名額。先做壓力測試找出真正的上限,確定資料庫扛不住,再考慮加 Redis 或佇列。

Q冪等是什麼?為什麼報名系統一定要做?

冪等是指同一個動作做一次和做很多次,結果都一樣,就像電梯按鈕連按十下電梯也只來一次。使用者連點、網路逾時重送、佇列重複投遞都會讓同一個報名請求出現好幾次,沒有冪等就會重複報名、重複扣款或重複寄信。

Q廠商說系統 99.99% 可用,代表什麼?

代表一年停機時間最多約 52.6 分鐘、一個月約 4.4 分鐘。要確認的是怎麼量測、維護時段算不算、沒達標時補償什麼。雲端大廠的 SLA 也只承諾到 99.99%,沒達到時賠的是服務抵用金,任何宣稱 100% 可用的說法都要打問號。

回到那則段子,教授那一題問的其實是名額怎麼扣,跟用了幾套元件沒什麼關係。下次有人跟你說「我們有 Kafka、有 Redis、有三台主機」,你就問他最後一個名額怎麼扣、同一個人按三次會怎樣。答得出來,架構才有意義。

分享文章
恆

AUTHOR

恆遠數位編輯團隊

查看作者頁

留言(0)

尚無留言,成為第一個留言的人吧!

需要網站系統架設或軟體開發?

無論是品牌官網、客製化系統還是應用程式,我們的團隊擁有豐富經驗,歡迎聯繫我們,讓專業為您的事業加分。