在之前的文章中,我介紹了分庫分表的幾種表現形式和玩法桑涎,也重點介紹了垂直分庫所帶來的問題和解決方法彬向。本篇中,我們將聊聊水平分庫分表的一些技巧攻冷。
01
分片技術的由來
關系型數據庫本身比較容易成為系統(tǒng)性能瓶頸娃胆,單機存儲容量、連接數等曼、處理能力等都很有限里烦,數據庫本身的“有狀態(tài)性”導致了它并不像Web和應用服務器那么容易擴展。在互聯(lián)網行業(yè)海量數據和高并發(fā)訪問的考驗下禁谦,聰明的技術人員提出了分庫分表技術(有些地方也稱為Sharding胁黑、分片)。同時州泊,流行的分布式系統(tǒng)中間件(例如MongoDB丧蘸、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的遥皂。
02
分布式全局唯一ID
在很多中小項目中力喷,我們往往直接使用數據庫自增特性來生成主鍵ID,這樣確實比較簡單演训。而在分庫分表的環(huán)境中弟孟,數據分布在不同的分片上,不能再借助數據庫自增長特性直接生成仇祭,否則會造成不同分片上的數據表主鍵會重復披蕉。簡單介紹下使用和了解過的幾種ID生成算法。
1. Twitter的Snowflake(又名“雪花算法”)
2. UUID/GUID(一般應用程序和數據庫均支持)
3. MongoDB ObjectID(類似UUID的方式)
4. Ticket Server(數據庫生存方式乌奇,Flickr采用的就是這種方式)?
03
常見分片規(guī)則和策略
分片字段該如何選擇
在開始分片之前没讲,我們首先要確定分片字段(也可稱為“片鍵”)。很多常見的例子和場景中是采用ID或者時間字段進行拆分礁苗。這也并不絕對的爬凑,我的建議是結合實際業(yè)務,通過對系統(tǒng)中執(zhí)行的sql語句進行統(tǒng)計分析试伙,選擇出需要分片的那個表中最頻繁被使用嘁信,或者最重要的字段來作為分片字段于样。
常見分片規(guī)則
常見的分片策略有隨機分片和連續(xù)分片這兩種,如下圖所示:
當需要使用分片字段進行范圍查找時潘靖,連續(xù)分片可以快速定位分片進行高效查詢穿剖,大多數情況下可以有效避免跨分片查詢的問題。后期如果想對整個分片集群擴容時卦溢,只需要添加節(jié)點即可糊余,無需對其他分片的數據進行遷移。但是单寂,連續(xù)分片也有可能存在數據熱點的問題贬芥,就像圖中按時間字段分片的例子,有些節(jié)點可能會被頻繁查詢壓力較大宣决,熱數據節(jié)點就成為了整個集群的瓶頸蘸劈。而有些節(jié)點可能存的是歷史數據,很少需要被查詢到尊沸。
隨機分片其實并不是隨機的威沫,也遵循一定規(guī)則。通常洼专,我們會采用Hash取模的方式進行分片拆分壹甥,所以有些時候也被稱為離散分片。隨機分片的數據相對比較均勻壶熏,不容易出現熱點和并發(fā)訪問的瓶頸。但是浦译,后期分片集群擴容起來需要遷移舊的數據棒假。使用一致性Hash算法能夠很大程度的避免這個問題,所以很多中間件的分片集群都會采用一致性Hash算法精盅。離散分片也很容易面臨跨分片查詢的復雜問題帽哑。
數據遷移,容量規(guī)劃叹俏,擴容等問題
很少有項目會在初期就開始考慮分片設計的妻枕,一般都是在業(yè)務高速發(fā)展面臨性能和存儲的瓶頸時才會提前準備。因此粘驰,不可避免的就需要考慮歷史數據遷移的問題屡谐。一般做法就是通過程序先讀出歷史數據,然后按照指定的分片規(guī)則再將數據寫入到各個分片節(jié)點中蝌数。
此外愕掏,我們需要根據當前的數據量和QPS等進行容量規(guī)劃,綜合成本因素顶伞,推算出大概需要多少分片(一般建議單個分片上的單表數據量不要超過1000W)饵撑。
如果是采用隨機分片剑梳,則需要考慮后期的擴容問題,相對會比較麻煩滑潘。如果是采用的范圍分片垢乙,只需要添加節(jié)點就可以自動擴容。
04
跨分片技術問題
跨分片的排序分頁
一般來講语卤,分頁時需要按照指定字段進行排序追逮。當排序字段就是分片字段的時候,我們通過分片規(guī)則可以比較容易定位到指定的分片粱侣,而當排序字段非分片字段的時候羊壹,情況就會變得比較復雜了。為了最終結果的準確性齐婴,我們需要在不同的分片節(jié)點中將數據進行排序并返回油猫,并將不同分片返回的結果集進行匯總和再次排序,最后再返回給用戶柠偶。如下圖所示:
上面圖中所描述的只是最簡單的一種情況(取第一頁數據)情妖,看起來對性能的影響并不大。但是诱担,如果想取出第10頁數據毡证,情況又將變得復雜很多,如下圖所示:
有些讀者可能并不太理解蔫仙,為什么不能像獲取第一頁數據那樣簡單處理(排序取出前10條再合并料睛、排序)。其實并不難理解摇邦,因為各分片節(jié)點中的數據可能是隨機的苟径,為了排序的準確性元咙,必須把所有分片節(jié)點的前N頁數據都排序好后做合并既们,最后再進行整體的排序占调。很顯然,這樣的操作是比較消耗資源的丑慎,用戶越往后翻頁喜喂,系統(tǒng)性能將會越差。
跨分片的函數處理
在使用Max竿裂、Min玉吁、Sum、Count之類的函數進行統(tǒng)計和計算的時候腻异,需要先在每個分片數據源上執(zhí)行相應的函數處理诈茧,然后再將各個結果集進行二次處理,最終再將處理結果返回捂掰。如下圖所示:
跨分片join
Join是關系型數據庫中最常用的特性敢会,但是在分片集群中曾沈,join也變得非常復雜。應該盡量避免跨分片的join查詢(這種場景鸥昏,比上面的跨分片分頁更加復雜塞俱,而且對性能的影響很大)。通常有以下幾種方式來避免:
全局表
全局表的概念之前在“垂直分庫”時提過吏垮≌涎模基本思想一致,就是把一些類似數據字典又可能會產生join查詢的表信息放到各分片中膳汪,從而避免跨分片的join唯蝶。
ER分片
在關系型數據庫中,表之間往往存在一些關聯(lián)的關系遗嗽。如果我們可以先確定好關聯(lián)關系粘我,并將那些存在關聯(lián)關系的表記錄存放在同一個分片上,那么就能很好的避免跨分片join問題痹换。在一對多關系的情況下征字,我們通常會選擇按照數據較多的那一方進行拆分。如下圖所示:
這樣一來娇豫,Data Node1上面的訂單表與訂單詳細表就可以直接關聯(lián)匙姜,進行局部的join查詢了,Data Node2上也一樣冯痢〉粒基于ER分片的這種方式,能夠有效避免大多數業(yè)務場景中的跨分片join問題浦楣。
內存計算
隨著spark內存計算的興起郭计,理論上來講,很多跨數據源的操作問題看起來似乎都能夠得到解決椒振。可以將數據丟給spark集群進行內存計算梧乘,最后將計算結果返回澎迎。
05
跨分片事務問題
跨分片事務也分布式事務,想要了解分布式事務选调,就需要了解“XA接口”和“兩階段提交”夹供。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在問題的仁堪,會導致主從數據不一致哮洽。直到5.7x版本中才得到修復。Java應用程序可以采用Atomikos框架來實現XA事務(J2EE中JTA)弦聂。感興趣的讀者可以自行參考《分布式事務一致性解決方案》鸟辅,鏈接地址:
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency
06
我們的系統(tǒng)真的需要分庫分表嗎
讀完上面內容氛什,不禁引起有些讀者的思考,我們的系統(tǒng)是否需要分庫分表嗎匪凉?
其實這點沒有明確的判斷標準枪眉,比較依賴實際業(yè)務情況和經驗判斷。依照筆者個人的經驗再层,一般MySQL單表1000W左右的數據是沒有問題的(前提是應用系統(tǒng)和數據庫等層面設計和優(yōu)化的比較好)贸铜。當然,除了考慮當前的數據量和性能情況時聂受,作為架構師蒿秦,我們需要提前考慮系統(tǒng)半年到一年左右的業(yè)務增長情況,對數據庫服務器的QPS蛋济、連接數棍鳖、容量等做合理評估和規(guī)劃,并提前做好相應的準備工作瘫俊。如果單機無法滿足鹊杖,且很難再從其他方面優(yōu)化,那么說明是需要考慮分片的扛芽。這種情況可以先去掉數據庫中自增ID骂蓖,為分片和后面的數據遷移工作提前做準備。
很多人覺得“分庫分表”是宜早不宜遲川尖,應該盡早進行登下,因為擔心越往后公司業(yè)務發(fā)展越快、系統(tǒng)越來越復雜叮喳、系統(tǒng)重構和擴展越困難…這種話聽起來是有那么一點道理被芳,但我的觀點恰好相反,對于關系型數據庫來講馍悟,我認為“能不分片就別分片”畔濒,除非是系統(tǒng)真正需要,因為數據庫分片并非低成本或者免費的锣咒。
這里筆者推薦一個比較靠譜的過渡技術–“表分區(qū)”侵状。主流的關系型數據庫中基本都支持。不同的分區(qū)在邏輯上仍是一張表毅整,但是物理上卻是分開的趣兄,能在一定程度上提高查詢性能,而且對應用程序透明悼嫉,無需修改任何代碼艇潭。筆者曾經負責優(yōu)化過一個系統(tǒng),主業(yè)務表有大約8000W左右的數據,考慮到成本問題蹋凝,當時就是采用“表分區(qū)”來做的鲁纠,效果比較明顯,且系統(tǒng)運行的很穩(wěn)定仙粱。
07
小結
最后房交,有很多讀者都想了解當前社區(qū)中有沒有開源免費的分庫分表解決方案,畢竟站在巨人的肩膀上能省力很多伐割。當前主要有兩類解決方案:
1. 基于應用程序層面的DDAL(分布式數據庫訪問層)?
比較典型的就是淘寶半開源的TDDL候味,當當網開源的Sharding-JDBC等。分布式數據訪問層無需硬件投入隔心,技術能力較強的大公司通常會選擇自研或參照開源框架進行二次開發(fā)和定制白群。對應用程序的侵入性一般較大,會增加技術成本和復雜度硬霍。通常僅支持特定編程語言平臺(Java平臺的居多)帜慢,或者僅支持特定的數據庫和特定數據訪問框架技術(一般支持MySQL數據庫,JDBC唯卖、MyBatis粱玲、Hibernate等框架技術)。
2.?數據庫中間件拜轨,比較典型的像mycat(在阿里開源的cobar基礎上做了很多優(yōu)化和改進抽减,屬于后起之秀,也支持很多新特性)橄碾,基于Go語言實現kingSharding卵沉,比較老牌的Atlas(由360開源)等。這些中間件在互聯(lián)網企業(yè)中大量被使用法牲。另外史汗,MySQL 5.x企業(yè)版中官方提供的Fabric組件也號稱支持分片技術,不過國內使用的企業(yè)較少拒垃。
中間件也可以稱為“透明網關”停撞,大名鼎鼎的mysql_proxy大概是該領域的鼻祖(由MySQL官方提供,僅限于實現“讀寫分離”)悼瓮。中間件一般實現了特定數據庫的網絡通信協(xié)議戈毒,模擬一個真實的數據庫服務,屏蔽了后端真實的Server谤牡,應用程序通常直接連接中間件即可。而在執(zhí)行SQL操作時姥宝,中間件會按照預先定義分片規(guī)則翅萤,對SQL語句進行解析、路由,并對結果集做二次計算再最終返回套么。引入數據庫中間件的技術成本更低培己,對應用程序來講侵入性幾乎沒有,可以滿足大部分的業(yè)務胚泌。增加了額外的硬件投入和運維成本省咨,同時,中間件自身也存在性能瓶頸和單點故障問題玷室,需要能夠保證中間件自身的高可用零蓉、可擴展。
總之穷缤,不管是使用分布式數據訪問層還是數據庫中間件敌蜂,都會帶來一定的成本和復雜度,也會有一定的性能影響津肛。所以章喉,還需讀者根據實際情況和業(yè)務發(fā)展需要慎重考慮和選擇。
擴展閱讀
Redis 分布式鎖:樂觀鎖的實現身坐,以秒殺系統(tǒng)為例
作者:丁浪
來源:https://www.cnblogs.com/dinglang/p/6084306.html