實際應用場景(大模型生成)
場景設定:設計淘寶“雙十一”零點秒殺一個超熱門商品(比如 茅台)的後端系統
-
業務挑戰:
- 超高峯值流量:在零點鐘聲敲響的第一秒,可能會有數百萬用户同時點擊“搶購”按鈕。
- 數據強一致性:庫存只有 1000 瓶,絕對不能超賣。即賣出第 1001 瓶。
- 高可用性:系統不能崩潰。
- 公平性:儘量保證先到先得。
實施過程:一場精心編排的併發“戰役”
戰役前線:流量接入與削峯 (API 網關 & 消息隊列)
-
用户請求入口:
- 用户的請求首先會經過阿里雲的 CDN 和多層負載均衡 (SLB),這部分負責抵禦 DDoS 攻擊和進行初步的流量分發。
- 流量最終到達API 網關集羣(比如基於 Nginx/OpenResty 自研的網關)。
- (應用): 反向代理, 負載均衡器
-
API 網關的初步攔截:
- 網關層會執行一個前置的、粗粒度的限流。比如通過令牌桶算法,判斷這一秒的總請求數是不是已經超過了系統能承受的最大閾值(比如 200 萬次)。超過的請求,會直接返回“系統繁忙,請稍後再試”,根本不會進入後端。
- (應用): 令牌桶
-
異步化解耦 (核心步驟):
- 對於通過了限流的請求,API 網關不會直接調用後端的秒殺服務。
- 它會做一件極其重要的事:把這個搶購請求(包含 user_id, item_id 等信息)序列化後,以極快的速度扔進一個 Apache Kafka 或 阿里雲 RocketMQ (消息隊列) 的 Topic 裏,比如 seckill_request_topic。
- 然後,網關立刻給用户返回一個“排隊中,請稍後查看結果”的友好提示。
- (應用): 分佈式消息系統 (Kafka/RabbitMQ)
-
為什麼這麼做?
- 削峯填谷:把第一秒涌入的數百萬請求,變成了一個平穩流入的消息流。後端系統可以按照自己的節奏去消費,避免了被瞬間流量打垮。
- 解耦:接入層和處理層分離,任何一方升級或故障,不影響另一方。
戰役中場:核心庫存處理 (分佈式鎖 & 緩存)
現在,請求已經變成了 Kafka 裏的消息。我們有一組專門的 Go 服務(秒殺處理服務)來消費這些消息。
-
消費消息:
- 秒殺處理服務作為一個消費者組,從 Kafka 中拉取搶購消息。
- (應用): Go併發 - Worker Pool: 我們可以用一個協程池來併發地處理這些消息,以提高吞吐量。
- 庫存預檢查 (緩存層):
- 服務拿到一個請求後,不會立刻去查數據庫。
- 它會先訪問一個分佈式緩存 (Redis),檢查一個特殊的庫存標記位,比如 GET stock:maotai:flag。
- 如果這個標記位顯示“已售罄”,服務會直接丟棄這個請求,連鎖都不用搶了。這可以過濾掉絕大部分無效請求。
- (應用): 服務器端緩存 (Redis)
-
爭搶分佈式鎖 (最關鍵的一步):
- 如果緩存顯示還有庫存,現在就進入了最關鍵的“搶鎖”環節。
- 服務會嘗試獲取一個針對這個商品的分佈式鎖,比如用 Redis 的 SET stock_lock:maotai user_id NX PX 1000 命令。
- NX 保證了只有一個客户端能設置成功。
- PX 1000 設置了一個較短的超時時間(1秒),防止因某個節點宕機而導致死鎖。
- (應用): 分佈式併發原語 - 分佈式鎖 (用 Redis 實現), 強一致性
-
扣減庫存 (數據庫操作):
- 成功搶到鎖的那個 goroutine,獲得了唯一的操作庫存的權限。
-
它會去數據庫 (MySQL) 裏,執行一個事務:
- SELECT stock FROM items WHERE item_id = 'maotai' FOR UPDATE; (悲觀鎖,再次確認庫存)
- 如果 stock > 0,則 UPDATE items SET stock = stock - 1 WHERE item_id = 'maotai';
- INSERT INTO orders ...; (創建訂單)
- COMMIT;
- 操作完成後,立刻釋放分佈式鎖 (DEL stock_lock:maotai)。
- (應用): SQL 數據庫, ACID 事務
-
更新緩存標記:
- 如果這次扣減導致庫存變成了 0,那麼這個 goroutine 還需要負責去更新 Redis 裏的那個標記位 SET stock:maotai:flag "sold_out"。
戰役收尾:結果通知與數據同步
-
結果通知:
- 秒殺處理服務在創建訂單成功後,會把結果(“搶購成功,訂單號是xxx”)再發送到另一個 Kafka Topic 裏,比如 seckill_result_topic。
- 有一個專門的通知服務,會訂閲這個 Topic,然後通過WebSocket或移動推送,把結果實時地推送給用户。
- 對於失敗的用户,可以在一段時間後統一告知“已售罄”。
- (應用): WebSocket
-
數據同步與分析:
- 訂單數據、用户行為日誌等,都會被持續地寫入 Kafka。
- 下游的大數據平台 (MaxCompute) 會消費這些消息,進行批處理和流處理,用於生成報表、風控分析、實時大屏等。
- (應用): 批處理與流處理
這個過程中用到的其他“Go併發”知識
- errgroup: 在一個服務內部,如果需要同時調用多個下游依賴(比如同時查詢用户信息和風控評分),會用 errgroup 來進行併發編排,並確保一個失敗,全部取消。
- context: 貫穿全程! 從 API 網關接收到請求的那一刻起,就會創建一個帶 trace_id 和超時時間的 Context。這個 Context 會被一路傳遞到 Kafka 消息體中,再到秒殺處理服務,再到數據庫調用,實現了全鏈路追蹤和超時控制。
- singleflight: 在查詢一些不那麼頻繁變化的熱點數據時(比如商品的靜態描述信息),會用 singleflight 來防止緩存擊穿。
這個例子,幾乎把你筆記裏所有重要的分佈式和併發概念都串聯起來了。它清晰地展示了在面對極端併發挑戰時,一個大型系統是如何通過分層、解耦、異步化和各種併發原語的組合來保證系統的穩定性和數據一致性的。
場景設定:設計B站/騰訊視頻,支撐一個千萬播放量、百萬評論的爆款視頻
-
業務挑戰:
- 海量讀取 (Read Heavy):一個熱門視頻,可能會有數十萬 QPS(每秒請求數)來讀取它的評論。數據庫根本扛不住。
- 海量存儲:一個視頻可能有幾百萬條評論,所有視頻加起來是千億甚至萬億級別。單張 MySQL 表無法存儲。
- 寫入/讀取的實時性:用户剛發表的評論,需要近乎實時地被其他人看到。
- 複雜的查詢需求:需要按時間排序、按熱度(點贊數)排序、支持分頁。
實施過程:一場“讀寫分離”與“多級緩存”的藝術
第一階段:評論的寫入 (Write Path) —— “異步化、分而治之”
當一個用户點擊“發表評論”時:
-
API 網關接收請求:
- 請求 POST /api/v2/comment/add 到達 B 站的 API 網關。
- 網關進行基礎的認證(通過 JWT 確認用户身份)、鑑權(用户是否被禁言)、內容安全審核(調用 AI 服務檢測違規內容)。
- (應用): API 網關, 無狀態架構
-
評論寫入服務 (Go):
- 網關將合法的請求轉發給後端的“評論寫入服務”。
-
這個服務不直接寫主數據庫!它會做兩件事:
- 寫入消息隊列 (Kafka/RocketMQ):將完整的評論內容(comment_id, video_id, user_id, content, timestamp)打包成一條消息,發送到 comment_add_topic。這是為了後續的數據同步和分析。
- 寫入“評論審核庫” (MySQL):將評論寫入一個專門的、結構簡單的審核數據庫,狀態為“審核中”。這個庫只負責存儲新評論,寫入壓力相對可控。
- 寫入成功後,立刻給前端返回“發表成功”。
- (應用): 分佈式消息系統, SQL數據庫 (用於寫入)
-
異步處理與數據同步:
- 有一個後台的“評論同步服務”(消費者),會訂閲 comment_add_topic。
- 它拿到新評論數據後,會將其寫入真正的主存儲系統。
-
主存儲的選擇:對於評論這種數據,傳統 MySQL 很難水平擴展。大廠通常會選擇列式 NoSQL 數據庫,如 HBase 或 Cassandra。
- 分區/分片:數據會以 video_id 作為分區鍵 (Partition Key) 進行水平分區。所有同一個視頻的評論,都會被路由到同一個分片上,便於按視頻聚合查詢。
- (應用): NoSQL (寬列), 數據分區 (水平分區)
第二階段:評論的讀取 (Read Path) —— “層層設防,多級緩存”
當成千上萬的用户刷新評論區時:
-
第一道防線:客户端緩存 (Client-Side Cache)
- App/瀏覽器可能會在本地緩存第一頁的評論。當用户短時間內反覆進入頁面,可以直接從本地加載,連網絡請求都不發。
- (應用): 客户端緩存
-
第二道防線:CDN 緩存
- 對於極度熱門且不常變化的數據(比如一個視頻的“精選評論”或“置頂評論”),可以把獲取這些評論的 API 接口配置到 CDN 上,緩存幾十秒。
- (應用): CDN
-
第三道防線:分佈式緩存集羣 (Redis) —— 主力防線
- 這是最核心的緩存層。當請求穿透 CDN 到達源站的“評論讀取服務” (Go) 時,服務絕對不會直接去查 HBase/Cassandra。
- 它會先去 Redis 集羣裏查詢。
-
緩存設計:
- 按頁緩存:Redis 中的 Key 設計成 comment:video_id:page_1:by_time,Value 就是第一頁評論列表的 JSON 字符串。
- 數據結構:可以用 String 存預先序列化好的 JSON,也可以用 ZSet (有序集合) 來存儲評論,score 是時間戳或點贊數,便於排序和分頁。
- 絕大多數(99.9%)的讀請求,都會在這一層被命中並直接返回。
- (應用): 服務器端緩存 (Redis)
-
第四道防線:本地內存緩存 (groupcache / freecache)
- 在“評論讀取服務”的進程內部,還可以再加一層本地內存緩存。
- 當服務A從 Redis 加載了 video_id:123 的第一頁評論後,它會把這個結果在自己的內存裏再緩存幾秒鐘。
- 如果下一秒,又有 100 個請求被負載均衡器打到了服務A上,要查詢同一個視頻的同一頁評論,那麼這 100 個請求會直接命中本地內存緩存,連 Redis 都不用訪問了!
- (應用): 內存緩存 (應用內), groupcache (如果需要節點間協作)
-
終極防線:數據源 (HBase/Cassandra)
- 只有當所有緩存層都未命中時(比如一個冷門視頻的第一條評論,或者熱門視頻的緩存剛好過期),請求才會到達最終的數據源。
-
此時,會觸發“回源”邏輯:
- 獲取分佈式鎖 (用 etcd 或 Redis 實現),防止緩存擊穿。第一個拿到鎖的請求去 HBase/Cassandra 查詢數據。
- 查詢到數據後,重建各級緩存(寫入本地內存、寫入 Redis)。
- 釋放鎖,並把數據返回給用户。
- (應用): 分佈式鎖 (etcd/Redis), 緩存擊穿防護 (SingleFlight)
這個架構中 Go 併發原語的應用
- WaitGroup / errgroup: 在“評論讀取服務”中,可能需要聚合評論數據和UP主的附加信息,這時會用 errgroup 併發地調用評論數據源和用户服務。
- sync.Pool: 在進行大量的 JSON 序列化/反序列化時,會用 sync.Pool 來複用 encoder/decoder 和 buffer,降低 GC 壓力。
- Channel: 在服務內部,用於不同組件間的異步消息傳遞,比如將需要更新緩存的任務扔給一個專門的 goroutine 去處理。
這個場景,完美地展示了為了支撐超高讀取 QPS,一個系統是如何通過層層緩存來構建縱深防禦體系的。寫入時通過異步化和分區來分散壓力,讀取時則想盡一切辦法避免訪問底層存儲。
好幾個月以來,非常喜歡讓大模型陪我學習或者是指導我學習,雖然不能保證完全的正確性,但我還是黏上了它,分不開了啊屬於是🤣你們如果覺得內容有誤或者與自己的理解不同,請儘量先堅持自己的想法,這些內容僅供參考