但是我們可以想像一個極端一點的案例,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)緩存的重建和持久化。緩存在內存,系統總要維護,所以,緩存就會丟失,如果緩存沒了,就需要重建,如果數據量很大,緩存重建的過程會很慢,這會影響生產環境,所以,緩存的持久化也是需要考慮的。
諸多強大的NoSQL都很好支持了上述三大緩存的問題。
后端性能優化技術
前面討論了前端性能的優化技術,于是前端可能就不是瓶頸問題了。那么性能問題就會到后端數據上來了。下面說幾個后端常見的性能優化技術。
一、數據冗余
關于數據冗余,也就是說,把我們的數據庫的數據冗余處理,也就是減少表連接這樣的開銷比較大的操作,但這樣會犧牲數據的一致性。風險比較大。很多人把 NoSQL用做數據,快是快了,因為數據冗余了,但這對數據一致性有大的風險。這需要根據不同的業務進行分析和處理。(注意:用關系型數據庫很容易移植到 NoSQL上,但是反過來從NoSQL到關系型就難了)
二、數據鏡像
幾乎所有主流的數據庫都支持鏡像,也就是replication。數據庫的鏡像帶來的好處就是可以做負載均衡。把一臺數據庫的負載均分到多臺上,同時又保證了數據一致性(Oracle的SCN)。最重要的是,這樣還可以有高可用性,一臺廢了,還有另一臺在服務。
數據鏡像的數據一致性可能是個復雜的問題,所以我們要在單條數據上進行數據分區,也就是說,把一個暢銷商品的庫存均分到不同的服務器上,如,一個暢銷商品有1萬的庫存,我們可以設置10臺服務器,每臺服務器上有1000個庫存,這就好像B2C的倉庫一樣。
三、數據分區
數據鏡像不能解決的一個問題就是數據表里的記錄太多,導致數據庫操作太慢。所以,把數據分區。數據分區有很多種做法,一般來說有下面這幾種:
1)把數據把某種邏輯來分類。比如火車票的訂票系統可以按各鐵路局來分,可按各種車型分,可以按始發站分,可以按目的地分……,反正就是把一張表拆成多張有一樣的字段但是不同種類的表,這樣,這些表就可以存在不同的機器上以達到分擔負載的目的。
2)把數據按字段分,也就是豎著分表。比如把一些不經常改的數據放在一個表里,經常改的數據放在另外多個表里。把一張表變成1對1的關系,這樣,你可以減少表的字段個數,同樣可以提升一定的性能。另外,字段多會造成一條記錄的存儲會被放到不同的頁表里,這對于讀寫性能都有問題。但這樣一來會有很多復雜的控制。
3)平均分表。因為第一種方法是并不一定平均分均,可能某個種類的數據還是很多。所以,也有采用平均分配的方式,通過主鍵ID的范圍來分表。
4)同一數據分區。這個在上面數據鏡像提過。也就是把同一商品的庫存值分到不同的服務器上,比如有10000個庫存,可以分到10臺服務器上,一臺上有1000個庫存。然后負載均衡。
這三種分區都有好有壞。最常用的還是第一種。數據一旦分區,你就需要有一個或是多個調度來讓你的前端程序知道去哪里找數據。把火車票的數據分區,并放在各個省市,會對12306這個系統有非常有意義的質的性能的提高。
四、后端系統負載均衡
前面說了數據分區,數據分區可以在一定程度上減輕負載,但是無法減輕熱銷商品的負載,對于火車票來說,可以認為是大城市的某些主干線上的車票。這就需要使用數據鏡像來減輕負載。使用數據鏡像,你必然要使用負載均衡,在后端,我們可能很難使用像路由器上的負載均衡器,因為那是均衡流量的,因為流量并不代表服務器的繁忙程度。因此,我們需要一個任務分配系統,其還能監控各個服務器的負載情況。
任務分配服務器有一些難點:
負載情況比較復雜。什么叫忙?是CPU高?還是磁盤I/O高?還是內存使用高?還是并發高?還是內存換頁率高?你可能需要全部都要考慮。這些信息要發送給那個任務分配器上,由任務分配器挑選一臺負載最輕的服務器來處理。
任務分配服務器上需要對任務隊列,不能丟任務啊,所以還需要持久化。并且可以以批量的方式把任務分配給計算服務器。
任務分配服務器死了怎么辦?這里需要一些如Live-Standby或是failover等高可用性的技術。我們還需要注意那些持久化了的任務的隊列如何轉移到別的服務器上的問題。