幾年前,公司組織了一次技術競賽。自由組隊,每組4人,36小時以內按要求完成設計、代碼實現和文檔。第一名獎金3萬元。前端
一開始不是很想參加,由於要通宵熬夜(如今想一想,不參加競賽是個明智之舉)!不事後來仍是接收了其餘部門的邀請,參賽了。可是,在比賽前一天,其餘三我的放我鴿子!!!放我鴿子!!!放我鴿子!!!沒辦法,只能從新組隊,最終兩個後端、兩個前端!可是,比賽當天,另外一個後端去參加歌唱比賽了!!!參加歌唱比賽了!!!參加歌唱比賽了!!!因此變成了一個後端+兩個前端。咱們成了當時參賽小組裏惟一的三人小組,惟一的有前端的小組,仍是兩個前端!!!node
而後比賽的題目是:實現一個秒殺系統!!!web
當時咱們三個就想直接棄權了!可是轉念一想,都走到這一步了,仍是試試吧!數據庫
最終的結果是,咱們沒完成(意不意外?驚不驚喜?)!!!前端在最後時間點纔開發完成,測試的時間都沒有!後來測試的時候,修復了幾個bug,爲了公平起見,就沒有參與最後的比賽!後端
不過,在全部的參賽隊伍中,咱們的吞吐量是最高的,其餘隊伍的TPS基本在三四千左右!咱們的TPS基本在一萬七左右!若是一開始就完成了,基本秒殺其餘組!瀏覽器
咱們的吞吐量是其它組的四倍,究其緣由是由於咱們的人員組成與其餘組不一樣,致使咱們的設計思路與其餘組的設計思路也不一樣。緩存
在以前的什麼是軟件架構一文中,我對軟件架構作了一個定義:「架構是特定約束下決策的結果」!服務器
此次技術競賽的經歷,正好能夠驗證這一觀點:即便是在技術不對口、人數不足等劣勢條件下,只要作出合適的決策,依然能作出超出預期的架構設計!markdown
有時候,對於合適的架構設計,劣勢有時候會成爲優點!架構
咱們先來看下,對於秒殺系統來講,通常的設計思路。
秒殺系統的特色是:
因此秒殺系統須要解決的是「在高併發狀況下,用戶請求及數據更新的問題」!
通常的設計思路:
具體方式有:
動靜分離
對於通常的應用來講,請求流程大體以下:
當訪問量很大的時候,服務器壓力會很是的大!解決方案就是動靜分離!
作軟件開發的都知道要「將變化的內容和不變的內容隔離開」,以便於獨立進化。這裏其實也是同樣的思路。
模板是個靜態的內容,部署後通常是不會變化的;而數據是個相對動態的內容,根據請求參數的不一樣,數據可能不一樣。因此咱們須要將模板與數據分離。
之前的作法是後端事先生成渲染後的頁面,緩存起來或直接部署到靜態服務器或CDN,請求時直接從緩存(靜態服務器/CDN)中獲取頁面,而動態數據經過AJAX請求的方式獲取。服務器再也不須要渲染頁面,只須要返回少許的數據便可。既下降了服務器壓力,又減小了服務端數據的傳輸。
而如今很流行的先後端分離就能很容易的解決這個問題。頁面獨立部署,數據異步獲取,頁面渲染由瀏覽器負責。這裏和普通的先後端分離還有些差別,須要將相對靜態的數據都靜態化,以減小動態數據量。
分離後,靜態內容和動態內容就能夠獨立進化。例如靜態內容能夠部署到CDN上,用戶能夠從最近的服務器獲取到數據。相對熱點的動態數據能夠作緩存,下降數據庫壓力,進一步提升服務端響應。
獨立部署
「獨立部署」其實也能夠當作是一種「動靜分離」。將秒殺系統這個相對動態的系統,和相對靜態的業務系統分開部署。
緣由很好理解,秒殺系統的請求量很大,可能會因爲預估不足或系統問題,致使了秒殺系統的負載太高、響應變慢。若是秒殺系統是業務系統的一部分,則會致使業務系統響應變慢,甚至致使系統沒有響應。且秒殺是個短時間活動也不是核心業務,而業務系統是須要長期穩定運行的。不能由於一個短時間非核心的活動,而影響了核心的業務系統。
因此秒殺系統最好和業務系統分開獨立部署。即便秒殺系統掛了,也不會影響業務系統的正常對外服務。
一樣的道理,秒殺系統的數據庫也須要和業務系統的數據庫獨立開。
限流削峯
動靜分離,獨立部署能提升系統的響應能力和容量。可是可提供的訪問量是必定的,當超過了系統所能承受的容量,該怎麼辦呢?你可能會說,能夠擴容啊。的確是能夠,可是擴容也是有限度的。假設單機能承受10萬的請求量,預計有1億的請求量,你要擴容1000臺服務器?!這會致使嚴重的浪費。
首先,上面提到了,秒殺是短時間活動,爲了秒殺多部署1000臺服務器,秒殺結束後這些服務器再銷燬?既浪費硬件資源、又浪費人力資源。
其次,秒殺的商品數量其實並很少,可能秒殺賺的那點錢還不夠付服務器和帶寬的費用。真·花錢賺吆喝!
咱們該如何處理呢?
上面說了,秒殺的商品數量很少,也就是說,其實最後的真實成交量並不大。再進一步講,不少的請求都是沒用的。
其次,在秒殺前,買家會頻繁的刷頁面,這又額外增長了無用請求的數量。
咱們只要把這些無用的請求提早都過濾掉,最終到達服務端的請求就會少不少,也就不須要這麼多的服務器了。這就是限流削峯。具體作法有不少:
服務端優化
上面的「請求排隊」,能夠作在web服務層,也能夠在服務端處理,亦能夠兩處都處理。除了排隊,服務端的優化的核心手段就是緩存,儘可能減小到數據庫的數據訪問,將熱點數據緩存起來。
更極致的優化可能還涉及到:
另外還有扣庫存邏輯處理:
上面說的秒殺系統的通常設計思路。下面來看看咱們是怎麼鑽漏洞的!
因爲競賽規定,只能使用Java和Spring來處理,對其餘隊來講,這就致使了一個比較棘手的問題,IO優化。
Java支持兩種IO,BIO和NIO。對於秒殺這種場景來講,確定不能使用BIO。可是,若是使用Java原生NIO的話,須要處理不少問題,例如半包問題。以最終結果來看,選用原生NIO本身手寫異步IO框架的,全都以失敗了結。
那就只有另一條路,寧肯扣分,也要使用第三方框架,例如Netty。這就是賭,賭使用JavaNIO的隊伍沒法完成系統了。
而對咱們隊來講,咱們原先的劣勢---前端、一會兒就變成了優點。由於前端有node啊(若是沒有node,咱們妥妥的棄賽了)。當時node剛火起來,node天生就是個異步IO框架,競賽規定裏,可沒有對前端技術作技術限制!這個漏洞,咱們怎麼能不鑽?!
最終競賽評比時,我發現一個問題,其餘隊伍很看重公平!!!即先到先得原則,優先到達的請求,優先排隊下單。這就致使,在秒殺結束前或請求被處理前,都須要等待,直到服務器處理後纔有返回。
這明顯增長了服務端的壓力,這也是致使他們的吞吐量限制在4000左右的緣由。但不是根本緣由。
根本緣由是這樣作就真的公平嗎?!這就要看每一個人對公平的理解了!我認爲這世上「沒有絕對的公平,只有相對的公平」!
你在秒殺系統裏排隊,保證先到先得,這就是公平嗎?
既然不能,我爲何要在服務端保證公平呢?!
秒殺就是拼個運氣,只要不暗箱操做,那就是公平的。因此咱們不保證先到達的請求就能先買到商品!客戶哪知道他是否是先到的呢(雖然這樣說,看起來不公平,但實際確實是這樣)。因此咱們放棄了所謂的公平。
咱們使用了兩個隊列:
大體請求流程以下:
人員、技術、考量點的不一樣都會影響架構設計。一個符合當前人員、技術以及適合考量點的架構,可能能獲得意想不到的效果。