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

發表於2015-01-27

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

這裡我們先回顧下資料庫的垂直拆分和水平拆分的定義:

垂直拆分:把一個資料庫中不同業務單元的資料分到不同的資料庫裡。

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

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

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

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

相關文章