如何設計一個秒殺系統----學習總結


第一章學習總結——概覽https://time.geekbang.org/column/article/40153
1.秒殺主要解決問題——並發讀和並發寫。並發讀的核心優化理念是盡量減少用戶到服務端來讀取數據,或者讓他們讀更少的數據。並發寫的處理原則是在數據庫層面獨立出一個庫,做特殊的處理。另外針對秒殺系統做一些保護,針對意料之外的情況設計兜底方案,以防止最壞的情況發生。

2.從一個架構師的角度來看,要想打造並維護一個超大流量並發讀寫、高性能、高可用的系統,在整個用戶請求路徑上從瀏覽器到服務端我們要遵循幾個原則,就是要保證用戶請求的數據盡量少、請求數盡量少、路徑盡量短、依賴盡量少,並且不要有單點。

3.秒殺的整體架構可以概括為“穩、准、快”(高可用、一致性、高性能)幾個關鍵字。
所謂“穩”,就是整個系統架構要滿足高可用,流量符合預期時肯定要穩定,就是超出預期時也同樣不能掉鏈子,你要保證秒殺活動順利完成,即秒殺商品順利地賣出去,這個是最基本的前提。

“准”,就是秒殺 10 台 iPhone,那就只能成交 10 台,多一台少一台都不行。一旦庫存不對,那平台就要承擔損失,所以“准”就是要求保證數據的一致性。

“快”,“快”其實很好理解,它就是說系統的性能要足夠高,否則你怎么支撐這么大的流量呢?不光是服務端要做極致的性能優化,而且在整個請求鏈路上都要做協同的優化,每個地方快一點,整個系統就完美了。

所以從技術角度上看“穩、准、快”,就對應了我們架構上的高可用、一致性和高性能的要求。

高性能:
秒殺涉及大量的並發讀和並發寫,因此支持高並發訪問這點非常關鍵。主要包含數據的動靜分離方案、熱點的發現與隔離、請求的削峰與分層過濾、服務端的極致優化這 4 個方面。

一致性:
秒殺中商品減庫存的實現方式同樣關鍵。有限數量的商品在同一時刻被很多倍的請求同時來減庫存,減庫存又分為“拍下減庫存”“付款減庫存”以及預扣等幾種,在大並發更新的過程中都要保證數據的准確性。

高可用:
現實中總難免出現一些考慮不到的情況,所以要保證系統的高可用和正確性,還要設計一個 PlanB 來兜底,以便在最壞情況發生時仍然能夠從容應對。
——————————————————————————————————————————————————————————————————————————————————
第二章學習總結——設計秒殺系統應該注意的五個(4要1不要)架構原則。https://time.geekbang.org/column/article/40726
1.數據量盡量少:請求數據和返回數據都要盡量少,以減少CPU使用。

2.請求數要盡量少:減少額外請求,如合並js、css等,首屏HTML內聯所需的CSS、JS。

3.路徑要盡量短:減少節點數,相互強依賴的應用合並部署。節點越少出錯概率就越小,可用性就越高。所以縮短請求路徑不僅可以增加可用性,同樣可以有效提升性能(減少中間節點可以減少數據的序列化與反序列化),並減少延時(可以減少網絡傳輸耗時)。

4.強依賴盡量少:給數據重要性分等級,盡量減少所要加載的數據。

5.不要有單點,要有備份,如設計分布式系統,關鍵點是把服務無狀態話,避免將服務的狀態和機器綁定,使服務可以在機器中隨意移動。

架構是一種平衡的藝術,而最好的架構一旦脫離了它所適應的場景,一切都將是空談。所以設計架構時上面幾點只是方向,應該根據實際情況適當取舍。

補充:
答疑:
1 .本地cache用什么實現好呢?
本地cache一般就是用內存實現,如java用集合類型就行
2. 通過什么方式往本地cache 寫數據呢?
用訂閱的方式,在初始化時加載到內存
3. 秒殺系統的及時性非常高,把庫存寫進cache ,怎么及時更新呢?
有兩種方法,一是定時更新取3秒,二是,主動更新,數據庫字段更新后發消息更新緩存,這個需要用到一個組件阿里叫metaq就是就是數據庫字段更新會產生一條消息。另外cache里庫存不需要100%和數據庫一致,最終強一致性即可
4.各QPS級別架構可能瓶頸點?
不同QPS量級下瓶頸也會不一樣,10w級別可能瓶頸就在數據讀取上,通過增加緩存一般就能解決,如果要到100w那么,可能服務端的網絡都是瓶頸,所以要把大部分的靜態數據放到cdn上甚至緩存在瀏覽器里。
5.單點是什么?
單點就是一個狀態值存儲在一台機器上,這台機器掛了,這個狀態就丟了,導致整個服務不可用。最常用的就是本機存儲數據,而這個數據沒有備份的情況

架構示例:
1-10萬QPS級別架構設計示例圖:

100萬級別QPS架構示意圖:

————————————————————————————————————————————————————————————————————————————————————
第三章:如何才能做好動靜分離?有哪些方案可選?https://time.geekbang.org/column/article/40727
1.what?

2.how?

3.動態內容的處理方式:

4.靜態數據的處理方式:

5.動靜分離的幾種架構:
實體機單機部署;統一Cache層;上CDN。

6.問題一:
存儲在瀏覽器或 CDN 上,有多大區別?
在 CDN 上,我們可以做主動失效,而在用戶的瀏覽器里就更不可控,如果用戶不主動刷新的話,你很難主動地把消息推送給用戶的瀏覽器。

7.問題二:
在什么地方把靜態數據和動態數據合並並渲染出一個完整的頁面也很關鍵,假如在用戶的瀏覽器里合並,那么服務端可以減少渲染整個頁面的 CPU 消耗。如果在服務端合並的話,就要考慮緩存的數據是否進行 Gzip 壓縮了:如果緩存 Gzip 壓縮后的靜態數據可以減少緩存的數據量,但是進行頁面合並渲染時就要先解壓,然后再壓縮完整的頁面數據輸出給用戶;如果緩存未壓縮的靜態數據,這樣不用解壓靜態數據,但是會增加緩存容量。雖然這些都是細節問題,但你在設計架構方案時都需要考慮清楚。
————————————————————————————————————————————————————————————————————————————————————
第四講:流量削峰這是該怎么做?(https://time.geekbang.org/column/article/40736)
一個是通過隊列來緩沖請求,即控制請求的發出;一個是通過答題來延長請求發出的時間,在請求發出后承接請求時進行控制,最后再對不符合條件的請求進行過濾;最后一種是對請求進行分層過濾。

削峰的存在,一是可以讓服務端處理變得更加平穩,二是可以節省服務器的資源成本。針對秒殺這一場景,削峰從本質上來說就是更多地延緩用戶請求的發出,以便減少和過濾掉一些無效請求,它遵從“請求數要盡量少”的原則。

無損削峰的一般思路:排隊 答題 分層過略

排隊:把同步的直接調用轉換成異步的間接推送,中間通過一個隊列在一端承接瞬時的流量洪峰,在另一端平滑地將消息推送出去。

答題:目的一防止部分買家使用秒殺器作弊;目的二延緩請求,起到對請求流量進行削峰的目的從而更好的支持瞬時流量高峰
答題設計思路:

題庫生成模塊:這個部分主要就是生成一個個問題和答案,其實題目和答案本身並不需要很復雜,重要的是能夠防止由機器來算出結果,即防止秒殺器來答題。
題庫的推送模塊:用於在秒殺答題前,把題目提前推送給詳情系統和交易系統。題庫的推送主要是為了保證每次用戶請求的題目是唯一的,目的也是防止答題作弊。
題目的圖片生成模塊:用於把題目生成為圖片格式,並且在圖片里增加一些干擾因素。這也同樣是為防止機器直接來答題,它要求只有人才能理解題目本身的含義。這里還要注意一點,由於答題時網絡比較擁擠,我們應該把題目的圖片提前推送到 CDN 上並且要進行預熱,不然的話當用戶真正請求題目時,圖片可能加載比較慢,從而影響答題的體驗。

庫存校驗設計圖:

分層過濾:

——————————————————————————————————————————————————————————————————————————————————————
第五講:影響性能的因素有哪些?又該如何提高系統的性能?(https://time.geekbang.org/column/article/40742)
CPU 主要看主頻、磁盤主要看 IOPS(Input/Output Operations Per Second,即每秒進行讀寫操作的次數);
系統服務端性能,一般用 QPS(Query Per Second,每秒請求數)來衡量,還有一個影響和 QPS 也息息相關,那就是響應時間(Response Time,RT),它可以理解為服務器處理響應的耗時。

理論上就變成了“總 QPS =(1000ms / 響應時間)× 線程數量”,這樣性能就和兩個因素相關了,一個是一次響應的服務端耗時,一個是處理請求的線程數。

1.響應時間對QPS的影響
真正對性能有影響的是 CPU 的執行時間。因為 CPU 的執行真正消耗了服務器的資源。經過實際的測試,如果減少 CPU 一半的執行時間,就可以增加一倍的 QPS。所以要想減少響應時間應該致力於減少CPU的執行時間。
2.線程數對QPS的影響

秒殺場景主要性能瓶頸在CPU,最常用的就是 JProfiler 和 Yourkit 這兩個工具,它們可以列出整個請求中每個函數的 CPU 執行時間,可以發現哪個函數消耗的 CPU 時間最多。

怎樣簡單地判斷 CPU 是不是瓶頸呢?一個辦法就是看當 QPS 達到極限時,你的服務器的 CPU 使用率是不是超過了 95%,如果沒有超過,那么表示 CPU 還有提升的空間,要么是有鎖限制,要么是有過多的本地 I/O 等待發生。

Java系統優化方法:
減少編碼:

減少序列化:

java極致優化:

並發讀優化:

一般思路:
1.發現短板=》減少數據(服務端的數據處理、網絡傳輸數據)=》數據分級(分清主次)=》減少中間環節,減少字符到字節的轉換,增加預處理(提前做好字符到字節的轉換),去掉不需要的操作=》做好優化

補充:
性能優化的核心就一個字-減
如:
1:異步化-減少等待響應的時間
2:降日志-減本地磁盤的交互
3:多級緩存-再減少獲取數據路徑
4:減功能-非核心功能或后補功能去掉

——————————————————————————————————————————————————————————————————————————————————
第六講:秒殺系統“減庫存”設計的核心邏輯https://time.geekbang.org/column/article/40743
減庫存的方式:
下單減庫存:
即當買家下單后,在商品的總庫存中減去買家購買數量。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種,下單時直接通過數據庫的事務機制控制商品庫存,這樣一定不會出現超賣的情況。但是你要知道,有些人下完單可能並不會付款。(不足之處在於可能存在惡意下單導致正常用戶無法或者難以正常秒殺)
付款減庫存:
即買家下單后,並不立即減庫存,而是等到有用戶付款后才真正減庫存,否則庫存一直保留給其他買家。但因為付款時才減庫存,如果並發比較高,有可能出現買家下單后付不了款的情況,因為可能商品已經被其他人買走了。(不足之處在於可能庫存超賣,假如有 100 件商品,就可能出現 300 人下單成功的情況,因為下單時不會減庫存,所以也就可能出現下單成功數遠遠超過真正庫存數的情況,這尤其會發生在做活動的熱門商品上。這樣一來,就會導致很多買家下單成功但是付不了款,買家的購物體驗自然比較差。)
預扣庫存:下單時先預扣,在規定時間內不付款再釋放庫存
這種方式相對復雜一些,買家下單后,庫存為其保留一定的時間(如 10 分鍾),超過這個時間,庫存將會自動釋放,釋放后其他買家就可以繼續購買。在買家付款前,系統會校驗該訂單的庫存是否還有保留:如果沒有保留,則再次嘗試預扣;如果庫存不足(也就是預扣失敗)則不允許繼續付款;如果預扣成功,則完成付款並實際地減去庫存。(這種方案確實可以在一定程度上緩解上面的問題。但是否就徹底解決了呢?其實沒有!針對惡意下單這種情況,雖然把有效的付款時間設置為 10 分鍾,但是惡意買家完全可以在 10 分鍾后再次下單,或者采用一次下單很多件的方式把庫存減完。針對這種情況,解決辦法還是要結合安全和反作弊的措施來制止。)

秒殺減庫存的極致優化:

補充:
下單和扣庫存兩個操作的事務性是怎么做的?
可以分兩步來做,先創建訂單但是先不生效,然后減庫存,如果減庫存成功后再生效訂單,否則訂單不生效
————————————————————————————————————————————————————————————————————————————————————
第七講:准備Plan B:如何設計兜底方案?https://time.geekbang.org/column/article/40744

高可用建設涉及方面:

高並發的秒殺系統兜底方案:降級、限流和拒絕服務。

降級的核心目標是犧牲次要的功能和用戶體驗來保證核心業務流程的穩定,是一個不得已而為之的舉措。

(客戶端和服務端限流是針對rpc調用來說的,發起方可以理解為客戶端,調用方可以理解為服務端,限流就是分別限制發起方和調用方的次數)
在限流的實現手段上來講,基於 QPS 和線程數的限流應用最多,最大 QPS 很容易通過壓測提前獲取,例如我們的系統最高支持 1w QPS 時,可以設置 8000 來進行限流保護。線程數限流在客戶端比較有效,例如在遠程調用時我們設置連接池的線程數,超出這個並發線程請求,就將線程進行排隊或者直接超時丟棄。

限流無疑會影響用戶的正常請求,所以必然會導致一部分用戶請求失敗,因此在系統處理這種異常時一定要設置超時時間,防止因被限流的請求不能 fast fail(快速失敗)而拖垮系統。

——————————————————————————————————————————————————————————————————————————————————————
第八講:答疑解惑https://time.geekbang.org/column/article/68247

  1. 應用層排隊的問題,應用層用隊列接受請求,然后結果怎么返回的問題。
    此處的排隊,更多地是說在服務端的服務調用之間采用排隊的策略。例如,秒殺需要調用商品服務、調用價格優惠服務或者是創建訂單服務,由於調用這些服務出現性能瓶頸,或者由於熱點請求過於集中導致遠程調用的連接數都被熱點請求占據,那么那些正常的商品請求(非秒殺商品)就得不到服務器的資源了,這樣對整個網站來說是不公平的。
    通常的解決方案就是在部分服務調用的地方對請求進行 Hash 分組,來限制一部分熱點請求過多地占用服務器資源,分組的策略就可以根據商品 ID 來進行 Hash,熱點商品的請求始終會進入一個分組中。
    結果的返回沒必要太關注,因為服務端接受請求本身就是按照請求順序處理的,而且這個處理在 Web 層是實時同步的,處理的結果也會立馬就返回給用戶。但是整個請求的處理涉及很多服務調用也涉及很多其他的系統,也會有部分的處理需要排隊,所以可能有部分先到的請求由於后面的一些排隊的服務拖慢,導致最終整個請求處理完成的時間反而比較后面的請求慢的情況。

采用請求對列方式的問題及做法:
問題:
一是體驗會比較差,因為是異步的方式,在頁面中搞個倒計時,處理的時間會長一點;
二是如果是根據入隊列的時間來判斷誰獲得秒殺商品,那也太沒有意思了,沒有運氣成分不也就沒有驚喜
做法:
一是頁面中采用輪詢的方式定時主動去服務端查詢結果,例如每秒請求一次服務端看看有沒有處理結果(現在很多支付頁面都采用了這種策略),這種方式的缺點是服務端的請求數會增加不少。
二是采用主動 push 的方式,這種就要求服務端和客戶端保持連接了,服務端處理完請求主動 push 給客戶端,這種方式的缺點是服務端的連接數會比較多。

2.一個商品數據存儲在多個 Cache 實例中,如何保證數據一致性
Hash 分組可以基於 Nginx+Varnish 實現的,Nginx 把請求的 URL 中的商品 ID 進行 Hash 並路由到一個 upstream 中,這個 upstream 掛載一個 Varnish 分組(如下圖所示)。這樣,一個相同的商品就可以隨機訪問一個分組的任意一台 Varnish 機器了。


3.Cache失效問題:
為了保證數據的一致性,所以必然存在失效。
失效方式:
被動失效,主要處理如模板變更和一些對時效性不太敏感數據的失效,采用設置一定時間長度(如只緩存 3 秒鍾)這種自動失效的方式。當然,你也要開發一個后台管理界面,以便能夠在緊急情況下手工失效某些 Cache。
主動失效,一般有 Cache 失效中心監控數據庫表變化發送失效請求、系統發布也需要清空 Cache 數據等幾種場景。其中失效中心承擔了主要的失效功能,這個失效中心的邏輯圖如下:

失效中心會監控關鍵數據表的變更(有個中間件來解析 MySQL 的 binglog,然后發現有 Insert、Update、Delete 等操作時,會把變更前的數據以及要變更的數據轉成一個消息發送給訂閱方),通過這種方式來發送失效請求給 Cache,從而清除 Cache 數據。如果 Cache 數據放在 CDN 上,那么也可以采用類似的方式來設計級聯的失效結構,采用主動發請求給 Cache 軟件失效的方式,如下圖所示:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM