關於大型網站技術演進的思考(三)--存儲的瓶頸(3)[轉]

存儲的瓶頸寫到如今就要進入到深水區了,若是咱們所作的網站已經到了作數據庫垂直拆分和水平拆分的階段,那麼此時咱們所面臨的技術難度的挑戰也會大大加強。html

  這裏咱們先回顧下數據庫的垂直拆分和水平拆分的定義:前端

  垂直拆分:把一個數據庫中不一樣業務單元的數據分到不一樣的數據庫裏。java

  水平拆分:是根據必定的規則把同一業務單元的數據拆分到多個數據庫裏。sql

  垂直拆分是一個粗粒度的拆分數據,它主要是將原來在一個數據庫下的表拆分到不一樣的數據庫裏,水平拆分粒度比垂直拆分要更細點,它是將一張表拆到不一樣數據庫裏,粒度的粗細也會致使實現技術的難度的也不同,很明顯水平拆分的技術難度要遠大於垂直拆分的技術難度。難度意味着投入的成本的增長以及咱們須要承擔的風險的加大,咱們作系統開發必定要有個清晰的認識:能用簡單的方案解決問題,就必定要絕不猶豫的捨棄複雜的方案,當系統須要使用高難度技術的時候,咱們必定要讓本身感覺到這是無可奈何的數據庫

  我是以java工程師應聘進了我如今的公司,因此在我轉到專職前端前,我也作過很多java的應用開發,當時我在公司的前輩告訴我,咱們公司的數據庫建模很簡單,怎麼個簡單法了,數據庫的表之間都沒有外鍵,數據庫不許寫觸發器,能夠寫寫存儲過程,可是存儲過程決不能用於處理生產業務邏輯,而只能是一些輔助工做,例如導入導出寫數據啊,後面據說就算是數據庫作到了讀寫分離,數據之間同步也最好是用java程序作,也不要使用存儲過程,除非無可奈何。開始我還不太理解這些作法,這種不理解不是指我質疑了公司的作法,而是我在想若是一個數據庫咱們就用了這麼一點功能,那還不如讓數據庫公司爲咋們定製個閹割版算了,不過在我學習了hadoop以後我有點理解這個背後的深意了,其實做爲存儲數據的數據庫,它和咱們開發出的程序的本質是同樣的那就是:存儲和計算,那麼當數據庫做爲一個業務系統的存儲介質時候,那麼它的存儲對業務系統的重要性要遠遠大於它所能承擔的計算功能,當數據庫做爲互聯網系統的存儲介質時候,若是這個互聯網系統成長迅速,那麼這個時候咱們對數據庫存儲的要求就會愈來愈高,最後估計咱們都想把數據庫的計算特性給閹割掉,固然數據庫基本的增刪改查咱們是不能捨棄的,由於它們是數據庫和外界溝通的入口,咱們若是接觸過具備海量數據的數據庫,咱們會發現讓數據庫運行的單個sql語句都會變得異常簡潔和簡單,由於這個時候咱們知道數據庫已經在存儲這塊承擔了太多的負擔,那麼咱們能幫助數據庫的手段只能是儘可能下降它運算的壓力編程

  回到關於數據庫垂直拆分和水平拆分的問題,假如咱們的數據庫設計按照咱們公司業務數據庫爲藍本的話,那麼數據庫進行了水平拆分咱們會碰到什麼樣的問題了?爲了回答這個問題我就要比較下拆分前和拆分後會給調用數據庫的程序帶來怎樣的不一樣,不一樣主要是兩點:安全

  第一點:被拆出的表和原庫的其餘表有關聯查詢即便用join查詢的操做須要進行改變;服務器

  第二點:某些增刪改(注意:通常業務庫設計不多使用物理刪除,由於這個操做十分危險,這裏的刪每每是邏輯刪除,通常作法就是更新下記錄的狀態,本質是一個更新操做)牽涉到拆分的表和原庫其餘表共同完成,那麼該操做的事務性就會被打破,若是處理很差,假如碰到操做失敗,業務沒法作到回滾,這會對業務操做的安全性帶來極大的風險。mybatis

  關於解決第一點的問題仍是相對比較簡單的,方式方法也不少,下面我來說講我所知道的一些方法,具體以下:框架

  方法一:在垂直拆表時候,咱們先梳理下使用到join操做sql查詢,梳理的維度是以被拆分出的表爲原點,若是是弱依賴的join表咱們改寫下sql查詢語句,若是是強依賴的join表則隨拆分表一塊兒拆分,這個方法很簡單也很可控,可是這個技術方案存在一個問題,就是讓拆分粒度變大,拆分的業務規則被幹擾,這麼拆分很容易犯一個問題就是一個數據庫裏總會存在這樣一些表,就是不少數據庫都會和它關聯,咱們很難拆解這些關聯關係,當咱們沒法理清時候就會把該表作冗餘,即不一樣數據庫存在雷同表,隨着業務增加,這種表的數據同步就成爲了數據庫的一個軟肋,最終它會演變爲整個數據庫系統的短板甚至是全系統的短板。

  方法二:咱們拆表的準則仍是按業務按需求在數據庫層面進行,等數據庫拆好後,再改寫原來受到影響的join查詢語句,這裏我要說明的是查詢語句修改的成本很低,由於查詢操做是個只讀操做,它不會改變任何底層的東西,若是數據表跨庫,咱們能夠把join查詢拆分爲屢次查詢,最後將查詢結果在內存中概括和合並,其實咱們若是主動拆庫,毫不會把換個不一樣的數據庫產品創建新庫,確定是使用相同數據庫,同類型的數據庫基本都支持跨庫查詢,不過跨庫查詢據說效率不咋地,咱們能夠有選擇的使用。這種方案也有個致命的缺點,咱們作數據庫垂直拆分毫不可能一次到位,通常都是屢次迭代,而該方案的影響面很大,關聯方過多,每次拆表幾乎要檢查全部相關的sql語句,這會致使系統不斷累積不可預知的風險。

  如下三段內容是方法三:

      無論是方法一仍是方法二,都有一個很根本的缺陷就是數據庫和上層業務操做耦合度很高,每次數據庫的變遷都致使業務開發跟隨作大量的同步工做,這樣的後果就是資源浪費,作服務的人不能每天被數據庫牽着鼻子走,這樣業務系統的平常維護和業務擴展會很存問題,那麼咱們必定要有一個服務和數據庫解耦方案,那麼這裏咱們就得借鑑ORM技術了。(這裏我要說明下,方法一和方法二我都是以修改sql闡述的,在現實開發裏不少系統會使用ORM技術,互聯網通常用ibatis和mybatis這種半ORM的產品,由於它們能夠直接寫sql和數據庫最爲親近,若是使用hibernate則就不一樣了,可是hibernate雖然大部分不是直接寫sql,可是它只不過是對數據庫操做作了一層映射,本質手段是一致,因此上文的sql能夠算是一種指代,它也包括ORM裏的映射技術)

       傳統的ORM技術例如hibernate還有mybatis都是針對單庫進行的,並不能幫咱們解決垂直拆分的問題,所以咱們必須本身開發一套解決跨庫操做的ORM系統,這裏我只針對查詢的ORM談談本身的見解(講到這裏是否是有些人會有種似成相識的感受,這個不是和分佈式系統很像嗎)。

       其實具體怎麼重構有問題的sql不是我想討論的問題,由於這是個技術手段或者說是一個技術上的技巧問題,我這裏重點講講這個ORM與服務層接口的交互,對於服務層而言,服務層最怕的就是被數據庫牽着鼻子走,由於當數據庫要進行重大改變時候,服務層老是千方百計讓本身不要發生變化,對於數據庫層而言服務層的建議都應該是合理,數據庫層要把服務層當作本身的需求方,這樣雙方纔能齊心合力完成這件重要的工做,那麼服務層通常是怎樣和數據庫層交互的呢?

       從傳統的ORM技術咱們能夠找到答案,具體的方式有兩種:

       第一種:以hibernate爲表明的,hibernate框架有一套本身的查詢語言就是hql,它相似於sql,自定義一套查詢語言看起來很酷,也很是靈活,可是實現難度很是之高,由於這種作法至關於咱們要本身編寫一套新的編程語言,若是這個語言設計很差,使用者又理解不深刻,最後每每會事與願違,就像hibernate的hql,咱們常常令可直接使用sql也不肯意使用hql,這其中的原因用過的人必定很好理解的。

      第二種:就是數據層給服務層提供調用方法,每一個方法對應一個具體的數據庫操做,就算底層數據庫發生重大變遷,只要提供給服務端的方法定義不變,那麼數據庫的變遷對服務層影響度也會最低。

      前面我提到技術難度是咱們選擇技術的一個重要指標,相比之下第二種方案將會是咱們的首選。

      垂直拆分數據庫還會帶來另外一個問題就是對事務的影響,垂直拆分數據庫會致使原來的事務機制變成了分佈式事務,解決分佈式事務問題是很是難的,特別是若是咱們想使用業界推出的解決分佈式事務方案,那麼要本身實現個分佈式事務就更難了,不過這裏我要說明一下,我這裏說的更難是和我寫本文有關,我本篇文章之因此如今才寫是由於我想先研究下業界推出的分佈式解決方案,可是這些方案的原理看得我很沮喪,我就想若是咱們直接用方案的接口實現了它,由於仍是不懂他的不少原理,那麼這些方案其實就是不可控方案,說不定使用過多就會給系統埋下定時炸彈,所以這裏我就只提提這些方案,有興趣的童鞋能夠去研究下:

  1、X/OPEN組織推出的分佈式事務規範XA,其中還包括該組織定義的分佈式事務處理模型X/OPEN

  2、大型網站一致性理論CAP/BASE

  3、 PAXOS協議。

  這裏特別要提的是PAXOS協議,我之前寫過好幾篇關於zookeeper的文章,zookeeper框架有一個特性就是它自己是一個分佈式文件系統,當咱們往zookeeper寫數據時候,zookeeper集羣能保證咱們的寫操做的可靠性,這個可靠性和咱們使用線程安全來控制寫數據同樣,絕對不會讓寫操做出錯,之因此zookeeper能作到這點,是由於zookeeper內部有一個相似PAXOS協議的協議,這個協議相似一個選舉方案,它能保證寫入操做的原子性。

  其實事務也是和線程安全技術相似,只不過事務是要保證一個業務操做的原子性問題,固然事務還要有個特色就是回滾機制即業務操做失敗,事務能夠保證系統恢復到業務操做前的狀態,回滾機制的本質實際上是維護業務操做的狀態性,具體點我這裏列舉個例子:當系統將要執行一個業務操做時候,咱們首先爲業務系統定義一個初始狀態,業務執行操做時候咱們能夠定義一個執行狀態,操做成功就是一個成功狀態,操做失敗就是一個操做失敗狀態,若是業務操做是失敗狀態,咱們可讓業務回滾到初始狀態,更進一步若是執行狀態超時也能夠將整個業務狀態回退到初始狀態,其實全部事務回滾機制的本質基本都是如此。記得不久前,在羣裏有個羣友就問你們如何實現分佈式事務,他想要知道的分佈式事務是有沒有一種技術能像咱們操做數據庫或者是jdbc那樣一個commit,一個rollback就搞定,可是現實中的分佈式事務比commit和rollback複雜的多,不可能簡單的讓咱們寫幾個標記就能實現分佈式事務,固然業界是有方案的,就是我上面提到的,若是有人真想知道能夠本身研究下,不過我本人如今仍是不太懂上面這些技術的原理和思想。

  其實當時我立刻給那位羣友一個解答,我說咱們開發時候是常常碰到分佈式事務,可是咱們解決分佈式事務大多數從業務角度來解決的,而沒去選擇純技術手段,由於技術手段太複雜難以控制。這個答案可能不會令提問者滿意,可是我如今仍是堅持這個觀點,這個觀點符合我提到的原則,當技術方案難度太高,咱們就不要輕易選擇使用它,由於這麼作是很危險的,今天我就舉個例子吧,這樣可能更有說服力。我如今作的系統不少業務操做常常要和其餘系統共同完成,其餘系統有咱們公司本身的系統,也有其餘企業的系統,這裏我仍是把業務操做比做一輛在高速公路的汽車,那麼每一個系統就是高速公路上的一個收費站,業務每到一個收費站,該系統的數據庫就會在對應的數據庫的某張表裏某條記錄上記錄一個狀態,當汽車跑徹底程,各個收費站就會相互通知,告訴你們任務完成,最終將全部的狀態置爲已完成,若是失敗,就廢掉這輛汽車,收費站之間也會相互通知,讓全部的記錄狀態迴歸到初始狀態,就當歷來沒有這輛汽車來過。這個作法的原理就是使用了事務回滾的本質,狀態的變遷和回退,這個作法在業務系統開發裏也有個專有術語就是工做流。其實大多數問如何實現分佈式事務如何實現的問題的本質就是想解決事務的回滾問題,咱們其實不要被這個分佈式事務的名字給嚇住了,其實有不少不起眼的技術手段和業務手段都能達到相同的目的。

  晚上11點了,看來本文今天寫不完了,今天就到此爲止,最後我要總結下本文的內容,具體以下:

  1. 大型網站解決存儲瓶頸的問題,咱們要找準存儲這個關鍵點,由於數據庫實際上是存儲和運算的組合體,可是在咱們這個場景下,存儲是第一位的,當存儲是瓶頸時候咱們要狠下心來儘可能多的拋棄數據的計算特色,因此上文中我提出咱們數據庫就不要濫用計算功能了例如觸發器、存儲過程等等。

  2. 數據庫剝離計算功能不表明不要數據的計算功能,由於沒有數據的計算功能數據庫也就沒價值了,那麼咱們要將數據庫的計算功能進行遷移,遷移到程序裏面,通常大型系統程序和數據庫都是分開部署到不一樣服務器上,所以程序裏處理數據計算就不會影響到數據庫所在服務器的性能,就可讓安裝數據庫的服務器專心服務於存儲。

  3. 咱們要盡一切可能的把數據庫的變化對服務層的影響降到最低,最好是數據庫作拆分後,現有業務不要任何的更改,那麼咱們就得設計一個全新的數據訪問層,這個數據訪問層將數據庫和服務層進行解耦,任何數據庫的變化都由數據訪問層消化,數據訪問層對外接口要高度統一,不要輕易改變。

  4. 若是咱們設計了數據訪問層來解決數據庫拆分的問題,數據訪問層加上數據庫其實就組合出了一個分佈式數據庫的解決方案,因而可知拆分數據庫的難度是很高的,由於數據庫將擁有分佈式的特性,而分佈式開發就意味開發難度的增長。

  5. 對於分佈式事務的處理,咱們儘可能要從具體問題具體分析,不要一感受這個事務操做本質是分佈式事務就去尋找通用的分佈式事務技術手段,這樣的想法實際上是迴避困難的思想,結果可能會是把問題搞得更加複雜。

  好了,今天就寫到這裏吧,祝你們晚安,生活愉快!

 

 

轉自 http://www.cnblogs.com/sharpxiajun/p/4251714.html

相關文章
相關標籤/搜索