1. 程式人生 > >資料庫分割槽、分表、分庫,讀寫分離

資料庫分割槽、分表、分庫,讀寫分離

分割槽

就是把一張表的資料分成N個區塊,在邏輯上看最終只是一張表,但底層是由N個物理區塊組成的。

分割槽的實現方式(簡單)

mysql5 開始支援分割槽功能

CREATE TABLE sales (
    id INT AUTO_INCREMENT,
    amount DOUBLE NOT NULL,
    order_day DATETIME NOT NULL,
    PRIMARY KEY(id, order_day)
) ENGINE=Innodb 
PARTITION BY RANGE(YEAR(order_day)) (
    PARTITION p_2010 VALUES LESS THAN (2010),
    PARTITION p_2011 VALUES LESS THAN (2011),
    PARTITION p_2012 VALUES LESS THAN (2012),
PARTITION p_catchall VALUES LESS THAN MAXVALUE);

分表

就是把一張表按一定的規則分解成N個具有獨立儲存空間的實體表。系統讀寫時需要根據定義好的規則得到對應的字表明,然後操作它。

分表的實現方式(複雜)

需要業務系統配合遷移升級,工作量較大

常見分割槽分表的規則策略(類似)

  1. Range(範圍)
  2. Hash(雜湊)
  3. 按照時間拆分
  4. Hash之後按照分表個數取模
  5. 在認證庫中儲存資料庫配置,就是建立一個DB,這個DB單獨儲存user_id到DB的對映關係

分庫

一旦分表,一個庫中的表會越來越多,這時需要分成多個數據庫。

垂直拆分

將系統中不存在關聯關係或者需要join的表可以放在不同的資料庫不同的伺服器中。

按照業務垂直劃分。比如:可以按照業務分為資金、會員、訂單三個資料庫。

需要解決的問題:跨資料庫的事務、jion查詢等問題。

水平拆分

例如,大部分的站點。資料都是和使用者有關,那麼可以根據使用者,將資料按照使用者水平拆分。

按照規則劃分,一般水平分庫是在垂直分庫之後的。比如每天處理的訂單數量是海量的,可以按照一定的規則水平劃分。需要解決的問題:資料路由、組裝。

讀寫分離

對於時效性不高的資料,可以通過讀寫分離緩解資料庫壓力。需要解決的問題:在業務上區分哪些業務上是允許一定時間延遲的,以及資料同步問題。

思路

垂直分庫-->水平分庫-->讀寫分離

垂直分庫帶來的問題和解決思路

跨庫join的問題

在拆分之前,系統中很多列表和詳情頁所需的資料是可以通過sql join來完成的。而拆分後,資料庫可能是分散式在不同例項和不同的主機上,join將變得非常麻煩。而且基於架構規範,效能,安全性等方面考慮,一般是禁止跨庫join的。那該怎麼辦呢?首先要考慮下垂直分庫的設計問題,如果可以調整,那就優先調整。如果無法調整的情況,下面將結合以往的實際經驗,總結幾種常見的解決思路,並分析其適用場景。

跨庫Join的幾種解決思路

全域性表

所謂全域性表,就是有可能系統中所有模組都可能會依賴到的一些表。比較類似我們理解的“資料字典”。為了避免跨庫join查詢,我們可以將這類表在其他每個資料庫中均儲存一份。同時,這類資料通常也很少發生修改(甚至幾乎不會),所以也不用太擔心“一致性”問題。

欄位冗餘

這是一種典型的反正規化設計,在網際網路行業中比較常見,通常是為了效能來避免join查詢。

舉個電商業務中很簡單的場景:

“訂單表”中儲存“賣家Id”的同時,將賣家的“Name”欄位也冗餘,這樣查詢訂單詳情的時候就不需要再去查詢“賣家使用者表”。

欄位冗餘能帶來便利,是一種“空間換時間”的體現。但其適用場景也比較有限,比較適合依賴欄位較少的情況。最複雜的還是資料一致性問題,這點很難保證,可以藉助資料庫中的觸發器或者在業務程式碼層面去保證。當然,也需要結合實際業務場景來看一致性的要求。就像上面例子,如果賣家修改了Name之後,是否需要在訂單資訊中同步更新呢?

資料同步

A庫中的tab_a表和B庫中tbl_b有關聯,可以定時將指定的表做同步。當然,同步本來會對資料庫帶來一定的影響,需要效能影響和資料時效性中取得一個平衡。這樣來避免複雜的跨庫查詢。

系統層組裝

在系統層面,通過呼叫不同模組的元件或者服務,獲取到資料並進行欄位拼裝。說起來很容易,但實踐起來可真沒有這麼簡單,尤其是資料庫設計上存在問題但又無法輕易調整的時候。具體情況通常會比較複雜。組裝的時候要避免迴圈呼叫服務,迴圈RPC,迴圈查詢資料庫,最好一次性返回所有資訊,在程式碼裡做組裝。

跨庫事務(分散式事務)的問題

http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

水平分庫帶來的問題和解決思路

分散式全域性唯一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)。如果是採用隨機分片,則需要考慮後期的擴容問題,相對會比較麻煩。如果是採用的範圍分片,只需要新增節點就可以自動擴容。

跨分片的排序分頁 

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

跨分片的函式處理

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

跨分片join

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

全域性表

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

ER分片

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

記憶體計算

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

儲存演進

單庫單表

單庫單表是最常見的資料庫設計,例如,有一張使用者(user)表放在資料庫db中,所有的使用者都可以在db庫中的user表中查到。

單庫多表

隨著使用者數量的增加,user表的資料量會越來越大,當資料量達到一定程度的時候對user表的查詢會漸漸的變慢,從而影響整個DB的效能。如果使用mysql, 還有一個更嚴重的問題是,當需要新增一列的時候,mysql會鎖表,期間所有的讀寫操作只能等待。

可以通過某種方式將user進行水平的切分,產生兩個表結構完全一樣的user_0000,user_0001等表,user_0000 + user_0001 + …的資料剛好是一份完整的資料。

多庫多表

隨著資料量增加也許單臺DB的儲存空間不夠,隨著查詢量的增加單臺數據庫伺服器已經沒辦法支撐。這個時候可以再對資料庫進行水平拆分。

總結

總的來說,優先考慮分割槽  -->  當分割槽不能滿足需求時,開始考慮分表,合理的分表對效率的提升會優於分割槽 --> 最後才是分庫。MySQL 使用自增ID主鍵和UUID 作為主鍵的優劣比較詳細過程(從百萬到千萬表記錄測試)

1)單例項或者單節點組:

經過500W、1000W的單機表測試,自增ID相對UUID來說,自增ID主鍵效能高於UUID,磁碟儲存費用比UUID節省一半的錢。所以在單例項上或者單節點組上,使用自增ID作為首選主鍵。

2)分散式架構場景:

 20個節點組下的小型規模的分散式場景,為了快速實現部署,可以採用多花儲存費用、犧牲部分效能而使用UUID主鍵快速部署;

20到200個節點組的中等規模的分散式場景,可以採用自增ID+步長的較快速方案。

200以上節點組的大資料下的分散式場景,可以借鑑類似twitter雪花演算法構造的全域性自增ID作為主鍵。