秒殺系統解決方案

我看了二十篇左右的秒殺系統設計及解決方案的文章,從架構、產品、前端、後端四個層面分別總結了一些解決方案。html

要點總結:

1.架構:擴容,業務分離,數據分離前端

2.產品:下單按鈕控制,秒殺答題削峯,簡化頁面設計redis

3.前端:限流(反做弊) 靜態化sql

4.後端:內存 隊列數據庫

1、秒殺通常會帶來2個問題:

一、高併發

比較火熱的秒殺在線人數都是10w起的,如此之高的在線人數對於網站架構從前到後都是一種考驗。後端

二、超賣(也有叫超發)

任何商品都會有數量上限,如何避免成功下訂單買到商品的人數不超過商品數量的上限,這是每一個搶購活動都要面臨的難題。瀏覽器

實際上超賣問題是高併發帶來的一個子問題,可是由於這個問題太過致命,因此咱們把他的解決方案單獨拿出來講。緩存

解決辦法:安全

一、將庫存字段number字段設爲unsigned,當庫存爲0時,由於字段不能爲負數,將會返回false。服務器

二、採用數據庫的悲觀鎖思路,select ...for update。見《MySQL鎖之三:MySQL的共享鎖與排它鎖編碼演示

三、採用數據庫的樂觀鎖思路,常見的就是大可能是基於數據版本( Version )記錄機制實現。(即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。)

四、採用Redis中的watch。見《Redis事務和watch

五、採用FIFO隊列思路。(直接將請求放入隊列中的,採用FIFO(First Input First Output,先進先出),這樣的話,咱們就不會致使某些請求永遠獲取不到鎖。看到這裏,是否是有點強行將多線程變成單線程。由於請求不少,極可能一瞬間將隊列內存「撐爆」

六、採用文件鎖的思路。單臺物理機上是能夠,分佈式集羣下不可行。見《NIO文件鎖FileLock

 

2、如何解決?

1.架構層面:

秒殺架構設計原則:
  1. 儘可能將請求攔截在系統上游

  2. 讀多寫少的經常使用多使用緩存

擴容

說白了加機器

系統隔離

爲了不短期內的大訪問量對現有網站業務形成的衝擊,能夠將秒殺系統獨立部署。系統隔離更可能是運行時的隔離,能夠經過分組部署的方式和另外99%分開。秒殺還申請了單獨的域名,目的也是讓請求落到不一樣的集羣中。即便秒殺系統崩潰了,也不會對網站形成影響。

數據隔離

將即將被秒殺的熱數據維護到redis。秒殺所調用的數據大部分都是熱數據,好比會啓用單獨cache集羣或MySQL數據庫來放熱點數據,目前也是不想0.01%的數據影響另外99.99%。

減庫存操做
一種是拍下減庫存 另一種是付款減庫存;目前採用的「拍下減庫存」的方式,拍下就是一瞬間的事,對用戶體驗會好些。

2.產品層面:

1.控制秒殺商品頁面搶購按鈕的可用/禁用。

購買按鈕只有在秒殺開始的時候才能點亮,在此以前是灰色的,顯示活動未開始。

2.增長了秒殺答題,基於時間分片削峯

秒殺答題一個很重要的目的是爲了防止秒殺器。還有一個重要的功能,就是把峯值的下單請求給拉長了,從之前的1s以內延長到2~10s左右,請求峯值基於時間分片了,這個時間的分片對服務端處理併發很是重要,會減輕很大壓力,另外因爲請求的前後,靠後的請求天然也沒有庫存了,也根本到不了最後的下單步驟,因此真正的併發寫就很是有限了。其實這種設計思路目前也很是廣泛,如支付寶的「咻一咻」已及微信的搖一搖。

3.秒殺頁面設計簡化:

秒殺場景業務需求與通常購物不一樣,用戶更在乎的是可以搶到商品而不是用戶體驗。因此秒殺商品頁面應儘量簡單而且拍下後地址等我的信息應該使用默認信息,減輕秒殺進行時系統負載,如有更改能夠在秒殺結束後進行更改。

3.前端層面

靜態化以及頁面緩存

將頁面可以靜態的部分都靜態化,並將靜態頁面緩存於CDN,以及反向代理服務器,可能還要臨時租借服務器。 利用 頁面靜態化、數據靜態化,反向代理 等方法能夠避免 帶寬和sql壓力 ,可是隨之而來一個問題,頁面搶單按鈕也不會刷新了,能夠把 js 文件單獨放在js服務器上,由另一臺服務器寫 定時任務 來控制js 推送。 另外還有一個問題,js文件會被大部分瀏覽器緩存,咱們可使用xxx.js?v=隨機數 的方式來避免js被緩存。

限流(反做弊)

1.針對同一個用戶id來實現,前端js控制一個客戶端幾秒以內只能發送同一個請求,後端校驗同一個uid在幾秒以內返回同一個頁面

2.針對同一個ip來實現,進行ip檢測,同一個ip幾秒以內不發送請求或者只返回同一個頁面

3.針對多用戶多ip來實現,依靠數據分析

4.爲了不用戶直接訪問下單頁面URL,須要將改URL動態化,即便秒殺系統的開發者也沒法在秒殺開始前訪問下單頁面的URL。辦法是在下單頁面URL加入由服務器端生成的隨機數做爲參數,在秒殺開始的時候才能獲得。

重啓與過載保護

若是系統發生「雪崩」,貿然重啓服務,是沒法解決問題的。最多見的現象是,啓動起來後,馬上掛掉。這個時候,最好在入口層將流量拒絕,而後再將重啓。若是是redis/memcache這種服務也掛了,重啓的時候須要注意「預熱」,而且極可能須要比較長的時間。

秒殺和搶購的場景,流量每每是超乎咱們系統的準備和想象的。這個時候,過載保護是必要的。若是檢測到系統滿負載狀態,拒絕請求也是一種保護措施。在前端設置過濾是最簡單的方式,可是,這種作法是被用戶「千夫所指」的行爲。更合適一點的是,將過載保護設置在CGI入口層,快速將客戶的直接請求返回。

4.後端層面:

1.加入緩存redis:

由於秒殺是典型的讀多寫少的場景,適合操做內存而非操做硬盤;緩存工具redis自己的操做是保證原子性的,因此能夠保證請求了redis的寫的操做的線程安全性。

2.加入消息隊列,利用隊列進行削峯:

將用戶請求放置於一個或多個隊列中,隊列中元素總和等於該商品庫存總和,未進入隊列的請求均失敗。利用多線程輪詢分別從一個或多個隊列中取出用戶請求。操做redis進行減庫存操做,成功減庫存以後返回成功,並將用戶信息與商品信息存入另外一個隊列當中,進行生成訂單的操做。利用兩個隊列異步處理業務減輕秒殺高峯時期服務器負載。

3.程序計數器:

隊列與緩存爲了保證請求redis的次數不超過總的庫存量,利用一個程序計數器來這一點。程序計數器用JUC包下原子類能夠實現。

4.分佈式鎖

分佈式狀況下能夠利用分佈式鎖來解決任務每次只能由一次服務來執行且不能重複執行。 分佈式鎖的實現:zk、redis 分佈式鎖的優化:先考慮是否能夠去鎖,而後考慮儘量多用樂觀鎖,少用悲觀鎖。這裏有一個問題,樂觀鎖若是每一次都會有併發衝突的話性能反而不如悲觀鎖,那麼難道真的多用樂觀鎖性能會比悲觀鎖高嗎?選舉考慮ha,好比心跳檢測。

5.分佈式去鎖 方案

利用集羣併發加入隊列,選舉隊列處理服務單點執行,這樣能夠保證併發實現和加鎖同樣的併發量但不會影響性能。

相關文章
相關標籤/搜索