Skip to content
Site Stats
總訪問量
訪客數人次

Redis 秒殺|不能插隊的扣庫存與削峰

上一篇我們停在這張危險圖:兩個人都讀到「還剩 1」,各自減一,最後變成超賣。

問題不在「減一」這個動作本身,而在它被拆成兩步————中間還能被人插隊。

怎樣才算「不能插隊」?很簡單:櫃檯規定這兩步必須黏在一起。

危險:先讀,再減A 讀到 1B 也讀到 1中間被人插隊 → 超賣目標:一次做完查庫存 + 決定 + 扣 1中間不開放插隊這種「黏成一次」的性質,就叫原子性(先建立直覺,細節慢慢補)

從這裡開始,我們要的不是更快的「先讀再減」,而是:在 Redis 裡把查與扣黏成一次、別人插不進來的動作。

為什麼 Redis 適合當「不能插隊」的櫃檯

Redis 處理指令時,可以想成:櫃檯一次只完整服務一位客人。

這位客人的「查庫存 → 決定能不能賣 → 扣 1」若被綁成一套手續,下一位要等這套做完才輪到他——中間沒有「兩個人同時讀到 1」的空隙。

客人 A 的整套手續查 → 決定 → 扣做完才輪到客人 B同一套手續排隊中…重點不是「世界只有一個執行緒」的考試定義,而是:黏在一起的手續,中間插不進別人的讀寫

實務上,這套手續可以是 Redis 內建的原子指令,或一小段在伺服器端一次跑完的腳本——你只要記住目的:查與扣不要拆開給網路來來回回。
細節與程式碼請看專案:redis-seckill 的秒殺熱路徑;本系列主角仍是 Redis 的角色分工,不是腳本語法課。

熱路徑最小劇本

櫃檯那套手續,縮到最小大概長這樣:

查:還有貨嗎?沒有回傳:已售完扣 1,回傳成功(可選)丟一張「稍後再記」的紙條

成功分支只做兩件必要的事:改庫存,以及(常常)留下一張稍後處理的紙條
紙條上可以寫:誰、買哪個、訂單編號、時間——夠 Worker 稍後把完整訂單與索引補齊就好。

熱路徑越短,轉圈圈越不容易回來。

為什麼不在熱路徑寫完整訂單?

若搶購成功的那一刻,還要順便做這些事:

  • 把整張訂單存成一份完整紀錄
  • 寫進「某用戶的歷史訂單」
  • 更新熱銷排行榜
  • 掛進後台分頁用的總索引

櫃檯手續會瞬間變長。人潮一來,你又把「單車道」從庫存格,搬到「寫一堆關聯資料」上——轉圈圈可能換個理由回來。

熱路徑(現在)扣庫存丟紙條短、快、回得了稍後再做訂單本體/用戶歷史排行榜/後台索引可以慢一點,但要齊兩種壓力:改同一個數字 vs 寫很多份關聯資料

所以設計上常拆成兩種壓力:

  1. 熱點數字 — 必須立刻、正確地改完(上一節的原子手續)。
  2. 關聯寫入 — 重要,但不該全部堵在用戶還在轉圈圈的那幾百毫秒裡。

紙條,就是把第 2 種壓力「延後」的橋。

紙條落地:Redis Streams

在 Redis 裡,這張「稍後再記」的紙條,常見落點就是 Stream(串流/訊息流)。

心智模型:一條有編號的傳送帶

  • 熱路徑成功扣庫存後,把一張小卡片(誰、哪個商品、訂單 id、時間…)放上傳送帶,然後立刻回覆使用者。
  • 旁邊的 Worker(工人) 依序取卡片,慢慢把完整訂單、索引、排行榜補齊。
熱路徑扣庫存後投信Redis Stream(傳送帶)每張卡片有編號,可依序處理Worker取信並補寫資料使用者不用等傳送帶跑完;尖峰卡片可以在後面幾秒被消化

這就是常說的削峰:瞬間湧進的寫入,變成傳送帶上的卡片,讓工人用自己的節奏處理,而不是全部堵在用戶還盯著轉圈圈的那一刻。

指令名稱(例如往 Stream 加一筆、工人用消費組讀取)記不記得沒關係;先記住 Redis 自己就能當這條傳送帶。實作見 redis-seckill 的 Worker。

整條路再看一次

把本篇拼起來,秒殺當下的資料流大概是這樣:

使用者搶購① 熱路徑原子:查+扣立刻回成功/售完② Stream紙條排隊③ Worker補訂單/索引排行榜等① 要快且不能插隊 ② 扛尖峰 ③ 把帳記齊——都還在 Redis 生態裡完成

使用者只感受到 ① 的快慢;②③ 是系統在背後把尖峰寫入攤平、把資料補齊。

本篇小结

📝 TL;DR:用「黏成一次」的手續避免超賣;熱路徑只扣庫存並丟 Stream 紙條;Worker 稍後補寫訂單與索引,這叫削峰。

你帶走三件事:

  1. 原子性(直覺) — 查+扣不要拆開被人插隊。
  2. 熱路徑要短 — 完整訂單與一堆索引,別堵在轉圈圈那幾百毫秒。
  3. Stream + Worker — Redis 當傳送帶,把關聯寫入延後消化。

下一篇會講:Worker 補寫時,那些「索引」在 Redis 裡長什麼樣子——沒有 SQL 的 ORDER BYJOIN,怎麼用 ZSet、List 做出後台分頁與排行榜。
專案:lucashsu95/redis-seckill