12306.cn網站掛了,被全國人民罵了。我這兩天也在思考這個事,我想以這個事來粗略地和大家討論一下網站性能的問題。因為倉促,而且完全基于本人有限的經驗和了解,所以,如果有什么問題還請大家一起討論和指正。(這又是一篇長文,只討論性能問題,不討論那些UI,用戶體驗,或是是否把支付和購票下單環節分開的功能性的東西)
業務
任何技術都離不開業務需求,所以,要說明性能問題,首先還是想先說說業務問題。
其一,有人可能把這個東西和QQ或是網游相比。但我覺得這兩者是不一樣的,網游和QQ在線或是登錄時訪問的更多的是用戶自己的數據,而訂票系統訪問的是中心的票量數據,這是不一樣的。不要覺得網游或是QQ能行你就以為這是一樣的。網游和QQ 的后端負載相對于電子商務的系統還是簡單。
其二,有人說春節期間訂火車的這個事好像網站的秒殺活動。的確很相似,但是如果你的思考不在表面的話,你會發現這也有些不一樣。火車票這個事,還有很多查詢操作,查時間,查座位,查鋪位,一個車次不 行,又查另一個車次,其伴隨著大量的查詢操作,下單的時候需要對數據庫操作。而秒殺,直接殺就好了。另外,關于秒殺,完全可以做成只接受前N個用戶的請求(完全不操作后端的任何數據, 僅僅只是對用戶的下單操作log),這種業務,只要把各個服務器的時間精確同步了就可以了,無需在當時操作任何數據庫。可以訂單數夠后,停止秒殺,然后批量寫數據庫。火車票這個豈止是秒殺那么簡單。能不能買到票得當時告訴用戶啊。
其三,有人拿這個系統和奧運會的票務系統比較。我覺得還是不一樣。雖然奧運會的票務系統當年也一上線就廢了。但是奧運會用的是抽獎的方式,也就是說不存在先來先得的搶的方式,而且,是事后抽獎,事前只需要收信息,事前不需要保證數據一致性,沒有鎖,很容易水平擴展。
其四,訂票系統應該和電子商務的訂單系統很相似,都是需要對庫存進行:1)占住庫存,2)支付(可選),3)扣除庫存的操作。這個是需要有一致性的檢查的,也就是在并發時需要對數據加鎖的。B2C的電商基本上都會把這個事干成異步的,也就是說,你下的訂單并不是馬上處理的,而是延時處理的,只有成功處理了,系統才會給你一封確認郵件說是訂單成功。我相信有很多朋友都收到認單不成功的郵件。這就是說,數據一致性在并發下是一個瓶頸。
其五,鐵路的票務業務很變態,其采用的是突然放票,而有的票又遠遠不夠大家分,所以,大家才會有搶票這種有中國特色的業務的做法。于是當票放出來的時候,就會有幾百萬人甚至上千萬人殺上去,查詢,下單。幾十分鐘內,一個網站能接受幾千萬的訪問量,這個是很恐怖的事情。據說12306的高峰訪問是10億PV,集中在早8點到10點,每秒PV在高峰時上千萬。
多說幾句:
庫存是B2C的惡夢,庫存管理相當的復雜。不信,你可以問問所有傳統和電務零售業的企業,看看他們管理庫存是多么難的一件事。不然,就不會有那么多人在問凡客的庫存問題了。(你還可以看看《喬布斯傳》,你就知道為什么Tim會接任Apple的CEO了,因為他搞定了蘋果的庫存問題)
對于一個網站來說,瀏覽網頁的高負載很容易搞定,查詢的負載有一定的難度去處理,不過還是可以通過緩存查詢結果來搞定,最難的就是下單的負載。因為要訪問庫存啊,對于下單,基本上是用異步來搞定的。去年雙11節,淘寶的每小時的訂單數大約在60萬左右,京東一天也才能支持40萬(居然比12306還差),亞馬遜5年前一小時可支持70萬訂單量。可見,下訂單的操作并沒有我們相像的那么性能高。
淘寶要比B2C的網站要簡單得多,因為沒有倉庫,所以,不存在像B2C這樣有N個倉庫對同一商品庫存更新和查詢的操作。下單的時候,B2C的 網站要去找一個倉庫,又要離用戶近,又要有庫存,這需要很多計算。試想,你在北京買了一本書,北京的倉庫沒貨了,就要從周邊的倉庫調,那就要去看看沈陽或 是西安的倉庫有沒有貨,如果沒有,又得看看江蘇的倉庫,等等。淘寶的就沒有那么多事了,每個商戶有自己的庫存,庫存分到商戶頭上了,反而有利于性能。
數據一致性才是真正的性能瓶頸。有 人說nginx可以搞定每秒10萬的靜態請求,我不懷疑。但這只是靜態請求,理論值,只要帶寬、I/O夠強,服務器計算能力夠,并支持的并發連接數頂得住10萬TCP鏈接的建立 的話,那沒有問題。但在數據一致性面前,這10萬就完完全全成了一個可望不可及的理論值了。
我說那么多,我只是想從業務上告訴大家,我們需要從業務上真正了解春運鐵路訂票這樣業務的變態之處。
前端性能優化技術
要解決性能的問題,有很多種常用的方法,我在下面列舉一下,我相信12306這個網站使用下面的這些技術會讓其性能有質的飛躍。
一、前端負載均衡
通過DNS的負載均衡器(一般在路由器上根據路由的負載重定向)可以把用戶的訪問均勻地分散在多個Web服務器上。這樣可以減少Web服務器的請求負載。因為http的請求都是短作業,所以,可以通過很簡單的負載均衡器來完成這一功能。最好是有CDN網絡讓用戶連接與其最近的服務器(CDN通常伴隨著分布式存儲)。(關于負載均衡更為詳細的說明見“后端的負載均衡”)
二、減少前端鏈接數
我看了一下12306.cn,打開主頁需要建60多個HTTP連接,車票預訂頁面則有70多個HTTP請求,現在的瀏覽器都是并發請求的。所以,只要有100萬個用戶,就會有6000萬個鏈接,太多了。一個登錄查詢頁面就好了。把js打成一個文件,把css也打成一個文件,把圖標也打成一個文件,用css分塊展示。把鏈接數減到最低。
三、減少網頁大小增加帶寬
這個世界不是哪個公司都敢做圖片服務的,因為圖片太耗帶寬了。現在寬帶時代很難有人能體會到當撥號時代做個圖頁都不敢用圖片的情形(現在在手機端瀏覽也是這個情形)。我查看了一下12306首頁的需要下載的總文件大小大約在900KB左右,如果你訪問過了,瀏覽器會幫你緩存很多,只需下載10K左右的文件。但是我們可以想像一個極端一點的案例,1百萬用戶同時訪問,且都是第一次訪問,每人下載量需要1M,如果需要在120秒內返回,那么就需要,1M * 1M /120 * 8 = 66Gbps的帶寬。很驚人吧。所以,我估計在當天,12306的阻塞基本上應該是網絡帶寬,所以,你可能看到的是沒有響應。后面隨著瀏覽器的緩存幫助12306減少很多帶寬占用,于是負載一下就到了后端,后端的數據處理瓶頸一下就出來。于是你會看到很多http 500之類的錯誤。這說明服務器垮了。
四、前端頁面靜態化
靜態化一些不常變的頁面和數據,并gzip一下。還有一個并態的方法是把這些靜態頁面放在/dev/shm下,這個目錄就是內存,直接從內存中把文件讀出來返回,這樣可以減少昂貴的磁盤I/O。
五、優化查詢
很多人查詢都是在查一樣的,完全可以用反向代理合并這些并發的相同的查詢。這樣的技術主要用查詢結果緩存來實現,第一次查詢走數據庫獲得數據,并把數據放到緩存,后面的查詢統統直接訪問高速緩存。為每個查詢做Hash,使用NoSQL的技術可以完成這個優化。(這個技術也可以用做靜態頁面)
對于火車票量的查詢,個人覺得不要顯示數字,就顯示一個“有”或“無”就好了,這樣可以大大簡化系統復雜度,并提升性能。
六、緩存的問題
緩存可以用來緩存動態頁面,也可以用來緩存查詢的數據。緩存通常有那么幾個問題:
1)緩存的更新。也叫緩存和數據庫的同步。有這么幾種方法,一是緩存time out,讓緩存失效,重查,二是,由后端通知更新,一量后端發生變化,通知前端更新。前者實現起來比較簡單,但實時性不高,后者實現起來比較復雜 ,但實時性高。
2)緩存的換頁。內存可能不夠,所以,需要把一些不活躍的數據換出內存,這個和操作系統的內存換頁和交換內存很相似。FIFO、LRU、LFU都是比較經典的換頁算法。相關內容參看Wikipeida的緩存算法。
3)緩存的重建和持久化。緩存在內存,系統總要維護,所以,緩存就會丟失,如果緩存沒了,就需要重建,如果數據量很大,緩存重建的過程會很慢,這會影響生產環境,所以,緩存的持久化也是需要考慮的。
精彩內容,請點擊下一頁!