歡迎光臨
每天分享高質量文章

關於大型網站技術演進的思考(五):儲存的瓶頸(5)

上文裡我遺留了兩個問題,一個問題是資料庫做了水平拆分以後,如果我們對主鍵的設計採取一種均勻分佈的策略,那麼它對於被水平拆分出的表後續的查詢操作將有何種影響,第二個問題就是水平拆分的擴容問題。這兩個問題在深入下去,本系列就越來越技術化了,可能最終很多朋友讀完後還是沒有找到解決實際問題的啟迪,而且我覺得這些問題都是像BAT這樣巨型網際網路公司才會認真思考的,因此本篇我打算換個角度來闡述本文的後續內容。

這裡我們首先要明確一個問題,到底是什麼因素促使我們去做資料庫的垂直拆分和水平拆分的呢?答案很簡單就是業務發展的需求,前文裡的水平拆分技術方案基本都是拋棄千變萬化的業務規則的限制,儘量將水平拆分的問題歸為一個簡單的技術實現方案,而純技術手段時常是看起來很美,但是到了面對現實問題時候,常常會變得那麼蒼白和無力。

水平拆分的難題裡我還有個難題沒有講述,就是水平拆分後對查詢操作的影響,特別是對單表查詢的影響,這點估計也是大夥最為關心的問題,今天我不在延著水平拆分的技術手段演進是闡述上文的遺留問題,而是我要把前面提到的技術手段和一些典型場景結合起來探討如何解決網站儲存的瓶頸問題。

前文中我總結過一個解決儲存瓶頸的脈絡,具體如下:

單庫資料庫–>資料庫讀寫分離–>快取技術–>搜尋技術–>資料的垂直拆分–>資料的水平拆分

這個脈絡給一些朋友產生了誤解,就是認為這個過程應該是個序列的過程,其實在實際的場景下這個過程往往是並行的,但是裡面有一個元素應該是序列的或者說思考時候有個先後問題,那就是對資料庫層的操作,具體如下:

單庫資料庫–>資料庫讀寫分離–>資料的垂直拆分–>資料的水平拆分

而快取技術和搜尋技術在資料庫的任意階段裡都可以根據實際的業務需求隨時切入其中幫助資料庫減輕不必要的壓力。例如,當網站的後臺資料庫還是單庫的時候,資料庫漸漸出現了瓶頸問題,而這個瓶頸又沒有達到需要採取大張旗鼓做讀寫分離方案的程度,那麼我這個時候可以考慮引入快取機制。不過要合理的使用快取我們首先要明確快取本身的特點,這些特點如下所示:

特點一:快取主要是適用於讀操作,並且快取的讀操作的效率要遠遠高於從資料庫以及硬碟讀取資料的效率。

特點二:快取的資料是儲存在記憶體當中,因此當系統重啟,宕機等等異常場景下,快取資料就會不可逆的丟失,且無法恢復,因此快取不能作為可靠儲存裝置,這就導致一個問題,快取裡的資料必須首先從資料庫裡同步到記憶體中,而使用快取的目的就是為瞭解決資料庫的讀操作效率低下的問題,資料庫的資料同步到快取的操作會因為資料庫的效率低下而在效能上大打折扣,所以快取適合的場景是那些固定不變的資料以及業務對實時性變化要求不高的資料。

根據快取的上述兩個特點,我們可以把資料庫裡和上述描述類似操作的相關資料遷移到快取裡,那樣我們就從資料庫上剝離了那些對資料庫價值不高的操作,讓資料庫專心做有價值的操作,這樣也是減輕資料庫壓力的一種手段。

不過這個手段侷限性很強,侷限性主要是一臺計算機了用於儲存快取的記憶體的大小都是遠遠要低於硬碟,並且記憶體的價格要遠貴於硬碟,如果我們將大規模的資料從硬碟往記憶體遷移,從資源成本和利用率角度考慮價效比還是很低的,因此快取往往都是用於轉存那些不會經常變化的資料字典,以及經常會被讀,而修改較少的資料,但是這些資料的規模也是有一定限度的,因此當單庫資料庫出現了瓶頸時候馬上就著手進行讀寫分離方案的設計價效比還是很高的。

前文我講到我們之所以選擇資料庫讀寫分離是主要原因是因為資料庫的讀寫比例嚴重失衡所致,但是做了讀寫分離必然有個問題不可避免,寫庫向讀庫同步資料一定會存在一定的時間差,如果我們想減小讀庫和寫庫資料的時間差,那麼任然會導致讀庫因為寫的粒度過細而發生部分效能的損失,但是時間差過大,或許又會無法滿足實際的業務需求,因此這個時間差的設計一定要基於實際的業務需求合理的設計。

同步的時間差的問題還是個小問題,也比較好解決,但是如何根據實際的業務需求做讀寫分離這其實還是非常有挑戰性的,這裡我舉個很常見的例子來說明讀寫分離的難度問題,我們這裡以淘寶為例,淘寶是個C2C的電商網站,它是網際網路公司提供一個平臺,商家自助接入這個平臺,在這個平臺上賣東西,這個和線下很多大賣場的樣式類似。淘寶是個大平臺,它的交易表裡一定是要記下所有商戶的交易資料,但是針對單個商家他們只會關心自己的網店的銷售資料,這就有一個問題了,如果某一個商家要查詢自己的交易資訊,淘寶就要從成千上萬的交易資訊裡檢索出該商家的交易資訊,那麼如果我們把所有交易資訊放在一個交易表裡,肯定有商家會有這樣的疑問,我的網店每天交易額不大,為什麼我查詢交易資料的速度和那些大商家一樣慢了?那麼我們到底該如何是解決這樣的場景了?

碰到這樣的情況,當網站的交易規模變大後就算我們把交易表做了讀寫分離估計也是沒法解決實際的問題,就算我們做的徹底點把交易表垂直拆分出來估計還是解決不了問題,因為一個業務資料庫擁有很多張表,但是真正壓力大的表畢竟是少數,這個符合28原則,而資料庫大部分的關鍵問題又都是在那些資料壓力大的表裡,就算我們把這些表單獨做讀寫分離甚至做垂直拆分,其實只是把資料庫最大的問題遷移出原來資料庫,而不是在解決該表的實際問題。

如果我們要解決交易表的問題我們首先要對交易表做業務級的拆分,那麼我們要為交易表增加一個業務維度:實時交易和歷史交易,一般而言實時交易以當天及當天24小時為界,歷史交易則是除去當天交易外的所有歷史交易資料。實時交易資料和歷史交易資料有著很大不同,實時交易資料讀與寫是比較均衡的,很多時候估計寫的頻率會遠高於讀的頻率,但是歷史交易表這點上和實時交易就完全不同了,歷史交易表的讀操作頻率會遠大於寫操作頻率,如果我們將交易表做了實時交易和歷史交易的拆分後,那麼讀寫分離方案適合的場景是歷史交易查詢而非實時交易查詢,不過歷史交易表的資料是從實時交易表裡同步過來的,根據這兩張表的業務特性,我們可以按如下方案設計,具體如下:

我們可以把實時交易表設計成兩張表,把它們分別叫做a表和b表,a表和b表按天交替進行使用,例如今天我們用a表記錄實時交易,明天我們就用b表記錄實時交易,當然我們事先可以用個配置表記錄今天到底使用那張表進行實時交易記錄,為什麼要如此麻煩的設計實時交易表了?這麼做的目的是為了實時交易資料同步到歷史資料時候提供便利,一般我們會在凌晨0點切換實時交易表,過期的實時交易表資料會同步到歷史交易表裡,這個時候需要資料遷移的實時交易表是全表資料遷移,效率是非常低下,假如實時交易表的資料量很大的時候,這種匯入同步操作會變得十分耗時,所以我們設計兩張實時交易表進行切換來把資料同步的風險降到最低。由此可見,歷史交易表每天基本都只做一次寫操作,除非同步出了問題,才會重覆進行寫操作,但是寫的次數肯定是很低的,所以歷史交易表的讀寫比例失衡是非常嚴重的。不過實時交易表的切換也是有技術和業務風險的,為了保證實時交易表的高效性,我們一般在資料同步操作成功後會清空實時交易表的資料,但是我們很難保證這個同步會不會有問題,因此同步時候我們最好做下備份,此外,兩個表切換的時候肯定會碰到這樣的場景,就是有人在凌晨0點前做了交易,但是這個交易是在零點後做完,假如實時交易表會記錄交易狀態的演變過程,那麼在切換時候就有可能兩個實時表的資料沒有做好接力,因此我們同步到歷史交易表的資料一定要保持一個原則就是已經完成交易的資料,沒有完成的交易資料兩張實時交易還要完成一個業務上的接力,這就是業界常說的資料庫日切的問題。

歷史交易表本身就是為讀使用的,所以我們從業務角度將交易表拆分成實時交易表和歷史交易表本身就是在為交易表做讀寫分離,居然了設計了歷史交易表我們就做的乾脆點,把歷史交易表做垂直拆分,將它從原資料庫裡拆分出來獨立建表,隨著歷史交易的增大,上文裡所說的某個商戶想快速檢索出自己的資料的難題並沒有得到根本的改善,為瞭解決這個難題我們就要分析下難題的根源在那裡。這個根源很簡單就是我們把所有商戶的資料不加區別的放進了一張表裡,不管是交易量大的商戶還是交易量小的商戶,想要查詢出自己的資料都要進行全表檢索,而關係資料庫單表記錄達到一定資料量後全表檢索就會變的異常低效,例如DB2當資料量超過了1億多,mysql單表超過了100萬條後那麼全表查詢這些表的記錄都會存在很大的效率問題,那麼我們就得對歷史交易表進一步拆分,因為問題根源是單表資料量太大了,那我們就可以對單表的資料進行拆分,把單表分成多表,這個場景就和前面說的水平拆分裡把原表變成邏輯表,原表的資料分散到各個獨立的邏輯表裡的方式一致,不過這裡我們沒有一開始做水平拆分,那是會把問題變麻煩,我們只要在一個資料庫下對單表進行拆分即可,這樣也能滿足我們的要求,並且避免了水平拆分下的跨庫寫作的難題。接下來我們又有一個問題了那就是我們按什麼維度拆分這張單表呢?

我們按照前文講到的水平拆分裡主鍵設計方案執行嗎?當然不行哦,因為那些方案明顯提升不了商戶檢索資料的效率問題,所以我們要首先分析下商戶檢索資料的方式,商戶一般會按這幾個維度檢索資料,這些維度分別是:商戶號、交易時間、交易型別,當然還有其他的維度,我這裡就以這三個維度為例闡述下麵的內容,商戶查詢資料效率低下的根本原因是全表檢索,其實商戶查詢至少有一個維度那就是商戶號來進行查詢,如果我們把該商戶的資料存入到一張單獨的表裡,自然查詢的效率會有很大的提升,但是在實際系統開發裡我們很少透過商戶號進行拆分表,這是為什麼呢?因為一個電商平臺的商戶是個動態的指標,會經常發生變化,其次,商戶號的粒度很細,如果使用商戶號拆分表的必然會有這樣的後果那就是我們可能要頻繁的建表,隨著商戶的增加表的數量也會增加,造成資料的碎片化,同時不同的商戶交易量是不一樣的,按商戶建表會造成資料儲存的嚴重不平衡。如果使用交易型別來拆分表,雖然維度的粒度比商戶號小,但是會造成資料的分散化,也就是說我們查詢一個商戶的全部交易資料會存在很大問題。由此可見拆表時候如何有效的控制維度的粒度以及資料的聚集度是拆分的關鍵所在,因為使用交易時間這個維度就會讓拆分更加合理,不過時間的維度的設計也是很有學問的,下麵我們看看騰訊分析的維度,如下所示:

騰訊分析的維度是今天這個其實相當於實時交易查詢,除此之外都是對歷史資料查詢,它們分為昨天、最近7天和最近30天,我們如果要對歷史交易表進行拆分也是可以參照騰訊分析的維度進行,不過不管我們選擇什麼維度拆分資料,那麼都是犧牲該維度成全了其他維度,例如我們按騰訊分析的維度拆分資料,那麼我們想靈活使用時間查詢資料將會受到限制。

我們把歷史交易資料透過交易時間維度進行了拆分,雖然得到了效率提升,但是歷史交易資料表是個累積表,隨著時間推移,首先是月表,接下來是周表都會因為資料累積產生查詢效率低下的問題,這個時候我們又該如何解決了?這個時候我們需要再引進一個維度,那麼這個時候我們可以選擇商戶號這個維度,但是商戶號作為拆分維度是有一定問題的,因為會造成資料分佈不均衡,那麼我們就得將維度的粒度由小變粗,其實一個電商平臺上往往少數商戶是完成了大部分電商平臺的交易,因此我們可以根據一定指標把重要商戶拆分出來,單獨建表,這樣就可以平衡了資料的分佈問題。

我們總結下上面的案例,我們會得到很多的啟迪,我將這些啟迪總結如下:

啟迪一:資料庫的讀寫分離不是簡單的把主庫資料匯入到讀庫裡就能解決問題,讀資料庫和寫資料的分離的目的是為了讓讀和寫操作不能相互影響效率。

啟迪二:解決讀的瓶頸問題的本質是減少資料的檢索範圍,資料檢索的範圍越小,讀的效率也就越高;

啟迪三:資料庫的垂直拆分和水平拆分首先不應該從技術角度進行,而是透過業務角度進行,如果資料庫進行業務角度的水平拆分,那麼拆分的維度往往是要根據該表的某個欄位進行的,這個欄位選擇要有一定原則,這個原則主要是該欄位的維度的粒度不能過細,該欄位的維度範圍不能經常的動態發生變化,最後就是該維度不能讓資料分佈嚴重失衡。

回到現實的開發裡,對於一個資料庫做拆表,分表的工作其實是一件很讓人惱火的工作,這主要是有以下原因所造成的,具體如下所述:

原因一:一個資料庫其實容納多少張表是有一定限制的,就算沒有超過這個限制,如果原庫本來有30張表,我們拆分後變成了60張,接著是120張,那麼資料庫本身管理這麼多表也會消耗很多效能,因此公司的DBA往往會控制那些過多分表的行為。

原因二:每次拆表後,都會牽涉到歷史資料的遷移問題,這個遷移風險很大,遷移方案如果設計的不完善可能會導致資料丟失或者損壞,如果關鍵資料發生了丟失和損壞,結果可能非常致命。因此在設計資料庫分表分庫方案時候我們要儘量讓受影響的資料範圍變得最小。

原因三:每次拆表和分表都會讓系統的相關方繃緊神經,方案執行後,會有很長時間的監控和觀察期,所以拆資料庫時常是一件令人討厭的事情。

原因四:為了保證新方案執行後確保系統沒有問題,我們常常會讓新舊系統並行執行一段時間,這樣可以保證如果新方案出現問題,問題的影響面最低,但是這種做法也有一個惡果就是會導致資料遷移方案要進行動態調整,從而增加遷移資料的風險

因此當公司不得不做這件事情時候,公司都會很自然去考慮第三種解決方案,第三種解決方案是指儘量不改變原資料庫的功能,而是另起爐灶,使用新技術來解決我們的問題,例如前文所說的搜尋技術解決資料庫like的低效問題就是其中方案之一,該方案只要我們將資料庫的表按一定時間匯入到檔案系統,然後對檔案建立倒排索引,讓like查詢效率更好,這樣就不用改變原資料庫的功能,又能減輕資料庫的壓力。

現在常用的第三種解決方案就是使用NoSql資料庫,NoSql資料庫大多都是針對檔案進行的,因此我們可以和使用搜索引擎那樣把資料匯入到檔案裡就行了,NoSql基本都採用Key/Value這種簡單的資料結構,這種資料結構和關係資料庫比起來更加的靈活,對原始資料的約束最少,所以在NoSql資料庫裡建表我們可以很靈活的把列和行的特性交叉起來用,這句話可能很多人不太理解,下麵我舉個例子解釋下,例如hadoop技術體系裡的hbase,hbase是一個基於列族的資料庫,使用hbase時候我們就可以透過列來靈活的拆分資料,比如我們可以把中國的省份作為一個列,將該省份的資料都放入到這個列下麵,在省這個維度下我們可以接著在定義一個列的維度,例如軟體行業,屬於軟體行業的資料放在這個列下麵,最終提供使用者查詢時候我們就可以減少資料檢索的範圍,最終達到提升查詢效率的目的。由此可見當我們用慣了關係資料庫後,學習像hbase這樣的Nosql資料庫我們會非常的不適應,因為關係資料庫的表有固定樣式,也就是我們常說的結構化資料,當表的定義好了後,就算裡面沒有資料,那麼這個結構也就固定了,我們使用表的時候都是按這個模型下麵,我們幾乎感覺不到它,但是到了hbase的使用就不同了,hbase使用時候我們都在不停的為資料增加結構化模型,而且這個維度是以列為維度的,而關係資料庫裡列確定後我們使用時候是無法改變的,這就是學習hbase的最大困難之一。Hbase之所以這麼麻煩的設計這樣的計算模型,終極目的就是為了讓海量資料按不同維度儲存起來,使用時候盡全力檢索資料檢索的數量,從而達到海量資料快速讀取的目的。

好了,今天就寫到這裡,祝大家生活愉快。



來自:夏天的森林

連結:http://www.cnblogs.com/sharpxiajun/p/4265853.html


贊(0)

分享創造快樂