解決海量數據的新思路——分佈式數據庫

目前,分佈式的概念越來越流行,但是在數據庫領域裏,分佈式的應用相對較少。在參閱了Google的Map/Reduce概念後,我構思了一種分佈式數據庫的架構,並實現了其雛形,現在將其基本思路寫出來,希望能起到拋磚引玉的作用。我工作時間不長,其中錯誤,不完善之處還請大家多多指出,謝謝。

設計這個分佈式數據庫的目的在於快速的處理海量數據。基本思路其實很簡單,將數據分佈到多個數據節點中,在執行SQL語句時,分析SQL語句的語義,對一個或多個數據庫進行操作。這樣就可以使查詢的壓力分散到每一個節點上面,面對海量數據時的處理時間大大縮短。

先拿幾個簡單的SQL語句做分析,看看在分佈式的環境下和平常有何不同。假設我們現在有兩個數據節點A和B,表名爲Table,其中ID爲1~100的數據保存在節點A,ID爲101~200的數據保存在節點B。以下的SQL語句都是同時對2個數據庫執行。

Select * from Table where ID=1
這樣A數據庫將返回ID爲1的數據,數據庫B返回爲空。這時簡單的合併A和B的數據,就可以得到正確的結果。

Select top 10 * from Table
這時A數據庫將返回10條數據,B數據庫返回10條數據,這時如果合併A和B,將返回20條結果。這時必須移除多餘的10條數據纔是正確的結果。

Select * from Table order by ID
這時A,B數據庫將返回所有的數據,但是要使得數據符合order by的條件,很顯然應該進行一次排序操作。

Select top 10 * from Table order by ID
這時A,B數據庫都返回10條數據,經過合併後,還要經過排序,移除的操作,才能確保結果正確。

SQL語句中需要處理的關鍵字還有max,min,count,sum,avg等,這裏就不寫出來了。經過這幾個例子我們可以看到,其實只要經過一些處理,分別對不同數據節點上的查詢,可以轉化成對單一數據庫查詢等效的結果。而這些處理歸納起來,只有合併,排序,移除這三種情況,其實這和Map/Reduce思想非常的類似,無論什麼複雜的動作,最終歸結都可以通過幾個簡單操作來完成。這些處理當然需要一定的時間,但是在面對海量數據時,很多情況下,處理所需要的時間可以小到忽略不計。

上面只是一些簡單的SQL語句,面對一些複雜的SQL語句,要在SQL語句處理的過程中,進行數據節點之間的數據交換才能完成的(例子在文末會給出)。因此要實現一個完全能夠處理SQL語句的分佈式數據庫,需要在數據庫的內核部分進行改動。在實現這個組件時,時間是有限的,進行內核部分的改造不現實,所以我採取了中間件的方式,來實現了這個分佈式數據庫的雛形,採用的數據庫是MSSQL2000,下圖是我設計的分佈式數據庫的概念圖(參見附件1):


如圖所示,數據根據一定規則分佈(一般可以直接Hash主鍵)到每一個數據節點中,由分佈式數據庫服務器對每個數據節點進行訪問,進行歸併/排序/移除操作,然後通過數據接口,返回給程序。

其中幾個數據接口所適用的場景爲:

Reader:提供對數據庫的查詢結果,逐條進行讀取的接口。在海量數據下,有時候需要讀取大量數據進行處理,如果一次讀取到內存中顯然不現實。此時可以使用Reader模式逐條讀取,進行分批處理。

DataFiller:提供對數據的XML包裝,適用於小數據量的讀取,主要是給Web應用提供一個方便的接口。

Command:執行delete,update,insert等不返回數據的SQL語句。

BulkCopy:批量插入接口。主要是爲大數據量的導入提供高速接口。

實現這個中間件,難點應該是在SQL語句的語義分析上。這塊應該使用編譯原理來實現,但是在我的實現中,並沒有用到,原因一個是時間問題,另外一個是因爲基於中間件的方式,對一些複雜的SQL語句無法得到正確的結果。所以使用了正則表達式和一些方法來對SQL語句進行分析,分析出應該如何對執行結果進行處理,以及SQL語句應該發送到單個節點還是多個節點。以下是處理的流程示意圖(參見附件2):

在實現時需要注意的地方是,一定要讓SQL語句從發送到執行,到返回結果之間沒有任何延遲,否則每秒能夠執行的SQL語句最多隻有幾十條。一開始我使用的模型是很常見的查詢線程模型(參見附件3):

每個語句執行完畢之後,在HashMap中將執行狀態設置爲執行完畢。使用一個查詢線程,不斷的遍歷HashMap,發現有執行完畢的語句,便將其發往結果處理模塊。爲了避免CPU佔用率100%,查詢線程必須要有Sleep語句,但是windows下線程輪切的最小時間段爲15ms,並且在Sleep的過程中,CPU將優先處理其他線程,這樣Sleep一次至少需要20ms。這樣,無論SQL查詢再快,分佈式數據庫的處理速度也會被限制在1000/20=50條/秒以下。在我做的第一個模型中,每秒最多隻能處理20多條SQL語句,在面對Web應用時,顯然是不夠的。

後來我採用的是信號量機制,即在生成Query線程時,給其分配一個信號量,執行每個SQL語句都會將一個監視線程加入線程池,監視線程堵塞住,等待所有信號量置爲發信狀態,然後立刻將結果送入結果處理模塊。Windows處理信號量是非常快的,可以以CPU指令週期來計量。經過這個改進,分佈式數據庫處理一個查詢的語句,基本等同於執行查詢所需的時間。當然,這樣的設計造成了使用的線程比較多,調試起來非常困難,需要非常小心的設計,而且在數據節點多的時候,必須維護一個成百上千線程的線程池,個人覺得是非常不好的。我注意到無論處理多少數據,MSSQL中的線程只有20多個,可以判斷出他們的設計是非常精巧的,肯定和我的這種設計不同。如果有更好的方法解決這個問題,請不吝賜教,謝謝。

以上便是一個分佈式數據庫中間件的基本概念和一個基本實現。當然,實現一個商用的中間件,還有很多工作需要做,例如權限,數據安全,節點故障處理,日誌等模塊,都有很多改進的地方。目前我實現的這個中間件非常簡陋,由於MSSQL本身的限制,有很多模塊實現得不夠優雅,不過唯一值得欣慰的是,性能上來說是非常不錯的,達到了分佈式系統的初衷。目前有3臺機器作爲數據節點運行,進行隨機數據訪問時,負載基本平均分到了每一個節點上。大數據量讀取,大數據量寫入一般都有單數據庫2倍以上的速度。當然,分佈式不是萬能的,目前有些問題是無法解決的。例如:

1、多表問題:簡單的舉個例子,例如有一張用戶表,一張產品ID表,還有一張交易記錄表,以用戶表,產品ID表爲外鍵,如果執行諸如

Select * from 交易記錄表 where 交易記錄表.產品ID=產品ID表.ID and 交易記錄表.用戶ID=用戶表.用戶ID

這樣的語句時,如果只對執行完的結果進行處理,無論如何架構這幾張表,都會出錯。爲什麼?原因有點難說清楚,有興趣的話仔細思考一下就知道了。
對於這樣的語句,中間件根本無法處理,只有修改內核,在執行語句的過程中,對每個數據節點進行數據交換,纔可以解決。目前的解決方法是把其中一張表放到單個數據庫上。不過這樣程序看起來就很怪異,一個查詢動作要用到兩個不同的數據庫訪問類,沒有弄明白整個框架的程序員都不知道爲什麼要這樣做。

2、語意分析:在分佈式的環境下,SQL語意轉換爲操作原語的難度更加高了,確保其邏輯完全正確很困難,我離散數學學得很差,目前還不能達到100%的正確率,所以不得不在數據接口中保留了手動模式,即手工決定該如何處理數據,非常的醜陋。以目前的識別率,一些複雜的SQL語句要麼分開幾次寫,要麼使用手動模式自定義其處理流程才能確保其正確,目前也沒時間去完善分析模塊,只能隨它去了。

提出這些問題希望能得到大家的指點,畢竟獨自一人開發思路會有很多侷限性,個人感覺其中還有很多地方可以挖掘,完全可能成爲另外一種處理海量數據的方式。最後,謝謝你的觀賞。

描述: 查詢線程模型
大小: 15.3 KB

描述: SQL查詢流程示意圖
大小: 36.3 KB

描述: 分佈式數據庫的概念圖
大小: 24.9 KB
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章