一定要分表分庫嗎?
當然不一定。
雖然很多網際網路公司的體量很大、使用者非常多,但你千萬不要被這些現象迷惑了。實際上,90% 以上的系統能夠發展到上百萬、上千萬資料量已經很不錯了。對於千萬的資料量,開源的 MySQL 都可以很好地應對,更別說一些商業資料庫了。
一定要分庫嗎?說到分庫,他當然能夠解決儲存的問題,假設原先單庫只能最多儲存2千萬的資料量。採用分庫之後,儲存架構變成下圖所示的分庫架構,每個分庫都可以儲存2千萬資料量,容量的上限一下提升了。
雖然容量提升了,但也帶來了很多其他問題。
分庫資料間的資料無法再透過資料庫直接查詢了,比如跨多個分庫的資料需要多次查詢或藉助其他儲存進行聚合再查詢。分庫越多,出現問題的可能性越大,維護成本也變得更高。無法保障跨庫間事務,只能藉助其他中介軟體實現最終一致性。所以在解決容量問題上,可以根據業務場景選擇,不要一上來就要考慮分庫,分表也是一種選擇。
分表是指所有的資料均存在同一個資料庫例項中,只是將原先的一張大表按一定規則,劃分成多張行數較少的表。
它與分庫的區別是,分表後的子表仍在原有庫中,而分庫則是子表移動到新的資料庫例項裡並在物理上單獨部署。分表的拆分架構如下圖所示:
不管是打車的訂單、電商裡的支付訂單,還是外賣或團購的支付訂單,都是後臺服務中最重要的一環,關乎公司的營收。因此我將以訂單業務作為案例進行分析。
假設訂單只是單量多而每一單的資料量較小,這就適合採用分表。單條資料量小但行數多,會導致寫入(因為要構建索引)和查詢非常慢,但整體對於容量的佔用是可控的。
採用分表後,大表變成小表,寫入時構建索引的效能消耗會變小,其次小表的查詢效能也更好。如果採用了分庫,雖然解決了寫入和查詢的問題,但每張表所佔有的磁碟空間很少,也會產生資源浪費。兩種方案的對比如下圖所示:
在實際場景裡,因為要詳細記錄使用者的提單資訊,單個訂單記錄的資料量均較多,所以不存在行數多但單條資料量小的情況。但在其他寫入服務裡,經常會出現上述場景,你可以優先採用分表的方案。因為分表除了能解決容量問題,還能在一定程度上解決分庫所帶來的三個問題。
分表後可以透過 join 等完成一些富查詢,相比分庫簡單得多。
分表的資料仍儲存在一個數據庫裡,不會出現很多分庫。無須引入一些分庫中介軟體,因此維護成本和開發成本均較低。
因為在同一個資料庫裡,也可以很好地解決事務問題。
如何選擇分庫維度?如果你確定要對資料庫進行分庫,究竟要如何實現呢?
首先要解決的問題便是如何選擇分庫維度。
不同的分庫維度決定了部分查詢是否能直接使用資料庫,以及是否存在資料傾斜的問題。
介紹兩種常見的不同維度的分庫方式:
按直接滿足最重要的業務場景劃分按最細粒度隨機分按直接滿足最重要的業務場景劃分在業務上,所有的訂單資料都是隸屬於某一個使用者的。可以按訂單歸屬的使用者這個欄位進行分庫,則同一個使用者的訂單都在某一個分庫裡。
分庫後的場景如下圖所示:
訂單模組除了提供提交訂單介面外,還會提供給售賣商家對自己店鋪的訂單進行查詢及修改等功能。這些維度的查詢和修改需求,在採用了按購買使用者進行分庫之後,均無法直接滿足了。
這裡請你思考一個問題,訂單模組最重要的功能是什麼?
答案是保證客戶(即買家)的各項訂單功能能夠正常使用,比如下單、下單後立刻(無延遲)檢視已購的訂單資訊、待支付、待發貨、待配送的訂單列表等。
相對來說,訂單裡的商品售賣方(即賣家)所使用的功能並不是優先順序最高的。因為當我們要對賣家和買家的功能做取捨時,賣家是願意降低優先順序的,畢竟賣家是買賣的受益方。
按購買使用者劃分後,使用者的使用場景都可以直接透過分庫支援,而不需要透過異構資料(存在資料延遲)等手段解決,對使用者來說體驗較好。其次,在同一個分庫中,便於修改同一使用者的多條資料,因此也不存在分散式事務問題。
我們可以透過上述訂單案例抽象出一個分庫準則:在確定分庫欄位時應該以直接滿足最重要的業務場景為準。
很多其他的業務都參考了這一準則,比如——
對於微博和知乎等使用者生產內容(UGC)的業務,均會按使用者進行分庫。因為使用者新發布文章後就會去檢視列表。支付系統裡,也會按使用者的支付記錄進行分庫。在技術上,比如一個微服務下的監控資料,同樣會按微服務進行劃分。同一個微服務的監控資料均儲存在一個分庫裡,你可以直接在一個分庫裡檢視微服務下的所有監控資料。上述劃分方法雖然直接滿足了最重要的場景,但可能會出現資料傾斜的問題,比如出現一個超級客戶(如企業客戶),購買的訂單量非常大,導致某一個分庫資料量巨多,就會重現分庫前的場景。這屬於最極端的情況之一。
最細粒度隨機分對於傾斜的問題,可以採用最細粒度的拆分,即按資料的唯一標示進行拆分。
對於訂單來說唯一標示即為訂單號。採用訂單號進行分庫之後,使用者的訂單會按 Hash 隨機均勻地分散到某一個分庫裡。這樣就解決了某一個分庫資料不均勻的問題。
比如:
按使用者的每一條微博隨機分庫;按使用者的每一筆支付記錄隨機分庫;同一個微服務裡的每一個監控點的資料隨機分庫。採用最細粒度分庫後,雖然解決了資料均衡的問題,但又帶來了其他問題。
首先,除了對細粒度查詢外,對其他任何維度的查詢均不支援。這就需要透過異構等方式解決,但異構有延遲、對業務是有損的。其次,防重邏輯在資料庫層面無法支援。比如使用者對同一個訂單在業務上只能支付一次這一訴求,在支付系統按支付號進行分庫後便不能直接滿足了。因為上述分庫方式會導致不同支付單分散在不同的分庫裡,此時,期望在資料庫中透過訂單號的唯一索引進行支付防重就不可實施了。總之,這兩種分庫的方式,在解決問題的同時又帶來了一些新的問題。在架構中,沒有一種方案是可以解決所有問題的,更多的是根據場景去選擇更適合自己的方案。