大型高并发网站之查询性能优化(综合篇)
原文:http://blog.jobbole.com/83475/
http://blog.jobbole.com/83473/
一、什么是大型網站
首先我們要思考一個問題,什么樣的網站才是大型網站,從網站的技術指標角度考慮這個問題人們很容易犯一個毛病就是認為網站的訪問量是衡量的指標,懂點行的人也許會認為是網站在單位時間里的并發量的大小來作為指標,如果按這些標準那么像hao123這樣的網站就是大型網站了。
其實這種網站訪問量非常大,并發數也非常高,但是它卻能用最為簡單的web技術來實現:我們只要保持網站的充分的靜態化,多部署幾臺服務器,那么就算地球上所有人都用它,網站也能正常運行。
我覺得大型網站是技術和業務的結合,一個滿足某些用戶需求的網站只要技術和業務二者有一方難度很大,必然會讓企業投入更多的、更優秀的人力成本實現它,那么這樣的網站就是所謂的大型網站了。
某些網站在高并發下會報出503錯誤,503錯誤的含義是指網站服務端暫時無法提供服務的含義,503還表達了網站服務端現在有問題但是以后可能會提供正常的服務,對http協議熟悉的人都知道,5開頭的響應碼表達了服務端出現了問題,在我們開發測試時候最為常見的是500錯誤,500代表的含義是服務端程序出現了錯誤導致網站無法正常提供服務,500通常是服務端異常和錯誤所致,如果生產系統里發現了500錯誤,那么只能說明網站存在邏輯性的錯誤,這往往是系統上線前的測試做的不到位所致。
? 503錯誤其實更加準確的回答應該是服務不可用,503錯誤在高并發的情況下90%的原因是數據庫所致的。高并發的情況整個網站系統首先暴露出問題的是數據庫,如果我們把整個網站系統比作一個盛水的木桶,那么木桶最短的那個板就是數據庫了,一般而言網站的服務應用出問題都會是解決存儲問題之后才會出現。
? 數據庫出現了瓶頸并不是程序存在邏輯性錯誤,數據庫瓶頸的表現就是數據庫因為承受了太多的訪問后,數據庫無法迅速的做出響應,嚴重時候數據庫會拒絕進一步操作死鎖在哪里不能做出任何反應。數據庫猶如一把巨型的大鎖,很多人爭搶這個鎖時候會導致這個大鎖完全被鎖死,最終請求的處理就停留在這個大鎖上最終導致網站提示出503錯誤,503錯誤最終會傳遞到所有的客戶端上,最終的現象就是全站不可用了。
在網站遇到存儲瓶頸時,主要有以下的優化方向:
1、單庫數據庫
2、數據庫讀寫分離
3、緩存技術
4、搜索技術
5、數據庫的垂直拆分
6、數據庫的水平拆分
7、業務級拆分
但是這些優化方向并不完全是個串行的過程,其實在實際的場景下這個過程往往是并行的,但是里面有一個元素應該是串行的或者說思考時候有個先后問題,那就是對數據庫層的操作,具體如下:
單庫數據庫–>數據庫讀寫分離–>數據的垂直拆分–>數據的水平拆分
而緩存技術和搜索技術在數據庫的任意階段里都可以根據實際的業務需求隨時切入其中幫助數據庫減輕不必要的壓力。
二、慎用數據庫的計算功能
我是以java工程師應聘進了我現在的公司,所以在我轉到專職前端前,我也做過不少java的應用開發,當時我在公司的前輩告訴我,我們公司的數據庫建模很簡單,怎么個簡單法了,數據庫的表之間都沒有外鍵,數據庫不準寫觸發器,可以寫寫存儲過程,但是存儲過程決不能用于處理生產業務邏輯,而只能是一些輔助工作,例如導入導出寫數據啊,后面聽說就算是數據庫做到了讀寫分離,數據之間同步也最好是用java程序做,也不要使用存儲過程,除非迫不得已。開始我還不太理解這些做法,這種不理解不是指我質疑了公司的做法,而是我在想如果一個數據庫我們就用了這么一點功能,那還不如讓數據庫公司為咋們定制個閹割版算了,不過在我學習了hadoop之后我有點理解這個背后的深意了,其實作為存儲數據的數據庫,它和我們開發出的程序的本質是一樣的那就是:存儲和計算,那么當數據庫作為一個業務系統的存儲介質時候,那么它的存儲對業務系統的重要性要遠遠大于它所能承擔的計算功能,當數據庫作為互聯網系統的存儲介質時候,如果這個互聯網系統成長迅速,那么這個時候我們對數據庫存儲的要求就會越來越高,最后估計我們都想把數據庫的計算特性給閹割掉,當然數據庫基本的增刪改查我們是不能舍棄的,因為它們是數據庫和外界溝通的入口,我們如果接觸過具有海量數據的數據庫,我們會發現讓數據庫運行的單個sql語句都會變得異常簡潔和簡單,因為這個時候我們知道數據庫已經在存儲這塊承擔了太多的負擔,那么我們能幫助數據庫的手段只能是盡量降低它運算的壓力。
三、單庫數據庫
一個初建的網站往往用戶群都是很小的,最簡單的網站架構就能解決實際的用戶需求,當然為了保證網站的穩定性和安全性,我們會把網站的應用部署到至少兩臺機器上,后臺的存儲使用數據庫,如果經濟實力允許,數據庫使用單臺服務器部署,由于數據是網站的生命線,因此我們常常會把部署數據庫的服務器使用的好點,這個網站結構如下所示:
這個結構非常簡單,其實大部分初建網站開發里往往業務邏輯沒有企業級系統那么復雜,所以只要有個好的idea,建設一個新網站的成本是非常低的,所使用的技術手段也是非常的基本和簡單,不過該圖我們要準備三臺服務器,而且還要租個機房放置我們的服務器,這些成本對于草根和屌絲還是非常高的,幸運的是當下很多大公司和機構提供了云平臺,我們可以花費很少的錢將自己的應用部署到云平臺上,這種做法我們甚至不用去考慮把應用、數據庫分開部署的問題,更加進一步的降低了網站開發和運維的成本,但是這種做法也有一個問題,就是網站的小命被這個云平臺捏住了,如果云平臺掛了,俺們的網站服務也就跟著掛了。
通常我們要把網站服務應用部署到多臺服務器,這么做的目的一般有兩個:
不過要做到以上兩點,并不是我們簡單將網站分開部署就可以滿足的.其中一點就是Session同步的問題,關于此請參考《session同步問題》
四、數據庫讀寫分離
寫到這里一個嬰兒般的網站就這樣被我們創造出來了,我們希望網站能健康快速的成長,如果網站真的按我們預期成長了,那么一定會有一天我們制造的寶寶屋已經滿足不了現實的需求,這個時候我們應該如何抉擇了?換掉,全部換掉,使用新的架構例如我們以前長提的SOA架構,分布式技術,這個方法不錯,但是SOA和分布式技術是很難的,成本是很高的,如果這時候我們通過添加幾臺服務器就能解決問題的話,我們絕對不要去選擇什么分布式技術,因為這個成本太高了。上面我講到幾種session共享的方案,這個方案解決了應用的水平擴展問題,那么當我們網站出現瓶頸時候就多加幾臺服務器不就行了嗎?那么這里就有個問題了,當網站成長很快,網站首先碰到的瓶頸到底是哪個方面的問題?
本人是做金融網站的,我們所做的網站有個特點就是當用戶訪問到我們所做的網站時候,目的都很明確就是為了付錢,用戶到了我們所做的網站時候都希望能快點,再快點完成本網站的操作,很多用戶在使用我們做的網站時候不太去關心網站的其他內容,因此我們所做的網站相對于數據庫而言就是讀寫比例其實非常的均勻,甚至很多場景寫比讀要高,這個特點是很多專業服務網站的特點,其實這樣的網站和企業開發的特點很類似:業務操作的重要度超過了業務展示的重要度,因此專業性網站吸納企業系統開發的特點比較多。但是大部分我們日常常用的網站,我們逗留時間很長的網站按數據庫角度而言往往是讀遠遠大于寫,例如大眾點評網站它的讀寫比率往往是9比1。
12306或許是中國最著名的網站之一,我記得12306早期經常出現一個問題就是用戶登錄老是登不上,甚至在高峰期整個網站掛掉,頁面顯示503網站拒絕訪問的問題,這個現象很好理解就是網站并發高了,大量人去登錄網站,購票,系統掛掉了,最后所有的人都不能使用網站了。當網站出現503拒絕訪問時候,那么這個網站就出現了最致命的問題,解決大用戶訪問的確是個超級難題,但是當高并發無法避免時候,整個網站都不能使用這個只能說網站設計上發生了致命錯誤,一個好的網站設計在應對超出自己能力的并發時候我們首先應該是不讓他掛掉,因為這種結果是誰都不能使用,我們希望那些在可接受的請求下,讓在可接受請求范圍內的請求還是可以正常使用,超出的請求可以被拒絕,但是它們絕對不能影響到全網站的穩定性,現在我們看到了12306網站的峰值從未減少過,而且是越變越多,但是12306出現全站掛掉的問題是越來越少了。通過12036網站改變我們更進一步思考下網站的瓶頸問題。
排除一些不可控的因素,網站在高并發下掛掉的原因90%都是因為數據庫不堪重負所致,而應用的瓶頸往往只有在解決了存儲瓶頸后才會暴露,那么我們要升級網站能力的第一步工作就是提升數據庫的承載能力,對于讀遠大于寫的網站我們采取的方式就是將數據庫從讀寫這個角度拆分,具體操作就是將數據庫讀寫分離,如下圖所示:
我們這時要設計兩個數據庫,一個數據庫主要負責寫操作我們稱之為主庫,一個數據庫專門負責讀操作我們稱之為副庫,副庫的數據都是從主庫導入的,數據庫的讀寫分離可以有效的保證關鍵數據的安全性,但是有個缺點就是當用戶瀏覽數據時候,讀的數據都會有點延時,這種延時比起全站不可用那肯定是可以接受的。不過針對12306的場景,僅僅讀寫分離還是遠遠不夠的,特別是負責讀操作的副庫,在高訪問下也是很容易達到性能的瓶頸的,那么我們就得使用新的解決方案:使用分布式緩存,不過緩存的缺點就是不能有效的實時更新,因此我們使用緩存前首先要對讀操作的數據進行分類,對于那些經常不發生變化的數據可以事先存放到緩存里,緩存的訪問效率很高,這樣會讓讀更加高效,同時也減輕了數據庫的訪問壓力。至于用于寫操作的主庫,因為大部分網站讀寫的比例是嚴重失衡,所以讓主庫達到瓶頸還是比較難的,不過主庫也有一個讀的壓力就是主庫和副庫的數據同步問題,不過同步時候數據都是批量操作,而不是像請求那樣進行少量數據讀取操作,讀取操作特別多,因此想達到瓶頸還是有一定的難度的。聽人說,美國牛逼的facebook對數據的任何操作都是事先合并為批量操作,從而達到減輕數據庫壓力的目的。
??讀寫分離方案主要是應用于網站讀寫比例嚴重失衡的網站,而互聯網上絕大部分網站都是讀操作的比例遠遠大于寫操作,這是網站的主流,如果一個網站讀寫比例比較均衡,那么這個網站一般都是提供專業服務的網站,這種網站對于個人而言是一個提供生活便利的工具,它們和企業軟件類似。大部分關注大型網站架構技術關心的重點應該是那種對于讀寫比例失衡的網站,因為它們做起來更加有挑戰性。
將數據庫進行讀寫分離是網站解決存儲瓶頸的第一步,為什么說是第一步呢?因為讀寫分離從業務角度而言它是一種粗粒度的數據拆分,因此它所包含的業務復雜度比較低,容易操作和被掌控,從技術而言,實現手段也相對簡單,因此讀寫分離是一種低成本解決存儲瓶頸的一種手段,這種方案是一種改良方案而不是革命性的的方案,不管是從難度,還是影響范圍或者是經濟成本角度考慮都是很容易讓相關方接受的。
?那么我們僅僅將數據庫做讀寫分離為何能產生好的效率了?回答這個問題我們首先要了解下硬盤的機制,硬盤的物理機制就有一個大圓盤飛速旋轉,然后有個磁頭不斷掃描這個大圓盤,這樣的物理機制就會導致硬盤數據的順序操作比隨機操作效率更高,這點對于硬盤的讀和寫還算公平,但是寫操作在高并發情況下會有點復雜,寫操作有個特性就是我們要保證寫操作的準確性,但是高并發下可能會出現多個用戶同時修改某一條數據,為了保證數據能被準確的修改,那么我們通常要把并行的操作轉變為串行操作,這個時候就會出現一個鎖機制,鎖機制的實現是很復雜的,它會消耗很多系統性能,如果寫操作摻雜了讀操作情況就更復雜,效率會更加低效,相對于寫操作讀操作就單純多了,如果我們的數據只有讀操作,那么讀的性能也就是硬盤順序讀能力和隨機讀能力的體現,即使摻雜了并發也不會對其有很大的影響,因此如果把讀操作和寫操作分離,效率自然會得到很大提高。
五、使用緩存
雖然讀寫分離可以提升存儲系統的效率,但是內存效率是硬盤的幾萬倍,因此我們也要使用內存緩存來提示效率。
緩存技術和搜索技術在數據庫的任意階段里都可以根據實際的業務需求隨時切入其中幫助數據庫減輕不必要的壓力。例如,當網站的后臺數據庫還是單庫的時候,數據庫漸漸出現了瓶頸問題,而這個瓶頸又沒有達到需要采取大張旗鼓做讀寫分離方案的程度,那么我這個時候可以考慮引入緩存機制。不過要合理的使用緩存我們首先要明確緩存本身的特點,這些特點如下所示:
特點一:緩存主要是適用于讀操作,并且緩存的讀操作的效率要遠遠高于從數據庫以及硬盤讀取數據的效率。
特點二:緩存的數據是存儲在內存當中,因此當系統重啟,宕機等等異常場景下,緩存數據就會不可逆的丟失,且無法恢復,因此緩存不能作為可靠存儲設備,這就導致一個問題,緩存里的數據必須首先從數據庫里同步到內存中,而使用緩存的目的就是為了解決數據庫的讀操作效率低下的問題,數據庫的數據同步到緩存的操作會因為數據庫的效率低下而在性能上大打折扣,所以緩存適合的場景是那些固定不變的數據以及業務對實時性變化要求不高的數據。
根據緩存的上述兩個特點,我們可以把數據庫里和上述描述類似操作的相關數據遷移到緩存里,那樣我們就從數據庫上剝離了那些對數據庫價值不高的操作,讓數據庫專心做有價值的操作,這樣也是減輕數據庫壓力的一種手段。
不過這個手段局限性很強,局限性主要是一臺計算機了用于存儲緩存的內存的大小都是遠遠要低于硬盤,并且內存的價格要遠貴于硬盤,如果我們將大規模的數據從硬盤往內存遷移,從資源成本和利用率角度考慮性價比還是很低的,因此緩存往往都是用于轉存那些不會經常變化的數據字典,以及經常會被讀,而修改較少的數據,但是這些數據的規模也是有一定限度的,因此當單庫數據庫出現了瓶頸時候馬上就著手進行讀寫分離方案的設計性價比還是很高的。
以下是一個大型網站的緩存系統示例:
關于緩存的更多內容請參考《memcache的一致性hash算法使用》
六、使用搜索技術
上面的方案我們可以保證在高并發下網站的穩定性,但是針對于讀,如果數據量太大了,就算網站不掛掉了,用戶能很快的在海量數據里檢索到所需要的信息又成為了網站的一個瓶頸,如果用戶需要很長時間才能獲得自己想要的數據,很多用戶會失去耐心從而放棄對網站的使用,那么這個問題又該如何解決了?
解決方案就是我們經常使用的百度,谷歌哪里得來,對于海量數據的讀我們可以采用搜索技術,我們可以將數據庫的數據導出到文件里,對文件建立索引,使用倒排索引技術來檢索信息,我們看到了百度,谷歌有整個互聯網的信息我們任然能很快的檢索到數據,搜索技術是解決快速讀取數據的一個有效方案,不過這個讀取還是和數據庫的讀取有所區別的,如果用戶查詢的數據是通過數據庫的主鍵字段,或者是通過很明確的建立了索引的字段來檢索,那么數據庫的查詢效率是很高的,但是使用網站的人跟喜歡使用一些模糊查詢來查找自己的信息,那么這個操作在數據庫里就是個like操作,like操作在數據庫里效率是很低的,這個時候使用搜索技術的優勢就非常明顯了,搜索技術非常適合于模糊查詢操作。
六、數據庫垂直拆分
業務再接著的增長下去,數據量也會隨之越來越大了,這樣發展下去總有一天主庫也會產生瓶頸了,那么接下來我們又該如何解決主庫的瓶頸了?方法很簡單就是我們要拆分主庫的數據了,那么我該以什么維度拆分數據了?一個數據庫里有很多張表,不同的表都針對不同的業務,網站的不同業務所帶來的數據量也不是不同的,這個時候系統的短板就是那些數據量最大的表,所以我們要把那些會讓數據庫產生瓶頸的表拆出來,例如電商系統里商品表和交易表往往數據量非常大,那么我們可以把這兩種表建立在單獨的兩個數據庫里,這樣就拆分了數據庫的壓力,這種做法叫做數據垂直拆分,不過垂直拆分會給原有的數據庫查詢,特別是有事務的相關操作產生影響。關于此的更多內容請參考《大型網站之存儲瓶頸(數據庫的垂直拆分)》
最基本的讀寫分離的目的是為了解決數據庫的某張表讀寫比率嚴重失衡的問題。
我們要分析下數據庫的寫操作,單獨的寫操作效率都是很高的,不管我們的寫是單條記錄的寫操作,還是批量的寫操作,這些寫操作的數據量就是我們要去寫的數據的大小,因此控制寫的數據量的大小是一件很容易很天然的操作,所以這些操作不會造成數據庫太大負擔,詳細點的話,對于數據庫而言,新增操作無非是在原來數據后面追加些記錄,而修改操作或者刪除操作一般都是通過建立了高效索引的字段來定位數據后再進行的操作,因此它的性能也是非常高的。但是如果有大量的并發寫操作,而產生鎖競爭的話,就會變得低效了。而讀操作看起來比寫操作簡單(例如:讀操作不存在像事務這些烏七八糟因素的干擾),但是當讀操作面對海量數據時候就嚴重挑戰著數據庫和硬盤的極限能力,因此讀操作很容易產生瓶頸問題,而且這個瓶頸不管問題表是否讀寫失衡都會面臨的。
另外,像oracle和mysql這樣鼎鼎大名的關系數據庫默認的最大連接數是100,一般上了生產環境我們可能會設置為150或者200,這些連接數已經到了這些關系數據庫的最大極限了,如果再加以提升,數據庫性能會嚴重下降,最終很有可能導致數據庫由于壓力過大而變成了一個巨鎖,最終導致系統發生503的錯誤,如是我們就會想到采用讀寫分離方案,將數據庫的讀操作遷移到專門的讀庫里,如果系統的負載指標和我列舉的例子相仿,那么遷移的讀庫甚至不用做什么垂直拆分就能滿足實際的業務需求,因為我們的目的只是為了減輕數據庫的連接壓力。
七、數據庫的水平拆分
當我們的系統做完了讀寫分離,數據垂直拆分后,我們的網站還在迅猛發展,最終一定又會達到新的數據庫瓶頸,當然這些瓶頸首先還是出現在那些數據量大的表里,這些表數據的處理已經超出了單臺服務器的能力,這個時候我們就得對這個單庫單表的數據進行更進一步的拆分,也就是將一張表分布到兩臺不同的數據庫里,這個做法就是叫做數據的水平拆分了。關于此的更多內容請參考《大型網站之存儲瓶頸(數據庫的水平拆分)》
八、業務級的拆分
到底是什么因素促使我們去做數據庫的垂直拆分和水平拆分的呢?答案很簡單就是業務發展的需求,前文里的水平拆分技術方案基本都是拋棄千變萬化的業務規則的限制,盡量將水平拆分的問題歸為一個簡單的技術實現方案,而純技術手段時常是看起來很美,但是到了面對現實問題時候,常常會變得那么蒼白和無力。因此對于存儲瓶頸,我們應該更多的考慮業務的優化。關于此的更多內容請參考《大型網站存儲瓶頸(業務級的拆分)》
九、總結
啟迪一:數據庫的讀寫分離不是簡單的把主庫數據導入到讀庫里就能解決問題,讀數據庫和寫數據的分離的目的是為了讓讀和寫操作不能相互影響效率。
啟迪二:解決讀的瓶頸問題的本質是減少數據的檢索范圍,數據檢索的范圍越小,讀的效率也就越高;
啟迪三:數據庫的垂直拆分和水平拆分首先不應該從技術角度進行,而是通過業務角度進行,如果數據庫進行業務角度的水平拆分,那么拆分的維度往往是要根據該表的某個字段進行的,這個字段選擇要有一定原則,這個原則主要是該字段的維度的粒度不能過細,該字段的維度范圍不能經常的動態發生變化,最后就是該維度不能讓數據分布嚴重失衡。
回到現實的開發里,對于一個數據庫做拆表,分表的工作其實是一件很讓人惱火的工作,這主要是有以下原因所造成的,具體如下所述:
原因一:一個數據庫其實容納多少張表是有一定限制的,就算沒有超過這個限制,如果原庫本來有30張表,我們拆分后變成了60張,接著是120張,那么數據庫本身管理這么多表也會消耗很多性能,因此公司的DBA往往會控制那些過多分表的行為。
原因二:每次拆表后,都會牽涉到歷史數據的遷移問題,這個遷移風險很大,遷移方案如果設計的不完善可能會導致數據丟失或者損壞,如果關鍵數據發生了丟失和損壞,結果可能非常致命。因此在設計數據庫分表分庫方案時候我們要盡量讓受影響的數據范圍變得最小。
原因三:每次拆表和分表都會讓系統的相關方繃緊神經,方案執行后,會有很長時間的監控和觀察期,所以拆數據庫時常是一件令人討厭的事情。
原因四:為了保證新方案執行后確保系統沒有問題,我們常常會讓新舊系統并行運行一段時間,這樣可以保證如果新方案出現問題,問題的影響面最低,但是這種做法也有一個惡果就是會導致數據遷移方案要進行動態調整,從而增加遷移數據的風險
因此當公司不得不做這件事情時候,公司都會很自然去考慮第三種解決方案,第三種解決方案是指盡量不改變原數據庫的功能,而是另起爐灶,使用新技術來解決我們的問題,例如前文所說的搜索技術解決數據庫like的低效問題就是其中方案之一,該方案只要我們將數據庫的表按一定時間導入到文件系統,然后對文件建立倒排索引,讓like查詢效率更好,這樣就不用改變原數據庫的功能,又能減輕數據庫的壓力。
現在常用的第三種解決方案就是使用NoSql數據庫,NoSql數據庫大多都是針對文件進行的,因此我們可以和使用搜索引擎那樣把數據導入到文件里就行了,NoSql基本都采用Key/Value這種簡單的數據結構,這種數據結構和關系數據庫比起來更加的靈活,對原始數據的約束最少,所以在NoSql數據庫里建表我們可以很靈活的把列和行的特性交叉起來用,這句話可能很多人不太理解,下面我舉個例子解釋下,例如hadoop技術體系里的hbase,hbase是一個基于列族的數據庫,使用hbase時候我們就可以通過列來靈活的拆分數據,比如我們可以把中國的省份作為一個列,將該省份的數據都放入到這個列下面,在省這個維度下我們可以接著在定義一個列的維度,例如軟件行業,屬于軟件行業的數據放在這個列下面,最終提供用戶查詢時候我們就可以減少數據檢索的范圍,最終達到提升查詢效率的目的。由此可見當我們用慣了關系數據庫后,學習像hbase這樣的Nosql數據庫我們會非常的不適應,因為關系數據庫的表有固定模式,也就是我們常說的結構化數據,當表的定義好了后,就算里面沒有數據,那么這個結構也就固定了,我們使用表的時候都是按這個模型下面,我們幾乎感覺不到它,但是到了hbase的使用就不同了,hbase使用時候我們都在不停的為數據增加結構化模型,而且這個維度是以列為維度的,而關系數據庫里列確定后我們使用時候是無法改變的,這就是學習hbase的最大困難之一。Hbase之所以這么麻煩的設計這樣的計算模型,終極目的就是為了讓海量數據按不同維度存儲起來,使用時候盡全力檢索數據檢索的數量,從而達到海量數據快速讀取的目的。
總結
以上是生活随笔為你收集整理的大型高并发网站之查询性能优化(综合篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 性能摄影设计续航怎么选?荣耀Play4T
- 下一篇: java.lang.RuntimeExc