最近 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 入門那篇的選型表。
真正防超收的三招:條件更新、原子扣減、唯一約束
一句話:讓「檢查」和「扣除」變成同一個動作,再用唯一約束當最後保險。三招可以單獨用,也可以疊著用。
第一招:資料庫條件更新,看影響筆數
最簡單、也最常被忽略的一招,就是把「還有名額嗎」直接寫進扣名額的那一行指令:
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 官方文件保證腳本執行期間不會被其他指令插隊:
-- 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)是資料庫層的規則,比喻成「同一張身分證只能領一次」:不管程式寫得多亂、請求重送幾次,同一位學生同一門課的紀錄只會存在一筆,第二筆會直接被資料庫拒絕。
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)
尚無留言,成為第一個留言的人吧!