互聯網特別是電商平臺,阿里雙11秒殺、還有12306春運搶票、以及平時各類節假日搶購活動等,都是典型的高併發場景。這類場景最大的特徵就是活動週期短,瞬間流量大(高併發),大量的人短時間涌入服務器搶購,可是數量有限,最終只有少數人能成功下單。php
這裏,就來說一講對應該場景下須要考慮的技術實現。先從基本的概念的創建,再講對應的實現部分。前端
第一:高併發java
技術要作的事,一方面優化程序,讓程序性能最優,單次請求時間能從50ms優化到25ms,那就能夠在一秒鐘內成功響應翻倍的請求了。另外一方面就是增長服務器,用更大的集羣來處理用戶請求,設計好一個可靠且靈活擴充的分佈式方案就更加劇要了。mysql
第二:時間短golang
火熱的秒殺活動,真的是一秒鐘之內就會把商品搶購一空,而大部分用戶的感覺是,提交訂單的過程卻要等待好幾秒、甚至十幾秒,更糟糕的固然是請求報錯。那麼一個好的秒殺體驗,固然但願儘量減小用戶等待時間,準確的提示用戶當前是否還有商品庫存。而這些,也是須要有優秀的程序設計來保證的。面試
第三:系統容量預估redis
系統設計的時候,都須要有一個容量預估,那就是要提早計算好,咱們設計的系統,要承載多大的數量級。sql
假如:數據庫
線上前端服務器規格是8核16G內存的服務器,而提交訂單的處理程序耗時100ms,那麼能夠簡單計算一下。緩存
每秒能夠處理的訂單請求數=1000ms/100ms*8=80qps
上面這個結果,對於秒殺系統來講,確定是很是不理想的。
上面的預估,都是針對單機,那麼簡單的增長前端服務器,是否是就能有更好的併發處理量呢?確定沒這麼簡單,由於數據庫、緩存系統甚至機房網絡帶寬都會成爲瓶頸。因而就要有一個更好的分佈式方案。
第四:好的分佈式方案
一個好的分佈式方案,首先固然是穩定可靠,不要出亂子,而後就是方便擴充,最好的效果固然是增長一臺服務器,併發處理量能夠1:1線性增加。
好比:單機qps是1k,那麼10臺服務器能夠作到1w,100臺能夠作到10w每秒。
要作到這樣的線性增加效果,就要杜絕出現瓶頸,不然仍是會代價太大。
拒絕假的分佈式尤爲重要,好比:前端服務器是能夠獨立存在的,可是都依賴集中的一個數據庫或者緩存系統,那最後,必定是集中的那個數據庫或者緩存系統受不了,一樣沒法作到一個好的分佈式。
第五:關注系統的瓶頸
你們先有幾個基本的共識,系統的處理速度
程序內數據讀寫 > redis > mysql > 磁盤
單機網絡請求 > 局域網內請求 > 跨機房請求
咱們優化程序的時候,儘可能用最快的方式,儘可能用最簡短的邏輯。
用redis替代mysql來保存訂單處理中依賴的數據,用程序中的提交的數據代替從redis中二次獲取數據,好比:商品庫存信息,用戶訂單信息。
邏輯處理中,把速度快且提早中斷的邏輯放在最前面,好比:驗證登陸,驗證問答。
咱們作分佈式方案的時候,儘可能把資源調用放在最近的地方。
前端服務器依賴的數據儘可能就在局域網內,若是能在單機都有讀的redis服務固然更好,程序維護數據響應會複雜些。
不要出現跨機房網絡請求,不要出現跨機房網絡請求,不要出現跨機房網絡請求,重要的事情說三遍。
第六:什麼語言更適合這類系統
固然,像是用golang, ngx_lua可能在高併發和性能方面會更有優點。
若是使用java、php固然也是能夠的,做爲一個系統,語言只是工具,更好的設計和優化,才能達到最終想要的效果。
有了上面的基本概念,咱們接下來再來看看,具體運行時,會出現什麼情況。
下面是一些具體的實現問題:
問題:庫存超賣
只有10個庫存,可是一秒鐘有1k個訂單,怎麼能不超賣呢?
核心思想就是保證庫存遞減是原子性操做,10--返回9,9--返回8,8--返回7。
而不能是讀取出來庫存10,10-1=9再更新回去。由於這個讀取和更新是併發執行的,極可能就會有1k個訂單都成功了,而庫存實際只有10。
那麼,怎麼保證原子性操做呢?
1 數據庫
update product set left_num=left_num-1 where left_num>0;
這裏用到的是left_num=left_num-1,若是left_num>0才能執行成功,數據庫查詢、更新的時候有用到鎖,是能夠保證更新操做的原子性的。
數據庫性能較差,不建議使用。
2 分佈式鎖
用redis來作一個分佈式鎖,reids->setnx('lock', 1) 設置一個鎖,程序執行完成再del這個鎖。
鎖定的過程,不利於併發執行,你們都在等待鎖解開,不建議使用。
3 消息隊列
將訂單請求所有放入消息隊列,而後另一個後臺程序一個個處理隊列中的訂單請求。
併發不受影響,可是用戶等待的時間較長,進入隊列的訂單也會不少,體驗上並很差,也不建議使用。
4 redis遞減
經過 redis->incrby('product', -1) 獲得遞減以後的庫存數。
問題:集羣怎麼來規劃
前端服務器由於沒有相互間關聯,集羣的數量不受影響。redis的性能能夠達到每秒幾萬次響應,因此一個集羣的規模,也就是redis服務能夠承載的數量。
好比:一臺前端服務器是1~2k的qps(有庫存時),那麼10臺+1臺redis就能夠是一個獨立的集羣,能夠支撐1~2w每秒訂單量。
10個上述的集羣就能夠作到一秒鐘處理10w~20w的有效訂單。
若是秒殺活動的庫存量在1w之內,預計參與的人數在百萬左右,那麼有一個集羣也就能夠搞定。
若是秒殺參與的人數超過千萬,那麼就要用到不止一個集羣了。
問題:多個集羣的數據怎麼保持一致性
不要作多集羣的數據同步,而是用散列,每一個集羣的數據是獨立存在的。
假設,有10個商品,每一個商品有1w庫存,規劃用10個集羣,那麼每一個集羣有10個商品,每一個商品是1k庫存。
每一個集羣只須要負責把本身的庫存賣掉便可,至於說,會不會有用戶知道有10個集羣,而後每一個集羣都去搶。
這種狀況就不要用程序來處理了,利用運營規則,活動結束後彙總訂單的時候再去處理就行了。
若是擔憂散列的不合理,好比:某個集羣用戶訪問量特別少,那麼能夠引入一箇中控服務,來監控各個集羣的庫存,而後再作平衡。
問題:機器人搶購怎麼辦
沒什麼太好的辦法,相似DDOS攻擊,只能是讓自身更強大才是王道。
運營策略上,能夠嚴格控制用戶註冊,必須登陸,提交訂單的時候引入圖像驗證碼,問答,交互式驗證等。
---end---
往期熱文: