1. 程式人生 > >【資料庫】水平分庫分表的關鍵步驟以及可能遇到的問題

【資料庫】水平分庫分表的關鍵步驟以及可能遇到的問題

原址:作者:丁浪;http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table?utm_source=infoq&utm_campaign=user_page&utm_medium=link

經常在面試的時候碰到一個問題,分庫分表的時候如何保證全域性分散式ID唯一性。在聊全域性分散式ID唯一性的之前,先來聊聊分片技術的由來。

分片技術的由來

關係型資料庫本身比較容易成為系統性能瓶頸,單機儲存容量、連線數、處理能力等都很有限,資料庫本身的“有狀態性”導致了它並不像Web和應用伺服器那麼容易擴充套件。在網際網路行業海量資料和高併發訪問的考驗下,聰明的技術人員提出了分庫分表技術(有些地方也稱為Sharding、分片)。同時,流行的分散式系統中介軟體(例如MongoDB、ElasticSearch等)均自身友好支援Sharding,其原理和思想都是大同小異的。


分散式全域性唯一ID

平時我們在小專案中經常使用資料庫自增主鍵來作為主鍵ID,但是在分散式環境下,資料分佈在不同的分片上,資料庫自增式主鍵容易造成不同分片上主鍵重複,目前比較流行的幾種ID生成方式如下:

  1. Twitter的Snowflake(又名“雪花演算法”)
  2. UUID/GUID(一般應用程式和資料庫均支援)
  3. MongoDB ObjectID(類似UUID的方式)
  4. Ticket Server(資料庫生存方式,Flickr採用的就是這種方式)

Twitter 的Snowflake演算法生成的是64位唯一Id(由41位的timestamp+ 10位自定義的機器碼+ 13位累加計數器組成)。

常見分片規則和策略

分片欄位該如何選擇

在開始分片之前,我們首先要確定分片欄位(也可稱為“片鍵”)。很多常見的例子和場景中是採用ID或者時間欄位進行拆分。這也並不絕對的,我的建議是結合實際業務,通過對系統中執行的sql語句進行統計分析,選擇出需要分片的那個表中最頻繁被使用,或者最重要的欄位來作為分片欄位。

常見分片規則

常見的分片策略有隨機分片和連續分片這兩種,如下圖所示:

當需要使用分片欄位進行範圍查詢時,連續分片可以快速定位分片進行高效查詢,大多數情況下可以有效避免跨分片查詢的問題。後期如果想對整個分片叢集擴容時,只需要新增節點即可,無需對其他分片的資料進行遷移。但是,連續分片也有可能存在資料熱點的問題,就像圖中按時間欄位分片的例子,有些節點可能會被頻繁查詢壓力較大,熱資料節點就成為了整個叢集的瓶頸。而有些節點可能存的是歷史資料,很少需要被查詢到。

隨機分片其實並不是隨機的,也遵循一定規則。通常,我們會採用Hash取模的方式進行分片拆分,所以有些時候也被稱為離散分片。隨機分片的資料相對比較均勻,不容易出現熱點和併發訪問的瓶頸。但是,後期分片叢集擴容起來需要遷移舊的資料。使用一致性Hash演算法能夠很大程度的避免這個問題,所以很多中介軟體的分片叢集都會採用一致性Hash演算法。離散分片也很容易面臨跨分片查詢的複雜問題。

資料遷移,容量規劃,擴容等問題

很少有專案會在初期就開始考慮分片設計的,一般都是在業務高速發展面臨效能和儲存的瓶頸時才會提前準備。因此,不可避免的就需要考慮歷史資料遷移的問題。一般做法就是通過程式先讀出歷史資料,然後按照指定的分片規則再將資料寫入到各個分片節點中。

此外,我們需要根據當前的資料量和QPS等進行容量規劃,綜合成本因素,推算出大概需要多少分片(一般建議單個分片上的單表資料量不要超過1000W)。

如果是採用隨機分片,則需要考慮後期的擴容問題,相對會比較麻煩。如果是採用的範圍分片,只需要新增節點就可以自動擴容。

跨分片技術問題

跨分片的排序分頁

一般來講,分頁時需要按照指定欄位進行排序。當排序欄位就是分片欄位的時候,我們通過分片規則可以比較容易定位到指定的分片,而當排序欄位非分片欄位的時候,情況就會變得比較複雜了。為了最終結果的準確性,我們需要在不同的分片節點中將資料進行排序並返回,並將不同分片返回的結果集進行彙總和再次排序,最後再返回給使用者。如下圖所示:

上面圖中所描述的只是最簡單的一種情況(取第一頁資料),看起來對效能的影響並不大。但是,如果想取出第10頁資料,情況又將變得複雜很多,如下圖所示:

有些讀者可能並不太理解,為什麼不能像獲取第一頁資料那樣簡單處理(排序取出前10條再合併、排序)。其實並不難理解,因為各分片節點中的資料可能是隨機的,為了排序的準確性,必須把所有分片節點的前N頁資料都排序好後做合併,最後再進行整體的排序。很顯然,這樣的操作是比較消耗資源的,使用者越往後翻頁,系統性能將會越差。

跨分片的函式處理

在使用Max、Min、Sum、Count之類的函式進行統計和計算的時候,需要先在每個分片資料來源上執行相應的函式處理,然後再將各個結果集進行二次處理,最終再將處理結果返回。如下圖所示:

跨分片join

Join是關係型資料庫中最常用的特性,但是在分片叢集中,join也變得非常複雜。應該儘量避免跨分片的join查詢(這種場景,比上面的跨分片分頁更加複雜,而且對效能的影響很大)。通常有以下幾種方式來避免:

全域性表

全域性表的概念之前在“垂直分庫”時提過。基本思想一致,就是把一些類似資料字典又可能會產生join查詢的表資訊放到各分片中,從而避免跨分片的join。

ER分片

在關係型資料庫中,表之間往往存在一些關聯的關係。如果我們可以先確定好關聯關係,並將那些存在關聯關係的表記錄存放在同一個分片上,那麼就能很好的避免跨分片join問題。在一對多關係的情況下,我們通常會選擇按照資料較多的那一方進行拆分。如下圖所示:

這樣一來,Data Node1上面的訂單表與訂單詳細表就可以直接關聯,進行區域性的join查詢了,Data Node2上也一樣。基於ER分片的這種方式,能夠有效避免大多數業務場景中的跨分片join問題。

記憶體計算

隨著spark記憶體計算的興起,理論上來講,很多跨資料來源的操作問題看起來似乎都能夠得到解決。可以將資料丟給spark叢集進行記憶體計算,最後將計算結果返回。

跨分片事務問題

跨分片事務也分散式事務,想要了解分散式事務,就需要了解“XA介面”和“兩階段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支援是存在問題的,會導致主從資料不一致。直到5.7x版本中才得到修復。Java應用程式可以採用Atomikos框架來實現XA事務(J2EE中JTA)。

我們的系統真的需要分庫分表嗎

讀完上面內容,不禁引起有些讀者的思考,我們的系統是否需要分庫分表嗎?

其實這點沒有明確的判斷標準,比較依賴實際業務情況和經驗判斷。依照筆者個人的經驗,一般MySQL單表1000W左右的資料是沒有問題的(前提是應用系統和資料庫等層面設計和優化的比較好)。當然,除了考慮當前的資料量和效能情況時,作為架構師,我們需要提前考慮系統半年到一年左右的業務增長情況,對資料庫伺服器的QPS、連線數、容量等做合理評估和規劃,並提前做好相應的準備工作。如果單機無法滿足,且很難再從其他方面優化,那麼說明是需要考慮分片的。這種情況可以先去掉資料庫中自增ID,為分片和後面的資料遷移工作提前做準備。

很多人覺得“分庫分表”是宜早不宜遲,應該儘早進行,因為擔心越往後公司業務發展越快、系統越來越複雜、系統重構和擴充套件越困難…這種話聽起來是有那麼一點道理,但我的觀點恰好相反,對於關係型資料庫來講,我認為“能不分片就別分片”,除非是系統真正需要,因為資料庫分片並非低成本或者免費的。

這裡筆者推薦一個比較靠譜的過渡技術–“表分割槽”。主流的關係型資料庫中基本都支援。不同的分割槽在邏輯上仍是一張表,但是物理上卻是分開的,能在一定程度上提高查詢效能,而且對應用程式透明,無需修改任何程式碼。筆者曾經負責優化過一個系統,主業務表有大約8000W左右的資料,考慮到成本問題,當時就是採用“表分割槽”來做的,效果比較明顯,且系統執行的很穩定。

小結

最後,有很多讀者都想了解當前社群中有沒有開源免費的分庫分表解決方案,畢竟站在巨人的肩膀上能省力很多。當前主要有兩類解決方案:

  1. 基於應用程式層面的DDAL(分散式資料庫訪問層) 

    比較典型的就是淘寶半開源的TDDL,噹噹網開源的Sharding-JDBC等。分散式資料訪問層無需硬體投入,技術能力較強的大公司通常會選擇自研或參照開源框架進行二次開發和定製。對應用程式的侵入性一般較大,會增加技術成本和複雜度。通常僅支援特定程式語言平臺(Java平臺的居多),或者僅支援特定的資料庫和特定資料訪問框架技術(一般支援MySQL資料庫,JDBC、MyBatis、Hibernate等框架技術)。

  2. 資料庫中介軟體,比較典型的像mycat(在阿里開源的cobar基礎上做了很多優化和改進,屬於後起之秀,也支援很多新特性),基於Go語言實現kingSharding,比較老牌的Atlas(由360開源)等。這些中介軟體在網際網路企業中大量被使用。另外,MySQL 5.x企業版中官方提供的Fabric元件也號稱支援分片技術,不過國內使用的企業較少。 

    中介軟體也可以稱為“透明閘道器”,大名鼎鼎的mysql_proxy大概是該領域的鼻祖(由MySQL官方提供,僅限於實現“讀寫分離”)。中介軟體一般實現了特定資料庫的網路通訊協議,模擬一個真實的資料庫服務,遮蔽了後端真實的Server,應用程式通常直接連線中介軟體即可。而在執行SQL操作時,中介軟體會按照預先定義分片規則,對SQL語句進行解析、路由,並對結果集做二次計算再最終返回。引入資料庫中介軟體的技術成本更低,對應用程式來講侵入性幾乎沒有,可以滿足大部分的業務。增加了額外的硬體投入和運維成本,同時,中介軟體自身也存在效能瓶頸和單點故障問題,需要能夠保證中介軟體自身的高可用、可擴充套件。

總之,不管是使用分散式資料訪問層還是資料庫中介軟體,都會帶來一定的成本和複雜度,也會有一定的效能影響。所以,還需讀者根據實際情況和業務發展需要慎重考慮和選擇。