20191128 數據庫拆分方案

任何脫離業務的架構設計都是耍流氓。

 

數據庫分佈式,其核心內容無非就是數據切分(Sharding),以及切分後對數據的定位、整合工作解決單一數據庫或數據表因數據量過大而導致的性能瓶頸問題

已有的MySQL、ORACLE等成熟數據庫系統基礎上進行的一系列數據操作調度。後者分佈式數據庫則是集數據存儲、管理以及分佈式協調與計算爲一體的數據庫系統。

縱向拆分數據庫(邏輯關係),橫向拆分數據表。

如果需要拆表,如何設定數據記錄的切分規則是最重要考量。一旦確定切分規則,應用對該表的操作原則基本就已確定。根據哪個字段拆分成幾個庫?還是按照號段進行拆表(不涉及到數據遷移,缺點:熱點數據分佈不均勻,網遊合區,老區已經無活躍用戶了)。

假設我們將Customer表根據cus_no字段來切分到4個庫,如果我們所有查詢條件都帶有cus_no字段則可明確定位到相應庫去查詢,但如果我們頻繁用到的查詢條件中不帶cus_no時,將會導致無法定位數據庫,從而需要同時向4個庫發起查詢,最後再合併數據、取最小集返回給應用,導致分庫優勢反而可能成爲你的拖累。

 

隨着數據量的逐步增大,數據庫性能顯著降低,數據庫水平切分相關的架構實踐:

1)如何來實施水平切分;(如何進行水平分表?)

2)水平切分後常見的問題;

3)典型問題的優化思路及實踐;

 

一、關聯查詢問題

數據切分後一大難題就是關聯查詢跨庫關聯查詢的想法最好打消掉,目前沒有好的解決方案。

二、分頁查詢問題

正如前面提到的分庫並行查詢時,若需要分頁查詢則將出現悲劇,每個庫返回的結果集本身是無序的,無法確定應該如何返回數據給用戶。目前像MyCat這類中間件要求分頁查詢時必須帶上ORDER BY字段,好使得多庫查詢結果全部出來後,再在內存中根據排序字段單獨進行排序,最後返回最小結果集。可以想像,當查詢的總結果集過大時,這一排序過程對資源和時間的消耗相當可觀。

三、事務一致性問題

當我們需要更新的內容同時分佈在不同庫時,不可避免會帶來跨庫事務問題,在JavaEE體系下使用XA事務進行協調解決,但XA事務目前也並非完全安全,在最後確認提交這一步若某個庫失敗時,並不能確保所有庫都能成功roolback。目前針對此種情況尚無簡單的方案,需要採用日誌分析、事後補償的方式來解決,以達到互聯網類系統宣稱的“最終一致性”。

四、主鍵避重問題

由於表同時存在於多個數據庫內,主鍵值我們平時常用的自增序列將無用武之地,因此需要單獨設計全局主鍵,以避免跨庫主鍵重複問題

一旦數據庫被切分到多個物理結點上,我們將不能再依賴數據庫自身的主鍵生成機制。一方面,某個分區數據庫自生成的ID無法保證在全局上是唯一的;另一方面,應用程序在插入數據之前需要先獲得ID,以便進行SQL路由.

五、跨節點Join的問題

只要是進行切分,跨節點Join的問題是不可避免的。但是良好的設計和切分卻可以減少此類情況的發生。解決這一問題的普遍做法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求得到關聯數據

六、跨節點的count,order by,group by以及聚合函數問題

這些是一類問題,因爲它們都需要基於全部數據集合進行計算。多數的代理都不會自動處理合並工作。解決方案:與解決跨節點join問題的類似,分別在各個節點上得到結果後在應用程序端進行合併。和join不同的是每個結點的查詢可以並行執行,因此很多時候它的速度要比單一大表快很多。但如果結果集很大,對應用程序內存的消耗是一個問題。

 

分表策略:

1、按照號段劃分表;插入數據的時候,只能插入指定的表。老表與新表負載不均,老表用戶活躍度不高,新表用戶活躍度很高。

2、按照關鍵字段取模分表; 插入數據的時候,需要一個統一id生成器,生成id後,進行取模,然後決定數據存儲到哪張表?避免主鍵重複的問題。

3、拆分表時爲什麼把拆分後的兩張表放到不同的數據庫裏面?

4、在非關鍵業務應用中可以嘗試,原則上能不分表就不分表,在覈心業務上謹慎而行。

 

1、發現某些表數據達到500W+以後查詢統計性能嚴重下降,高峯時段出現了很多SQL阻塞的情況。這種阻塞帶來的災難是滾雪球的,由於越堆越多基本上把數據庫已經拖死,所以我們就面臨數據庫切分的問題。

2、技術選型:既然要分庫分表那數據庫集羣是少不了的。

1)中間件:Mycat(個人建議如果使用中間件的話可以考慮Mycat)。

2)Jar形式的開源工具:例如淘寶的TDDL,以及噹噹開源出來的,Sharding-JDBC等。

3)動態數據源:根據自己的業務來指定數據源來完成不同庫和表的操作。

之所以選擇動態數據源:主要是因爲技術相對簡單,對於業務代碼修改也比較少,可控性較高,減少了加入中間件或者第三方工具所帶來的風險。

 

主鍵生成策略

既然要分庫分表那麼全局唯一主鍵也是我們需要考慮的問題,我所知道的和有使用經驗的有如下幾種技術。

添加流程

 

查詢邏輯

 

分庫分表後能解決我們的性能問題,但是也帶來了很多其他的問題。

 

一些常見的主鍵生成策略。

1)UUID

使用UUID作主鍵是最簡單的方案,但是缺點也是非常明顯的。由於UUID非常的長,除佔用大量存儲空間外,最主要的問題是在索引上,在建立索引和基於索引進行查詢時都存在性能問題

2)結合數據庫維護一個Sequence表

此方案的思路也很簡單,在數據庫中建立一個Sequence表,表的結構類似於:

CREATE TABLE `SEQUENCE` (

`table_name` varchar(18) NOT NULL,

`nextid` bigint(20) NOT NULL,

PRIMARY KEY (`table_name`)

) ENGINE=InnoDB

每當需要爲某個表的新紀錄生成ID時就從Sequence表中取出對應表的nextid,並將nextid的值加1後更新到數據庫中以備下次使用。此方案也較簡單,但缺點同樣明顯:由於所有插入任何都需要訪問該表,該表很容易成爲系統性能瓶頸,同時它也存在單點問題,一旦該表數據庫失效,整個應用程序將無法工作。有人提出使用Master-Slave進行主從同步,但這也只能解決單點問題,並不能解決讀寫比爲1:1的訪問壓力問題。

3)Twitter的分佈式自增ID算法Snowflake

在分佈式系統中,需要生成全局UID的場合還是比較多的,twitter的snowflake解決了這種需求,實現也還是很簡單的,除去配置信息,核心代碼就是毫秒級時間41位 機器ID 10位 毫秒內序列12位。

* 10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000

在上面的字符串中,第一位爲未使用(實際上也可作爲long的符號位),接下來的41位爲毫秒級時間,然後5位datacenter標識位,5位機器ID(並不算標識符,實際是爲線程標識),然後12位該毫秒內的當前毫秒內的計數,加起來剛好64位,爲一個Long型。

這樣的好處是,整體上按照時間自增排序,並且整個分佈式系統內不會產生ID碰撞(由datacenter和機器ID作區分),並且效率較高,經測試,snowflake每秒能夠產生26萬ID左右,完全滿足需要。

 

數據庫設計上,一般來說在業務初期,單庫單表就能夠搞定這個需求

當數據量越來越大時,需要對數據庫進行水平切分,常見的水平切分算法有“範圍法”和“哈希法”

範圍法:以用戶中心的主鍵uid爲劃分依據,將數據水平切分到兩個數據庫實例上去;

範圍法的優點是:

1)切分策略簡單,根據uid,按照範圍,user-center很快能夠定位到數據在哪個庫上;

2)擴容簡單,如果容量不夠,只要增加user-db3即可;

 

範圍法的不足是:

1)uid必須要滿足遞增的特性;

2)數據量不均,新增的user-db3,在初期的數據會比較少;

3)請求量不均,一般來說,新註冊的用戶活躍度會比較高,故user-db2往往會比user-db1負載要高導致服務器利用率不平衡

 

只要建立非uid屬性(login_name / phone / email )到uid的映射關係,就能解決問題。

 

如果此時前臺業務和後臺業務公用一批服務和一個數據庫,有可能導致,由於後臺的“少數幾個請求”的“批量查詢”的“低效”訪問,導致數據庫的cpu偶爾瞬時100%,影響前臺正常用戶的訪問.

對於這一類業務,應該採用“前臺與後臺分離”的架構方案.

 

https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651960212&idx=1&sn=ab4c52ab0309f7380f7e0207fa357128&pass_ticket=G8v3RrpK9Is7NJZH0fOShUfY8lp5oz9un8K5L24LeGGVtiBTXkBMc9UKkTMdQeDS

 

 

發佈了303 篇原創文章 · 獲贊 104 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章