#1 實現要求javascript
#2 實現效果css
#4 秒殺技術挑戰 假設一個場景,某網站秒殺活動只推出一件商品,預計會吸引1萬人參加活動,也就說最大併發請求數是10000,秒殺系統須要面對的技術挑戰有:html
解決方案:將秒殺系統獨立部署,甚至使用獨立域名,使其與網站徹底隔離。前端
解決方案:從新設計秒殺商品頁面,不使用網站原來的商品詳細頁面,頁面內容靜態化,用戶請求不須要通過應用服務(獨立開發一套屬於秒殺的服務)java
解決方案:由於秒殺新增的網絡帶寬,必須和運營商從新購買或者租借。爲了減輕網站服務器的壓力,須要將秒殺商品頁面緩存在CDN,一樣須要和CDN服務商臨時租借新增的出口帶寬。程序員
**解決方案:爲了不用戶直接訪問下單頁面URL,須要將改URL動態化,即便秒殺系統的開發者也沒法在秒殺開始前訪問下單頁面的URL。辦法是在下單頁面URL加入由服務器端生成的隨機數做爲參數,在秒殺開始的時候才能獲得(通常會設計一個token,時間還沒到,就沒法產生token,不管如何點擊都沒法觸發秒殺)。 **web
解決方案:使用JavaScript腳本控制,在秒殺商品靜態頁面中加入一個javascript文件引用,該JavaScript文件中包含 秒殺開始標誌爲否;當秒殺開始的時候生成一個新的JavaScript文件(文件名保持不變,只是內容不同),更新秒殺開始標誌爲是,加入下單頁面的URL及隨機數參數(這個隨機數只會產生一個,即全部人看到的URL都是同一個,服務器端能夠用Redis這種分佈式緩存服務器來保存隨機數),並被用戶瀏覽器加載,控制秒殺商品頁面的展現。這個JavaScript文件的加載能夠加上隨機版本號(例如xx.js?v=32353823),這樣就不會被瀏覽器、CDN和反向代理服務器緩存。 這個JavaScript文件很是小,即便每次瀏覽器刷新都訪問JavaScript文件服務器也不會對服務器集羣和網絡帶寬形成太大壓力redis
解決方案:假設下單服務器集羣有10臺服務器,每臺服務器只接受最多10個下單請求。在尚未人提交訂單成功以前,若是一臺服務器已經有十單了,而有的一單都沒處理,可能出現的用戶體驗不佳的場景是用戶第一次點擊購買按鈕進入已結束頁面,再刷新一下頁面,有可能被一單都沒有處理的服務器處理,進入了填寫訂單的頁面,能夠考慮經過cookie的方式來應對,符合一致性原則。固然能夠採用最少鏈接的負載均衡算法,出現上述狀況的機率大大下降。算法
定時秒殺的話,就要避免賣家在秒殺前對商品作編輯帶來的不可預期的影響。這種特殊的變動須要多方面評估。通常禁止編輯,如需變動,能夠走數據訂正多的流程。數據庫
10)庫存會帶來「超賣」的問題:售出數量多於庫存數量 因爲庫存併發更新的問題,致使在實際庫存已經不足的狀況下,庫存依然在減,致使賣家的商品賣得件數超過秒殺的預期。方案:採用樂觀鎖。
update auction_auctions set quantity = #inQuantity# where auction_id = #itemId# and quantity = #dbQuantity#
還有一種方式,會更好些,叫作嘗試扣減庫存,扣減庫存成功纔會進行下單邏輯:
update auction_auctions set quantity = quantity-#count# where auction_id = #itemId# and quantity >= #count#
傳統秒殺系統之因此掛,請求都壓倒了後端數據層,數據讀寫鎖衝突嚴重,併發高響應慢,幾乎全部請求都超時,流量雖大,下單成功的有效流量甚小【一趟火車其實只有2000張票,200w我的來買,基本沒有人能買成功,請求有效率爲0】。
這是一個典型的讀多寫少的應用場景【一趟火車其實只有2000張票,200w我的來買,最多2000我的下單成功,其餘人都是查詢庫存,寫比例只有0.1%,讀比例佔99.9%】,很是適合使用緩存。
秒殺系統爲秒殺而設計,不一樣於通常的網購行爲,參與秒殺活動的用戶更關心的是如何能快速刷新商品頁面,在秒殺開始的時候搶先進入下單頁面,而不是商品詳情等用戶體驗細節,所以秒殺系統的頁面設計應儘量簡單。
商品頁面中的購買按鈕只有在秒殺活動開始的時候才變亮,在此以前及秒殺商品賣出後,該按鈕都是灰色的,不能夠點擊。
下單表單也儘量簡單,購買數量只能是一個且不能夠修改,送貨地址和付款方式都使用用戶默認設置,沒有默認也能夠不填,容許等訂單提交後修改;只有第一個提交的訂單發送給網站的訂單子系統,其他用戶提交訂單後只能看到秒殺結束頁面。
要作一個這樣的秒殺系統,業務會分爲兩個階段,第一個階段是秒殺開始前某個時間到秒殺開始, 這個階段能夠稱之爲準備階段,用戶在準備階段等待秒殺; 第二個階段就是秒殺開始到全部參與秒殺的用戶得到秒殺結果, 這個就稱爲秒殺階段吧。
首先要有一個展現秒殺商品的頁面, 在這個頁面上作一個秒殺活動開始的倒計時, 在準備階段內用戶會陸續打開這個秒殺的頁面, 而且可能不停的刷新頁面。這裏須要考慮兩個問題:
2)第二個是倒計時 出於性能緣由這個通常由js調用客戶端本地時間,就有可能出現客戶端時鐘與服務器時鐘不一致,另外服務器之間也是有可能出現時鐘不一致。客戶端與服務器時鐘不一致能夠採用客戶端定時和服務器同步時間,這裏考慮一下性能問題,用於同步時間的接口因爲不涉及到後端邏輯,只須要將當前web服務器的時間發送給客戶端就能夠了,所以速度很快,就我之前測試的結果來看,一臺標準的web服務器2W+QPS不會有問題,若是100W人同時刷,100W QPS也只須要50臺web,一臺硬件LB就能夠了~,而且web服務器羣是能夠很容易的橫向擴展的(LB+DNS輪詢),這個接口能夠只返回一小段json格式的數據,並且能夠優化一下減小沒必要要cookie和其餘http頭的信息,因此數據量不會很大,通常來講網絡不會成爲瓶頸,即便成爲瓶頸也能夠考慮多機房專線連通,加智能DNS的解決方案;web服務器之間時間不一樣步能夠採用統一時間服務器的方式,好比每隔1分鐘全部參與秒殺活動的web服務器就與時間服務器作一次時間同步。
前端層的請求攔截,只能攔住小白用戶(不過這是99%的用戶喲),高端的程序員根本不吃這一套,寫個for循環,直接調用你後端的http請求,怎麼整? (1)同一個uid,限制訪問頻度,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面 (2)同一個item的查詢,例如手機車次,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面 如此限流,又有99%的流量會被攔截在站點層。
站點層的請求攔截,只能攔住普通程序員,高級黑客,假設他控制了10w臺肉雞(而且假設買票不須要實名認證),這下uid的限制不行了吧?怎麼整? (1)大哥,我是服務層,我清楚的知道小米只有1萬部手機,我清楚的知道一列火車只有2000張車票,我透10w個請求去數據庫有什麼意義呢?對於寫請求,作請求隊列,每次只透過有限的寫請求去數據層,若是均成功再放下一批,若是庫存不夠則隊列裏的寫請求所有返回「已售完」;
(2)對於讀請求,還用說麼?cache來抗,無論是memcached仍是redis,單機抗個每秒10w應該都是沒什麼問題的;
如此限流,只有很是少的寫請求,和很是少的讀緩存mis的請求會透到數據層去,又有99.9%的請求被攔住了。
1)用戶請求分發模塊:使用Nginx或Apache將用戶的請求分發到不一樣的機器上。
2)用戶請求預處理模塊:判斷商品是否是還有剩餘來決定是否是要處理該請求。
3)用戶請求處理模塊:把經過預處理的請求封裝成事務提交給數據庫,並返回是否成功。
4)數據庫接口模塊:該模塊是數據庫的惟一接口,負責與數據庫交互,提供RPC接口供查詢是否秒殺結束、剩餘數量等信息。
通過HTTP服務器的分發後,單個服務器的負載相對低了一些,但總量依然可能很大,若是後臺商品已經被秒殺完畢,那麼直接給後來的請求返回秒殺失敗便可,沒必要再進一步發送事務了,示例代碼能夠以下所示:
Java的併發包提供了三個經常使用的併發隊列實現,分別是:ConcurrentLinkedQueue 、 LinkedBlockingQueue 和 ArrayBlockingQueue。
ArrayBlockingQueue是初始容量固定的阻塞隊列,咱們能夠用來做爲數據庫模塊成功競拍的隊列,好比有10個商品,那麼咱們就設定一個10大小的數組隊列。 ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。 LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。 因爲咱們的系統入隊需求要遠大於出隊需求,通常不會出現隊空的狀況,因此咱們能夠選擇ConcurrentLinkedQueue來做爲咱們的請求隊列實現:
發送秒殺事務到數據庫隊列.
數據庫主要是使用一個ArrayBlockingQueue來暫存有可能成功的用戶請求。
分片解決的是「數據量太大」的問題,也就是一般說的「水平切分」。一旦引入分片,勢必有「數據路由」的概念,哪一個數據訪問哪一個庫。路由規則一般有3種方法:
分組解決「可用性」問題,分組一般經過主從複製的方式實現。 互聯網公司數據庫實際軟件架構是:又分片,又分組(以下圖)
數據庫軟件架構師平時設計些什麼東西呢?至少要考慮如下四點:
##7.1 請求接口的合理設計
一個秒殺或者搶購頁面,一般分爲2個部分,一個是靜態的HTML等內容,另外一個就是參與秒殺的Web後臺請求接口。
一般靜態HTML等內容,是經過CDN的部署,通常壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須可以支持高併發請求,同時,很是重要的一點,必須儘量「快」,在最短的時間裏返回用戶的請求結果。爲了實現儘量快這一點,接口的後端存儲使用內存級別的操做會更好一點。仍然直接面向MySQL之類的存儲是不合適的,若是有這種複雜業務的需求,都建議採用異步寫入。
固然,也有一些秒殺和搶購採用「滯後反饋」,就是說秒殺當下不知道結果,一段時間後才能夠從頁面中看到用戶是否秒殺成功。可是,這種屬於「偷懶」行爲,同時給用戶的體驗也很差,容易被用戶認爲是「暗箱操做」。
咱們一般衡量一個Web系統的吞吐率的指標是QPS(Query Per Second,每秒處理請求數),解決每秒數萬次的高併發場景,這個指標很是關鍵。舉個例子,咱們假設處理一個業務請求平均響應時間爲100ms,同時,系統內有20臺Apache的Web服務器,配置MaxClients爲500個(表示Apache的最大鏈接數目)。
#參考: