關於高並發和秒殺系統,你知道的和不知道的一些事
這篇文章也算是對於課程 《PHP秒殺系統 高並發高性能的極致挑戰》 的一個整理,視頻之外的另外一種形式吧。
大家也許開發過高並發的系統或者秒殺程序,但肯定都有接觸過,像電商平台的秒殺、搶購等活動,還有12306春運搶票。
互聯網公司,做一些有獎活動,而且數量有限,獎品給力,如果是先到先得的策略,那就類似秒殺系統了。
這類系統最大的問題就是活動周期短,瞬間流量大(高並發),很少人可以成功下單,絕大多數人都是很遺憾。所以從運營體驗上,沒有成功下單的人,心裡肯定是不好受的,如果這時候,因為技術、網路問題,影響用戶體驗,那就更是罵聲一片。
這裡,就來講一講技術在這種情況下,會發生和要做的事。
下面是基本的概念的建立。
- 第一:高並發
技術要做的事,一方面優化程序,讓程序性能最優,單次請求時間能從50ms優化到25ms,那就可以在一秒鐘內成功響應翻倍的請求了。
另一方面就是增加伺服器,用更大的集群來處理用戶請求,設計好一個可靠且靈活擴充的分散式方案就更加重要了。
- 第二:時間短
火熱的秒殺活動,真的是一秒鐘以內就會把商品搶購一空,而大部分用戶的感受是,提交訂單的過程卻要等待好幾秒、甚至十幾秒,更糟糕的當然是請求報錯。
那麼一個好的秒殺體驗,當然希望儘可能減少用戶等待時間,準確的提示用戶當前是否還有商品庫存。而這些,也是需要有優秀的程序設計來保證的。
- 第三:系統容量預估
系統設計的時候,都需要有一個容量預估,那就是要提前計算好,我們設計的系統,要承載多大的數量級。
假如線上前端伺服器規格是8核16G內存的伺服器,而提交訂單的處理程序耗時100ms,那麼可以簡單計算一下:
每秒可以處理的訂單請求數=1000ms/100ms*8=80qps
上面這個結果,對於秒殺系統來說,肯定是非常不理想的。
如果能將處理程序耗時優化後,降低到10ms,那麼就可以達到800qps。
如果我們可以把程序繼續優化,能快速區分開有庫存和無庫存處理,那麼無庫存時處理就有可能做到1ms甚至更低的耗時。這樣無庫存時就能有更好的性能,上萬的qps也是可以達到的。
上面的預估,都是針對單機,那麼簡單的增加前端伺服器,是不是就能有更好的並發處理量呢?
肯定沒這麼簡單,因為資料庫、緩存系統甚至機房網路帶寬都會成為瓶頸。
於是就要有一個更好的分散式方案。
- 第四:好的分散式方案
一個好的分散式方案,首先當然是穩定可靠,不要出亂子,然後就是方便擴充,最好的效果當然是增加一台伺服器,並發處理量可以1:1線性增長。
比如:單機qps是1k,那麼10台伺服器可以做到1w,100台可以做到10w每秒。
要做到這樣的線性增長效果,就要杜絕出現瓶頸,否則還是會代價太大。
拒絕假的分散式尤其重要,比如:前端伺服器是可以獨立存在的,但是都依賴集中的一個資料庫或者緩存系統,那最後,一定是集中的那個資料庫或者緩存系統受不了,同樣無法做到一個好的分散式。
- 第五:關注系統的瓶頸
大家先有幾個基本的共識,系統的處理速度
程序內數據讀寫 > redis > mysql > 磁碟
單機網路請求 > 區域網內請求 > 跨機房請求
我們優化程序的時候,盡量用最快的方式,盡量用最簡短的邏輯。
用redis替代mysql來保存訂單處理中依賴的數據,用程序中的提交的數據代替從redis中二次獲取數據,比如:商品庫存信息,用戶訂單信息。
邏輯處理中,把速度快且提前中斷的邏輯放在最前面,比如:驗證登錄,驗證問答。
我們做分散式方案的時候,盡量把資源調用放在最近的地方。
前端伺服器依賴的數據盡量就在區域網內,如果能在單機都有讀的redis服務當然更好,程序維護數據響應會複雜些。
不要出現跨機房網路請求,不要出現跨機房網路請求,不要出現跨機房網路請求,重要的事情說三遍。
- 第六:什麼語言更適合這類系統
課程中用的是PHP語言,開發這類系統也是沒問題的。
當然,像是用golang, ngx_lua可能在高並發和性能方面會更有優勢。
如果使用java、.net當然也是可以的,作為一個系統,語言只是工具,更好的設計和優化,才能達到最終想要的效果。
有了上面的基本概念,我們接下來再來看看,具體運行時,會出現什麼狀況。
下面是一些具體的問題:
- 問題1:庫存超賣
只有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) 得到遞減之後的庫存數。
性能方面很好,同時體驗上也很好,在PHP秒殺課程中,優化後就是用的這種方法,而沒有使用上述其他方法,大家應該也能對比了解啦。
- 問題2:集群怎麼來規劃
前端伺服器因為沒有相互間關聯,集群的數量不受影響。
redis的性能可以達到每秒幾萬次響應,所以一個集群的規模,也就是redis服務可以承載的數量。
比如:一台前端伺服器是1~2k的qps(有庫存時),那麼10台+1台redis就可以是一個獨立的集群,可以支撐1~2w每秒訂單量。
10個上述的集群就可以做到一秒鐘處理10w~20w的有效訂單。
如果秒殺活動的庫存量在1w以內,預計參與的人數在百萬左右,那麼有一個集群也就可以搞定。
如果秒殺參與的人數超過千萬,那麼就要用到不止一個集群了。
- 問題3:多個集群的數據怎麼保持一致性
不要做多集群的數據同步,而是用散列,每個集群的數據是獨立存在的。
假設,有10個商品,每個商品有1w庫存,規劃用10個集群,那麼每個集群有10個商品,每個商品是1k庫存。
每個集群只需要負責把自己的庫存賣掉即可,至於說,會不會有用戶知道有10個集群,然後每個集群都去搶。
這種情況就不要用程序來處理了,利用運營規則,活動結束後匯總訂單的時候再去處理就好了。
如果擔心散列的不合理,比如:某個集群用戶訪問量特別少,那麼可以引入一個中控服務,來監控各個集群的庫存,然後再做平衡。
- 問題4:機器人搶購怎麼辦:
沒什麼太好的辦法,類似DDOS攻擊,只能是讓自身更強大才是王道。
運營策略上,可以嚴格控制用戶註冊,必須登錄,提交訂單的時候引入圖像驗證碼,問答,互動式驗證等。
上面的內容不知道大家還會有什麼疑問,可以給我留言。
具體的關於高並發、秒殺的實戰內容,在課程中也有詳細的講解,這裡用文字的形式提煉整理出來,希望能更好的幫助大家理解,謝謝~
作者: 一凡Sir
鏈接:https://www.imooc.com/article/25596
來源:慕課網
本文原創發佈於慕課網 ,轉載請註明出處,謝謝合作
推薦閱讀:
打造個人品牌 so easy !_慕課手記
有獎徵文|談一談你是如何走上擼碼這條不歸路的?
慕課網陳志鋒:每一個用戶都是一個潛在開發者
為什麼真的真的要寫單元測試
面試中並發類問題的準備和學習
推薦閱讀:
※【綜述】從一致性哈希演算法到緩存集群實現
※論文筆記:[SRDS 2004] The Phi Accrual Failure Detector
※實時機器學習
※閱讀筆記:Scaling Memcache at Facebook
※論文筆記:Paxos Made Simple