iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
Cloud Native

Go 語言搶票煉金術:解鎖千萬級併發下的原子交易奇蹟系列 第 9

Go 語言搶票煉金術 Day 9 - 識別真正敵人:為什麼磁碟 I/O 是不可逾越之牆

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250915/20124462YARMcXUyfa.png

Go 語言搶票煉金術 Day 9 - 識別真正敵人:為什麼磁碟 I/O 是不可逾越之牆

在上一篇,我們的壓力測試在 1500 RPS 時撞牆了。
根本原因是資料庫為了保證資料的正確性,在單一熱點資料上產生了鎖競爭。

今天,我們來深入探討這堵牆的本質。
它不是資料庫的「錯」,而是它恪守其最重要承諾所付出的物理代價

承諾的代價:耐久性 (Durability)

這是電腦科學中關於權衡 (Trade-offs) 的工程學科。
資料庫之所以慢,不是因為它「笨」,而是因為它對你做出了一個神聖的承諾:ACID 中的 D - Durability (耐久性)

這個承諾意味著,一旦資料庫告訴你「交易成功」,那麼即使下一毫秒機房斷電、伺服器爆炸,資料也必須完好無損地存在於磁碟上。

為了兌現這個承諾,它必須執行這個世界上最慢的操作之一:將資料同步寫入磁碟 (fsync)

所有性能問題的根源,都來自於下面這個簡單的事實:

  • 在 CPU L1 快取中操作: ~1 奈秒

  • 從主記憶體 (RAM) 讀寫: ~100 奈秒

  • 將 4KB 資料同步寫入 NVMe SSD: ~15,000 - 50,000 奈秒 (15-50 微秒)

從記憶體到磁碟,有數百倍的鴻溝。
每一次 UPDATE,資料庫都必須跨越這條鴻溝,把交易日誌(WAL/Redo Log)刷到磁碟上。

而資料庫,為了保證你的交易在斷電後依然存在 (ACID 中的 D),它必須把交易日誌 (WAL/Redo Log) 寫入磁碟。
每一次 UPDATE,無論多小,都至少觸發一次昂貴的「出門開車」操作。

我們看到的 207ms P95 延遲,本質上就是成千上萬個請求,排隊等待跨越這條鴻溝的成本總和。
https://ithelp.ithome.com.tw/upload/images/20250923/20124462gPstXOvpe1.png

熱點,那個你不停毆打的點 (The Hotspot)

我們的搶票請求,有 99.99% 的時間都在做同一件事:

UPDATE tickets SET quantity = quantity - 1 WHERE id = 1;

成千上萬的請求,都在毆打同一筆資料(讀寫同一筆資料),這筆資料被稱為熱點 (Hotspot)

為了保證庫存不會被超賣(I - IsolationC - Consistency),資料庫必須上一個行級鎖 (Row-level Lock)

上鎖是唯一正確的事情。
但它的副作用是災難性的:強制序列化 (Serialization)

你發起了 1500 個並行請求,但在資料庫的熱點資料面前,它們被迫排成一條長隊,一個一個來。並行的優勢在這裡蕩然無存。
隊伍越長,排在後面的人等待的時間就越久。這就是為什麼 P95 延遲會失控。

更換戰場:一次誠實的權衡

我們面臨的困境是:

  1. 我們需要原子性來保證庫存正確。

  2. 資料庫透過鎖 + 磁碟 I/O 來提供最強的原子性和持久性保證。

  3. 這套機制的物理極限,在我們的熱點場景下就是 1500 RPS。

結論不是「資料庫是垃圾」,而是 「我們不能在要求強持久性的戰場上,進行一場需要極高吞吐量的閃電戰」

我們需要一個不同的模型,這個模型必須誠實地做出權衡:我們願意犧牲一部分持久性的保證,來換取數量級的效能提升。

這才是我們引入 Redis 的真正原因:

  • 基於記憶體: 徹底擺脫了磁碟 I/O 的枷鎖。

  • 單執行緒模型: 從根本上避免了複雜的鎖競爭問題,所有命令天生就是序列化的。

  • 原子指令: 提供了像 DECRBY 這樣專為計數器場景設計的、極度高效的原子操作。

新的架構,新的問題

我們不是用 Redis 取代 資料庫。
我們是把它當作一個承受衝擊的高速前置處理器 (High-Throughput Pre-processor)

  • 資料庫 依然是 「記錄系統」 (System of Record),是資料的最終真理。

  • Redis 則是 「交戰系統」 (System of Engagement),處理所有高併發的即時請求。

但這個新架構帶來了新的、必須回答的問題:

  1. 如果 Redis 掛了怎麼辦? 放在記憶體裡的資料會瞬間消失。我們必須啟用 Redis 的持久化機制,AOF 配置為 everysec。意思是:「我們可以接受在災難發生時,遺失最多一秒鐘的資料」。

  2. 資料如何同步回資料庫? 在 Redis 中 DECRBY 成功後,必須有一個可靠的機制去更新資料庫。
    一個常見的做法是,應用程式在操作 Redis 後,將一個「更新庫存」的任務放入一個持久化的訊息佇列 (Message Queue) 中,由一個後台消費者慢慢地、批量地更新資料庫。

  3. 如何保證資料最終一致? 需要有對帳 (Reconciliation) 程序。
    在搶票結束後,需要有一個工作來核對 Redis 的最終庫存和資料庫中的記錄,確保沒有偏差。

最終結論:停止尋找銀彈

這是一個需要改變架構接受權衡的工程問題。

把熱點庫存計數從資料庫移到 Redis,是因為可以用記憶體的效能,並有能力處理隨之而來的持久化和一致性挑戰。

下一章,我們就來正式認識這位,Redis。

參考資源


上一篇
Go 語言搶票煉金術 Day 8 - 度量標準:來寫你的第一個壓力測試
系列文
Go 語言搶票煉金術:解鎖千萬級併發下的原子交易奇蹟9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言