PHP秒殺系統全方位設計分析--轉載

轉載自::http://www.cnblogs.com/wt645631686/p/8242809.html

秒殺系統特點
人多商品少
時間短流量高
外掛機器[黃牛和非黃牛]

技術分析
瞬間高併發的處理能力
多層次的分佈式處理能力
人機交互與對抗[12306驗證碼圖片]

技術選型分析
Linux+Nginx+PHP+Mysql+Redis
CDN,智能DNS,分佈式緩存,全國多節點,多線路接入
LVS負載均衡

基本功能和流程
後臺:活動管理/商品管理/訂單管理/日誌管理,數據列表和內容的編輯增刪(邏輯刪除)改查
前臺:商品展示/搶購/我的訂單/購物車/登錄等功能
安全:驗證碼/回答/分析日誌,防攻擊、防作弊、防機器人

用戶大概訪問交互流程
用戶進來的時候先看到秒殺商品的展示頁面,然後從頁面選擇商品參與秒殺,參與秒殺時需要提交驗證碼,驗證用戶登錄狀態等之類的驗證,把問答或者一些驗證信息填完之後就可以提交訂單完成秒殺功能,然後等待結果,有可能成功或者失敗,提示一些信息,用戶能夠感知到的秒殺流程。

用戶選擇想要秒殺的商品,輸入了要購買的商品數量,點擊提交,這時候我們的秒殺程序就要開始響應了,於是秒殺開始。
先驗證用戶提交信息,比如還有用戶登錄狀態,驗證問答信息或者更多的信息。先驗證信息是否對,如果有錯誤,那麼提示錯誤信息,如果對,那麼進入庫存驗證,如果庫存不足,或者活動結束了,提示庫存不足,那麼秒殺結束。如果訂單提交成功,那麼生成訂單,生成訂單時會有訂單相關的數據處理,比如庫存的更新等。畢竟是併發提交,有可能生成訂單也會出現問題,如果生成訂單環節出現了問題,即使前面的環節通過,在此環節也會出現問題,比如訂單生成的時候,前面的一個人先生成了訂單,庫存不足了,還會出錯,所以在這個環節一定還會有其他的異常信息出現,那麼還需要給用戶提交錯誤信息,如果沒出錯,那麼秒殺成功。

以上大概流程是不可或缺的,也是有點粗略。如果我們流程僅僅這幾個點的話,那麼我們的流程中其實還差的很多。我們在設計過程中,先列出來,還需要根據這幾個流程進行補充。
程序運行起來會有幾個輸入的驗證:如問答的驗證,用戶登錄狀態驗證,用戶是否進入黑名單,以及參數的驗證,商品信息的參數,活動信息的參數,其他校驗信息的參數驗證等。
還有輸出的驗證:異常情況的輸出和成功正常情況的輸出。

其他情況:比如購買的庫存是一種商品的話,在處理的時候要容易一點,會做一下比較。如果是多件商品的話,每個商品就需要每個進行驗證。如果商品還有類型的區分,比如手機有好多種型號,那麼還需要根據型號處理。有的時候還會涉及到優惠券等等。。


商品頁面開發
靜態化展示頁面[效率要比動態PHP高很多,PHP程序需要解析等步驟,本身就需要很多流程,整個下來PHP的處理花的時間和資源要多]
商品狀態的控制
開始前、進行中、庫存不足、結束
數據邏輯處理
大致流程:驗證用戶是否登錄、驗證參數是否合法、驗證活動信息狀態、驗證商品信息狀態是否正常、驗證問題回答是否正確、驗證用戶是否已經購買、驗證用戶購買的商品數量是否在限制的範圍內、驗證商品庫存是否充足、扣除商品購買數量、創建訂單、返回提示信息
保證數據一致性、高效處理

秒殺商品類型

單商品秒殺特點
簡單,沒有選擇、獨立
沒有關聯關係、容易
驗證邏輯更少

組合商品秒殺特點
支持多商品選擇
多商品庫存、限購數量
驗證和處理的邏輯多

秒殺級別

萬次秒殺
就是幾萬次請求,活動就結束,請求比較少,併發低,實現基本功能就可以滿足
不太需要考慮性能優化方面,這樣服務器資源時間等成本節省
單機,比如PHP+Mysql數據庫放在一起,並且動態訪問,單臺就可以支持

百萬次秒殺
請求量和併發量都開始有明顯提升,需要做部分優化,系統架構、代碼邏輯、服務器等
WEB服務器集羣,多臺服務器
引入Redis緩存,不能僅僅Mysql來應對更多的查詢
不能有嚴重的接口漏洞問題等低級錯誤
壓力測試,把所有接口高併發測試

過億次秒殺
所有問題都要極端化考慮,不管是在邏輯運算,及運算次數,多一次內存的分配,等等,哪些能優化,哪些能避免
沒遇到過的問題很可能發生,比如想象中極不可能發生的問題,服務器宕機,內存不夠等,我們也需要思考避免
需要能臨時調配大量的服務器資源,平時準備大量的不現實也不合理,因爲併發可能很短就結束。比如購買雲主機,在時間階段內利用,快速克隆部署運行,不需要的時候再釋放掉,在成本方面可以減少服務器和資源型的依賴
引入中控服務器(控制服務器的服務器)
多機房多數據中心 

 大概秒殺流程

在用戶在點擊購買的時候,需要做一些安全性的參數驗證,大致包括活動標識、商品標識、要購買的商品數量、安全加密串、再比如問答驗證。在完成這些信息的提交後,程序流程的大概驗證順序爲:
驗證用戶是否登錄->驗證加密簽名是否合理->驗證規定時間段內IP和用戶保持不變(如五分鐘,加密簽名裏包含用戶登錄的時間還有IP,在用戶購買的時候提交和當前時間以及IP對比)->驗證問題回答是否正確->統一格式化一下購買的商品信息(比如轉化爲容易處理的數組格式)->配合redis緩存,驗證一下訂單是否已經購買過,防止重複購買->驗證活動、商品的狀態是否正常(比如是否賣完,是否還在賣等)->先更新緩存中剩餘的商品數量,再更新數據庫扣除商品的數量->扣除成功後創建訂單信息->創建完成訂單後設置緩存用於已經購買

優化單機性能

 提高頁面訪問速度

減少頁面大小,啓用gzip壓縮
減少資源請求數量,合併和壓縮css、js
設置瀏覽器緩存,利用CDN加速

 提高秒殺接口速度

因爲性能方面幾個瓶頸,無非就是通過網絡請求數據,網絡速度是比較大的問題;另外通過磁盤讀寫文件性能也比較低,那麼我們程序的時候,我們儘可能少用這種性能比較低的存儲設備,比如內存,內存的速度要比磁盤快很多。當然還有更多方法....
接口靜態化
如果接口是PHP編寫的動態程序,效率肯定比靜態化的低很多,比如我們的首頁、商品展示頁面 ,是動態頁面的話,動態的效率再高,裏面包括很多效率高的處理方法,比如緩存,就算再快,也會有開銷,也不如直接靜態化效率高。如果我們把頁面靜態化,效率會提高很多,Nginx對這種靜態文件的訪問極其高的,一秒鐘一兩萬次不會存在很大的瓶頸,對於服務器壓力來說,一秒鐘一兩萬次服務器負載也是沒多大問題;但是我們用PHP寫的動態結果,一秒鐘一兩萬次肯定是不可以的,性能再好一秒鐘五六百次一千次效率也是很高的了。所以可以把接口做靜態化處理很快。比如,把首頁靜態化,生成靜態的index.html文件就可以了,剩下只需要處理的一個地方就是登陸,基於cookie的方式保存登陸狀態,cookie的解析是通過服務端來解析,而我們在這個環節需要改一下,頁面展示的登錄狀態判斷不能通過服務器端了,因爲已經是一個靜態頁面了,這裏我們需要通過js來讀取cookie信息,把這個狀態信息通過js輸出出來就可以了。當然還有其他的接口,比如最重要的秒殺接口,我們肯定會想,這個接口肯定是一個動態的,如果是一個靜態的大家看到的結果是一樣的,那麼還怎麼進行秒殺?沒錯,秒殺接口肯定是一個動態的,按功能來講,如果是靜態的就是假的了,欺騙用戶。比如發佈了一款產品,整體下來大約是1000000次併發請求訪問,我們的實際要賣出的商品數量爲1000個,那麼我們有必要全部的訪問都是動態的麼,有沒有想過前面1000個是動態訪問,後面的多餘訪問次數是靜態的1000000-1000次,那麼這樣多放幾臺服務器,或者部署CDN,帶寬充足就好了。 
快速終止的邏輯放在前面,先判斷條件,再執行後面的程序(比如面對一千萬個訪問,商品庫存有5000件,那麼商品賣完[10000000-5000個訪問不走程序邏輯]或者活動沒有進行等狀態直接訪問靜態文件,和上一個差不多)
增加冗餘的定製化數據,保證程序更快捷。以空間換時間,比如增加新的數據結構,增加數組,在存儲空間啊,內存啊,程序編寫的複雜度要求高一點,讓用戶更快速的訪問我們的接口。

提高數據處理速度

數據量大的時候,數據庫索引不能少,更不能亂。(數據量大的時候,效率差別會很明顯。索引儘量的少,更不能亂,需要的時候就創建它,如果需要的時候沒有索引,那會非常不好。如在訂單表裏,用戶在秒殺的時候,需要判斷用戶之前有沒有參加過活動,那麼我們在表裏會查詢響應用戶和活動id兩個字段進行創建符合索引,效率會更高,同時還可以用前面字段索引查詢,利用率高了;還比如商品相關的訂單,給商品創建訂單;還比如時間範圍內的,給時間加索引;創建索引後,數據每次的更改和插入也會對索引操作,這樣對數據寫操作增加開銷。要遵循索引的創建原則。再比如我們對訂單狀態做了索引,因爲字段節點上有幾個狀態節點,節點特別少,如果表裏了有1000萬的數據量,發現每個節點裏有100萬個數據,雖然對符合狀態的查詢很快,但是每個節點下符合條件的都有100萬的數據量,但是後續的處理很困難。這也是不符合的,還不如多幾次查找。我們儘量把索引建到比較分散的列上,比如uid,活動id雖然現在不多,可能慢慢的規模會增多)
減少數據規模(如果要檢索的符合條件的數據很多,那麼效率也不會體現出來,比如查詢出符合條件的數據太多,而節點類型並不多的字段。比如一個數據表有1000萬條數據,另一個表裏有10萬,那很明顯我們在這1000萬條數據裏做操作及時有索引,但是他的更新查找等寫操作比只有10萬條數據的要差,這就是規模的影響。如何減少呢?比如預計訂單表會很大,訂單表相應的字段有活動字段,那麼我們可以通過不同的活動創建不同的表來存儲響應活動的訂單信息。這樣規模就減少了)
將數據放到redis緩存中(存內存、結構簡單、性能好,可以每秒做到一萬次甚至幾萬次的操作,減少Mysql數據庫壓力)
代碼邏輯方面的優化
比如在參數校驗方面,可以把較爲簡單的校驗放在前面;再比如數據結構的合理利用;合理地簡化程序流程

 分佈式方案

多個接入層服務器
如果效率要說高的話,其實單個服務器最高的,所有的請求傳輸都在一臺服務器完成,如果都在同一個服務器,整個業務的流程會更短。單機自己接收自己處理,數據庫、緩存,都在同一臺服務器可以避免網絡傳輸這種問題。但是要考慮更大的訪問量,隨着訪問量越大,單機的服務是沒辦法滿足需求的,當達到更高的訪問級別,那麼不得不考慮分佈式方案。如果規模比較小的話,像是面向企業內部的這種產品,用戶量不大,所以單機就可以完成,最多的是就是數據庫獨立,保證安全穩定,處理服務器就那麼一臺,這樣規模比較小。關於分佈式的方案無非就是不是一個服務器,多個服務器就可以理解爲分佈式。有的產品很難做到分佈式,原因呢就是有些業務邏輯很難分開,比如session分佈式存儲和文件,分流訪問導致session丟失,當然也可以配置到緩存裏解決這個問題。所以我們在開發的時候一定要注意減少服務器的差異,每一臺服務器最好是一樣的,就算隨時去掉一臺服務器或者加入一批服務器不會有太大影響,這樣就可以很容易實現分佈式。比如一個集羣,就一個接入的服務器,一般來說訪問量在百萬級或者千萬級左右,我構建一個集羣規模可大可小,也就可以提供這種服務。但是秒殺系統可能規模更大,過億的話,集羣小的話,可能就無法正常提供服務了。因爲我們就一個集羣,網絡的話可能訪問就比較慢,還有距離問題,比如跨南北城市,因爲就一個接入的終端。另外一個接入的話,自身處理的能力也是有瓶頸的。所以我們在做秒殺的時候,儘可能考慮更大的規模,更多的接入層服務器。
多個機房,每個機房部署一個集羣,每個集羣一個LVS作爲接入層服務器做請求轉發,不做業務處理,性能穩定性會更好,也就是部署多個集羣。一般一個LVS服務器每天提供上億個訪問轉發是有壓力的,如果我們的訪問量是十億,做十個集羣,LVS是可以應付的。我們可以做多個機房,分爲華北、華南等區域劃分,這麼做的目的還是希望離用戶更近一點,當然還是希望能同城訪問,這樣訪問更好。
智能DNS爲不同網絡不同地域的用戶解析到不同的LVS,就好比用戶來自北京,當然就希望用戶訪問北京的接入層服務;比如我用的電信寬帶,當然還是希望訪問的是電信。整體而言智能DNS就可以保障效率。
部分靜態文件、接口引入CDN,因爲CND是兩三百個機房,CDN本身也有緩存,這樣處理的速度會提高,減輕我們自己服務器的壓力,硬件就不要考慮了,只需要考慮帶寬等因素

多WEB服務器單數據中心
LVS後端掛載多個WEB服務器,規模在2~10不等,不建議規模做特別大,不如多做幾個集羣,因爲管理不太方便。
單數據中心,開發更簡單,數據一致性有保證。如果就一個數據庫,那麼開發在請求配置的時候,就當做本地請求就可以了,不用考慮一次操作寫多少次,需不需要同步。部署多個服務的話,代價就大,維護起來就有成本,單個的話還是比較方便的,百萬級的話還是可以的。不過如果太大,還是要花費大量人力物力。
跨機房時網絡問題比較突出,要有光線專線寬帶保障。比如同機房的網絡延遲是幾毫米,跨機房的話可能幾十毫秒,就跟跨城市差不多。一定要保障跨機房的時候,保障速度。當然數據調用也要考慮網絡時間開銷。

 多WEB服務器多數據中心

在多數據中心就可以避免單數據中心的一些問題,一個是單數據中心的瓶頸,另一個是網絡問題。我們在每一組服務器都有自己獨立的數據中心,那麼所有的數據處理在本地局域網就完成這些問題,就不會出現這種跨機房延遲問題。因爲我們有多個數據中心,它的處理能力也會提升,所以在處理瓶頸這塊就不是問題了。
每個數據中心都是一個獨立的服務器集羣,因爲每個數據中心都是和一個集羣在一起的,這一組服務器在處理數據的讀寫等操作,都在局域網完成,所以效率高了。不足的地方是,數據一致性的難度就會有所提高,那麼我們可以引入中控服務器來調配多數據中心的數據,保證用戶與庫存的均衡(管理數據中心,調配數據中心的數據,他不會處理業務,只處理多個數據中心的數據變化),那麼數據同步嗎、數據一致性也就不會有問題出現了。
服務器規模預估
數據量:訂單量、用戶量分析
訪問量:全程的請求數量,每次請求的速度
資源量:CDN帶寬(請求數量和文件資源大小來算),服務器帶寬,WEB服務器,Mysql,redis數量

防止黃牛

問答、交互式驗證碼等機制、用戶訪問頁面路徑校驗、頁面停留時間和點擊位置和時間、用戶基本信息和IP還有瀏覽信息等


發佈了0 篇原創文章 · 獲贊 11 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章