上文里我遺留了兩個問題,一個問題是數據庫做了水平拆分以后,如果我們對主鍵的設計采取一種均勻分布的策略,那么它對于被水平拆分出的表后續的查詢操作 將有何種影響,第二個問題就是水平拆分的擴容問題。這兩個問題在深入下去,本系列就越來越技術化了,可能最終很多朋友讀完后還是沒有找到解決實際問題的啟 迪,而且我覺得這些問題都是像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之所以這么麻煩的設計這樣的計算模型,終極目的就 是為了讓海量數據按不同維度存儲起來,使用時候盡全力檢索數據檢索的數量,從而達到海量數據快速讀取的目的。
?
好了,今天就寫到這里,祝大家生活愉快。
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
