230
魔獸
分布式數據庫——從線性擴展談分布式JOIN
在首屆阿裏巴巴中間件峰會上,來自阿裏巴巴DRDS團隊的夢實分享了《分布式數據庫——從線性擴展談分布式JOIN》。他主要從OLTP數據庫的線性擴展、水平擴容、IN查詢、分布式JOIN四個方麵進行了分享。在分享中,他主要通過買家與訂單場景、家庭與孩子場景介紹了IN查詢,通過同維度的JOIN、廣播表的JOIN、Nested Loop Join詳細介紹了分布式JOIN的坑與填坑。
以下內容根據直播視頻整理而成。
在數據庫的使用過程中,我們難免會問到這樣的問題,為什麼分庫分表?答案是為了達到線性擴展。在本次分享中,我們能夠知道分布式數據庫中線性擴展的含義,學會判定一個係統與查詢能否達到線性擴展的目的,達到使用分布式數據庫的目標。
OLTP數據庫的線性擴展
數據庫主要有兩類:OLAP數據庫,SQL一般比較複雜,執行時間可能在秒級至分鍾級,響應時間越快越好(單SQL占據更多的資源,例如Map Reduce模型),提供盡可能高的並發度;OLTP數據庫,SQL一般比較簡單,執行時間一般在毫秒級,響應時間在可接受範圍(例如10ms)內即可(單SQL一般隻有一個線程執行),提供盡可能高係統容量。
對於OLTP,來說,是否機器越多,SQL執行越快?答案是否定的。對於OLTP數據庫中的線性擴展,增加機器數,單SQL的響應時間基本不會發生太大變化;增加機器數,能線性增加整個係統的容量(並發度、吞吐量、TPS)。並且,在資源一定的情況下,從單機到分布式並不能帶來更高的係統容量。
水平擴容
比如做一個全表的count(*)操作,每個分庫上的時間可能是10ms,如果不帶拆分鍵的話則需要到所有的表上去執行一個count(*)操作,如果將這些SQL並行的發下去,則會發現查詢也隻消耗了10ms的時間,與隻在一個分庫上執行的時間差不多。那為什麼還要帶拆分鍵?
比如我們有兩台DB實例,如果帶拆分鍵的話,往分布式數據庫裏麵提交一個查詢,到DB這邊也是一個查詢,那麼提交6條查詢之後,係統容量是6QPS。我們把提交到分布式數據庫中的查詢稱為邏輯查詢,把分布式數據庫到底層所執行的查詢稱為物理查詢。對於業務來說,分布式數據庫是相對透明的,它關注的容量指的是邏輯QPS。
發現係統容量不夠時,需要進行擴容,我們需要加1台DB,成本提升了50%。如圖所示,性能確實提高了50%。
假如我們的SQL沒有帶拆分鍵,那麼它需要路由到所有的分片上去執行。一條邏輯SQL會生成2條物理SQL。擴容之後,成本提升了50%,一條邏輯SQL會生成3條物理SQL,係統性能也沒有變化。所以得出一個結論:係統一定要帶拆分鍵,否則沒有可擴展性。
IN查詢
買家與訂單場景
具體場景是:提供一個買家的所有訂單ID,查出這些訂單的信息,訂單表按照訂單ID進行拆分,SELECT * FROM ORDER WHERE ORDER_ID IN (?,?,?,?...),假設單DB容量是1000QPS。
比如,2005年數據分片數為16,平均一個買家的訂單數:2,IN查詢涉及的分片數:期望值接近2,係統容量:16*1000/2=8000。到了2015年,數據分片數為64,平均一個買家的訂單數:200,IN查詢涉及的分片數:期望值接近64≈全表掃描,係統容量:64*1000/64=1000。在這種場景下,IN查詢不具備線性擴展能力。
家庭與孩子場景
具體場景是:提供一個家庭的所有孩子ID,查出這些孩子的信息,孩子表按照孩子ID進行拆分,SELECT * FROM CHILD WHERE CHILD_ID IN (?,?),假設單DB容量是1000QPS。跟前麵的區別在於,訂單數會隨著時間的推移飛速發展,但是孩子數不會隨著時間發生太明顯的變化。
假設,2005年,數據分片數:16,平均一個家庭的孩子數:1-2,IN查詢涉及的分片數:期望值接近2,係統容量:16*1000/2=8000。2015年,數據分片數:64,平均一個家庭的孩子數:1-2,IN查詢涉及的分片數:期望值接近2,係統容量:64*1000/2=32000。在這種情況下,成本提升了50%,係統容量也提升了50%,IN查詢實現了線性擴展。
線性擴展
對IN查詢來說,必須滿足下述條件才能做到線性擴展:IN的值的數目遠遠小於分片數;一般情況下,IN的值的數目在2-3個;IN的值的數目不會隨著業務的發展而增長。相反的,隻要有一條不滿足,那麼IN查詢就無法做到線性擴展。
分布式JOIN
分布式JOIN有很多種情況,主要分為兩大類:可下推的JOIN,JOIN操作由存儲完成,DRDS層針對JOIN的結果進行處理,效率會高一些,因為存儲和計算在一起;不可以下推的JOIN,存儲層隻做單表的查詢,DRDS完成JOIN的操作。
同維度的JOIN
可下推的JOIN中很重要的是同維度的JOIN,JOIN的兩個表是按照拆分鍵做的JOIN,簡單例子如下:
SELECT* FROM
user JOIN user_address
ON user.user_id= user_address.user_id
user與user_address需要JOIN,並且均以user_id為拆分鍵。這樣的結果是,對於同一個用戶,其地址與它在同一個分片內。所以隻需要在存儲層把JOIN做好就可以了。對於DRDS來說,這個JOIN操作由下麵的MySQL或者RDS來完成。DRDS層隻需要把JOIN結果返回去就可以了。這種JOIN線性判斷的標準是與單表SQL相同。
廣播表的JOIN
另外一個比較常用的JOIN是廣播表的JOIN。有些表具有以下特點:比較小,總會與其他表進行關聯,這時候就不適合將其放在其中一個庫上麵,那麼,這個JOIN就沒有辦法下推了。廣播表是指所有分片中都存在一個完整的副本,一般用於變更比較少,容量比較小,需要頻繁與其表發生關聯的表。一張拆分表JOIN一張廣播表的簡單例子如下:
SELECT* FROM
user JOIN user_address
ON user.user_id= user_address.user_id
此時,選擇把level表作為廣播表複製到每一個分片上去,這樣的JOIN也可以做下推,隻要做分片內的JOIN就可以了。這樣一個查詢的線性判斷標準與單表SQL相同,由拆分表上的查詢決定。
Nested Loop Join
Nested Loop Join是不可下推的分布式JOIN。具體例子如下:
SELECT* FROM
order JOIN user
ON order.buyer_id=user.user_id
user以user_id為拆分鍵,order(訂單)以order_id為拆分鍵。JOIN不能由存儲來完成,隻能由DRDS層完成。具體的算法等價於在左表把需要的數據拿出來然後再去右表做應用查詢,即以小表驅動(經過WHERE條件過濾後數據量較少的表為小表)。
對於這種JOIN,線性判斷標準直觀來說是對兩個表的查詢均需要能夠線性。即對驅動表的判斷與單表查詢相同;對被驅動表的判斷:被驅動表的JOIN列是否是拆分鍵,被驅動表做的IN查詢的數目。
實例分析
主要有兩張表,order表拆分鍵為order_id,user表拆分鍵為User_id。
SELECT* FROM order JOIN user ON order.buyer_id = user.user_id
結果分析:兩張表均是全表掃描。
SELECT* FROM order JOIN user ON order.buyer_id = user.user_id WHERE order.id = 1
結果分析:order為驅動表,user表的JOIN列是拆分鍵,一個order隻有唯一一個user。
SELECT* FROM user JOIN order ON user.user_id = order.buyer_id WHERE user.id = 1
結果分析:user為驅動表,並且帶了拆分鍵上的等值條件,order表的JOIN列不是拆分鍵,對於order表是全表掃描。
SELECT* FROM user_oders JOIN order ON user_oders.order_id = order.order_id WHERE user_oders.user_id = 1
結果分析:user_orders記錄了一個用戶有哪些訂單,拆分鍵為user_id,user_oders表為驅動表,並且帶了拆分鍵上的等值條件,order表的JOIN列是拆分鍵,一個user對應的order會隨業務的增長而增長,在order表做的IN查詢也會增長。
買賣家問題
具體場景是:一個訂單,包含買家id(buyer_id)與賣家id(seller_id),買家希望根據買家來查,而賣家則希望根據賣家來查。那麼一張表通過買家來拆還是通過賣家來拆?
傳統的解法是空間換時間,模仿單機數據庫中建索引的這種方式,將數據存兩份,一份按買家id拆分,一份按照賣家id拆分。這個方案的缺點是占的空間太大,因為把整個表都冗餘起來了。
或許有人認為,一種較好的解決方案是隻冗餘id列,隻需要買家id到賣家id的映射關係和賣家id到買家id的映射關係。在JOIN查詢時,隨著時間發展,一個買家對應的賣家會越來越多,那麼這就是一個錯誤的方案。更好的方案是把關聯關係做一個全冗餘,使其避免出現這樣的JOIN查詢,用更多的空間來換取時間、線性擴展的能力。
DRDS
如何快速判斷一個正在使用DRDS的係統是否能做到線性擴展?在DRDS監控頁麵裏麵有兩個監控指標,一個是物理QPS,一個是邏輯QPS。判斷的方法是,看這兩個指標是否接近1:1。如果接近1:1說明係統很大概率是可以線性擴展的。若遠遠大於1:1,那麼就不能線性擴展。最後更新:2017-08-13 22:40:45