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

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

  存儲的瓶頸寫到現在就要進入到深水區了,如果我們所做的網站已經到了做數據庫垂直拆分和水平拆分的階段,那麼此時我們所面臨的技術難度的挑戰也會大大增強。

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

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

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

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

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

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

  第一點:被拆出的表和原庫的其他表有關聯查詢即使用join查詢的操作需要進行改變;

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

  關於解決第一點的問題還是相對比較簡單的,方式方法也很多,下面我來講講我所知道的一些方法,具體如下:

  方法一: 在垂直拆表時候,我們先梳理下使用到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,這其中的緣由用過的人一定很好理解的。

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

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

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

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

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

  三、 PAXOS協議。

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

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

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

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

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

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

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

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

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

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章