电竞比分网-中国电竞赛事及体育赛事平台

分享

水平分庫(kù)分表的關(guān)鍵問(wèn)題及解決思路

 quasiceo 2016-11-23

水平分庫(kù)分表的關(guān)鍵問(wèn)題及解決思路

在之前的文章中,我介紹了分庫(kù)分表的幾種表現(xiàn)形式和玩法,也重點(diǎn)介紹了垂直分庫(kù)所帶來(lái)的問(wèn)題和解決方法。本篇中,我們將繼續(xù)聊聊水平分庫(kù)分表的一些技巧。

分片技術(shù)的由來(lái)

關(guān)系型數(shù)據(jù)庫(kù)本身比較容易成為系統(tǒng)性能瓶頸,單機(jī)存儲(chǔ)容量、連接數(shù)、處理能力等都很有限,數(shù)據(jù)庫(kù)本身的“有狀態(tài)性”導(dǎo)致了它并不像Web和應(yīng)用服務(wù)器那么容易擴(kuò)展。在互聯(lián)網(wǎng)行業(yè)海量數(shù)據(jù)和高并發(fā)訪問(wèn)的考驗(yàn)下,聰明的技術(shù)人員提出了分庫(kù)分表技術(shù)(有些地方也稱為Sharding、分片)。同時(shí),流行的分布式系統(tǒng)中間件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的。

分布式全局唯一ID

在很多中小項(xiàng)目中,我們往往直接使用數(shù)據(jù)庫(kù)自增特性來(lái)生成主鍵ID,這樣確實(shí)比較簡(jiǎn)單。而在分庫(kù)分表的環(huán)境中,數(shù)據(jù)分布在不同的分片上,不能再借助數(shù)據(jù)庫(kù)自增長(zhǎng)特性直接生成,否則會(huì)造成不同分片上的數(shù)據(jù)表主鍵會(huì)重復(fù)。簡(jiǎn)單介紹下使用和了解過(guò)的幾種ID生成算法。

 

  1. Twitter的Snowflake(又名“雪花算法”)
  2. UUID/GUID(一般應(yīng)用程序和數(shù)據(jù)庫(kù)均支持)
  3. MongoDB ObjectID(類(lèi)似UUID的方式)
  4. Ticket Server(數(shù)據(jù)庫(kù)生存方式,F(xiàn)lickr采用的就是這種方式)

常見(jiàn)分片規(guī)則和策略

分片字段該如何選擇

在開(kāi)始分片之前,我們首先要確定分片字段(也可稱為“片鍵”)。很多常見(jiàn)的例子和場(chǎng)景中是采用ID或者時(shí)間字段進(jìn)行拆分。這也并不絕對(duì)的,我的建議是結(jié)合實(shí)際業(yè)務(wù),通過(guò)對(duì)系統(tǒng)中執(zhí)行的sql語(yǔ)句進(jìn)行統(tǒng)計(jì)分析,選擇出需要分片的那個(gè)表中最頻繁被使用,或者最重要的字段來(lái)作為分片字段。

常見(jiàn)分片規(guī)則

常見(jiàn)的分片策略有隨機(jī)分片和連續(xù)分片這兩種,如下圖所示:

當(dāng)需要使用分片字段進(jìn)行范圍查找時(shí),連續(xù)分片可以快速定位分片進(jìn)行高效查詢,大多數(shù)情況下可以有效避免跨分片查詢的問(wèn)題。后期如果想對(duì)整個(gè)分片集群擴(kuò)容時(shí),只需要添加節(jié)點(diǎn)即可,無(wú)需對(duì)其他分片的數(shù)據(jù)進(jìn)行遷移。但是,連續(xù)分片也有可能存在數(shù)據(jù)熱點(diǎn)的問(wèn)題,就像圖中按時(shí)間字段分片的例子,有些節(jié)點(diǎn)可能會(huì)被頻繁查詢壓力較大,熱數(shù)據(jù)節(jié)點(diǎn)就成為了整個(gè)集群的瓶頸。而有些節(jié)點(diǎn)可能存的是歷史數(shù)據(jù),很少需要被查詢到。

隨機(jī)分片其實(shí)并不是隨機(jī)的,也遵循一定規(guī)則。通常,我們會(huì)采用Hash取模的方式進(jìn)行分片拆分,所以有些時(shí)候也被稱為離散分片。隨機(jī)分片的數(shù)據(jù)相對(duì)比較均勻,不容易出現(xiàn)熱點(diǎn)和并發(fā)訪問(wèn)的瓶頸。但是,后期分片集群擴(kuò)容起來(lái)需要遷移舊的數(shù)據(jù)。使用一致性Hash算法能夠很大程度的避免這個(gè)問(wèn)題,所以很多中間件的分片集群都會(huì)采用一致性Hash算法。離散分片也很容易面臨跨分片查詢的復(fù)雜問(wèn)題。

數(shù)據(jù)遷移,容量規(guī)劃,擴(kuò)容等問(wèn)題

很少有項(xiàng)目會(huì)在初期就開(kāi)始考慮分片設(shè)計(jì)的,一般都是在業(yè)務(wù)高速發(fā)展面臨性能和存儲(chǔ)的瓶頸時(shí)才會(huì)提前準(zhǔn)備。因此,不可避免的就需要考慮歷史數(shù)據(jù)遷移的問(wèn)題。一般做法就是通過(guò)程序先讀出歷史數(shù)據(jù),然后按照指定的分片規(guī)則再將數(shù)據(jù)寫(xiě)入到各個(gè)分片節(jié)點(diǎn)中。

此外,我們需要根據(jù)當(dāng)前的數(shù)據(jù)量和QPS等進(jìn)行容量規(guī)劃,綜合成本因素,推算出大概需要多少分片(一般建議單個(gè)分片上的單表數(shù)據(jù)量不要超過(guò)1000W)。

如果是采用隨機(jī)分片,則需要考慮后期的擴(kuò)容問(wèn)題,相對(duì)會(huì)比較麻煩。如果是采用的范圍分片,只需要添加節(jié)點(diǎn)就可以自動(dòng)擴(kuò)容。

跨分片技術(shù)問(wèn)題

跨分片的排序分頁(yè)

一般來(lái)講,分頁(yè)時(shí)需要按照指定字段進(jìn)行排序。當(dāng)排序字段就是分片字段的時(shí)候,我們通過(guò)分片規(guī)則可以比較容易定位到指定的分片,而當(dāng)排序字段非分片字段的時(shí)候,情況就會(huì)變得比較復(fù)雜了。為了最終結(jié)果的準(zhǔn)確性,我們需要在不同的分片節(jié)點(diǎn)中將數(shù)據(jù)進(jìn)行排序并返回,并將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序,最后再返回給用戶。如下圖所示:

上面圖中所描述的只是最簡(jiǎn)單的一種情況(取第一頁(yè)數(shù)據(jù)),看起來(lái)對(duì)性能的影響并不大。但是,如果想取出第10頁(yè)數(shù)據(jù),情況又將變得復(fù)雜很多,如下圖所示:

有些讀者可能并不太理解,為什么不能像獲取第一頁(yè)數(shù)據(jù)那樣簡(jiǎn)單處理(排序取出前10條再合并、排序)。其實(shí)并不難理解,因?yàn)楦鞣制?jié)點(diǎn)中的數(shù)據(jù)可能是隨機(jī)的,為了排序的準(zhǔn)確性,必須把所有分片節(jié)點(diǎn)的前N頁(yè)數(shù)據(jù)都排序好后做合并,最后再進(jìn)行整體的排序。很顯然,這樣的操作是比較消耗資源的,用戶越往后翻頁(yè),系統(tǒng)性能將會(huì)越差。

跨分片的函數(shù)處理

在使用Max、Min、Sum、Count之類(lèi)的函數(shù)進(jìn)行統(tǒng)計(jì)和計(jì)算的時(shí)候,需要先在每個(gè)分片數(shù)據(jù)源上執(zhí)行相應(yīng)的函數(shù)處理,然后再將各個(gè)結(jié)果集進(jìn)行二次處理,最終再將處理結(jié)果返回。如下圖所示:

跨分片join

Join是關(guān)系型數(shù)據(jù)庫(kù)中最常用的特性,但是在分片集群中,join也變得非常復(fù)雜。應(yīng)該盡量避免跨分片的join查詢(這種場(chǎng)景,比上面的跨分片分頁(yè)更加復(fù)雜,而且對(duì)性能的影響很大)。通常有以下幾種方式來(lái)避免:

全局表

全局表的概念之前在“垂直分庫(kù)”時(shí)提過(guò)?;舅枷胍恢拢褪前岩恍╊?lèi)似數(shù)據(jù)字典又可能會(huì)產(chǎn)生join查詢的表信息放到各分片中,從而避免跨分片的join。

ER分片

在關(guān)系型數(shù)據(jù)庫(kù)中,表之間往往存在一些關(guān)聯(lián)的關(guān)系。如果我們可以先確定好關(guān)聯(lián)關(guān)系,并將那些存在關(guān)聯(lián)關(guān)系的表記錄存放在同一個(gè)分片上,那么就能很好的避免跨分片join問(wèn)題。在一對(duì)多關(guān)系的情況下,我們通常會(huì)選擇按照數(shù)據(jù)較多的那一方進(jìn)行拆分。如下圖所示:

這樣一來(lái),Data Node1上面的訂單表與訂單詳細(xì)表就可以直接關(guān)聯(lián),進(jìn)行局部的join查詢了,Data Node2上也一樣。基于ER分片的這種方式,能夠有效避免大多數(shù)業(yè)務(wù)場(chǎng)景中的跨分片join問(wèn)題。

內(nèi)存計(jì)算

隨著spark內(nèi)存計(jì)算的興起,理論上來(lái)講,很多跨數(shù)據(jù)源的操作問(wèn)題看起來(lái)似乎都能夠得到解決??梢詫?shù)據(jù)丟給spark集群進(jìn)行內(nèi)存計(jì)算,最后將計(jì)算結(jié)果返回。

跨分片事務(wù)問(wèn)題

跨分片事務(wù)也分布式事務(wù),想要了解分布式事務(wù),就需要了解“XA接口”和“兩階段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在問(wèn)題的,會(huì)導(dǎo)致主從數(shù)據(jù)不一致。直到5.7x版本中才得到修復(fù)。Java應(yīng)用程序可以采用Atomikos框架來(lái)實(shí)現(xiàn)XA事務(wù)(J2EE中JTA)。感興趣的讀者可以自行參考《分布式事務(wù)一致性解決方案》,鏈接地址:

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

我們的系統(tǒng)真的需要分庫(kù)分表嗎

讀完上面內(nèi)容,不禁引起有些讀者的思考,我們的系統(tǒng)是否需要分庫(kù)分表嗎?

其實(shí)這點(diǎn)沒(méi)有明確的判斷標(biāo)準(zhǔn),比較依賴實(shí)際業(yè)務(wù)情況和經(jīng)驗(yàn)判斷。依照筆者個(gè)人的經(jīng)驗(yàn),一般MySQL單表1000W左右的數(shù)據(jù)是沒(méi)有問(wèn)題的(前提是應(yīng)用系統(tǒng)和數(shù)據(jù)庫(kù)等層面設(shè)計(jì)和優(yōu)化的比較好)。當(dāng)然,除了考慮當(dāng)前的數(shù)據(jù)量和性能情況時(shí),作為架構(gòu)師,我們需要提前考慮系統(tǒng)半年到一年左右的業(yè)務(wù)增長(zhǎng)情況,對(duì)數(shù)據(jù)庫(kù)服務(wù)器的QPS、連接數(shù)、容量等做合理評(píng)估和規(guī)劃,并提前做好相應(yīng)的準(zhǔn)備工作。如果單機(jī)無(wú)法滿足,且很難再?gòu)钠渌矫鎯?yōu)化,那么說(shuō)明是需要考慮分片的。這種情況可以先去掉數(shù)據(jù)庫(kù)中自增ID,為分片和后面的數(shù)據(jù)遷移工作提前做準(zhǔn)備。

很多人覺(jué)得“分庫(kù)分表”是宜早不宜遲,應(yīng)該盡早進(jìn)行,因?yàn)閾?dān)心越往后公司業(yè)務(wù)發(fā)展越快、系統(tǒng)越來(lái)越復(fù)雜、系統(tǒng)重構(gòu)和擴(kuò)展越困難…這種話聽(tīng)起來(lái)是有那么一點(diǎn)道理,但我的觀點(diǎn)恰好相反,對(duì)于關(guān)系型數(shù)據(jù)庫(kù)來(lái)講,我認(rèn)為“能不分片就別分片”,除非是系統(tǒng)真正需要,因?yàn)閿?shù)據(jù)庫(kù)分片并非低成本或者免費(fèi)的。

這里筆者推薦一個(gè)比較靠譜的過(guò)渡技術(shù)–“表分區(qū)”。主流的關(guān)系型數(shù)據(jù)庫(kù)中基本都支持。不同的分區(qū)在邏輯上仍是一張表,但是物理上卻是分開(kāi)的,能在一定程度上提高查詢性能,而且對(duì)應(yīng)用程序透明,無(wú)需修改任何代碼。筆者曾經(jīng)負(fù)責(zé)優(yōu)化過(guò)一個(gè)系統(tǒng),主業(yè)務(wù)表有大約8000W左右的數(shù)據(jù),考慮到成本問(wèn)題,當(dāng)時(shí)就是采用“表分區(qū)”來(lái)做的,效果比較明顯,且系統(tǒng)運(yùn)行的很穩(wěn)定。

小結(jié)

最后,有很多讀者都想了解當(dāng)前社區(qū)中有沒(méi)有開(kāi)源免費(fèi)的分庫(kù)分表解決方案,畢竟站在巨人的肩膀上能省力很多。當(dāng)前主要有兩類(lèi)解決方案:

  1. 基于應(yīng)用程序?qū)用娴腄DAL(分布式數(shù)據(jù)庫(kù)訪問(wèn)層) 

    比較典型的就是淘寶半開(kāi)源的TDDL,當(dāng)當(dāng)網(wǎng)開(kāi)源的Sharding-JDBC等。分布式數(shù)據(jù)訪問(wèn)層無(wú)需硬件投入,技術(shù)能力較強(qiáng)的大公司通常會(huì)選擇自研或參照開(kāi)源框架進(jìn)行二次開(kāi)發(fā)和定制。對(duì)應(yīng)用程序的侵入性一般較大,會(huì)增加技術(shù)成本和復(fù)雜度。通常僅支持特定編程語(yǔ)言平臺(tái)(Java平臺(tái)的居多),或者僅支持特定的數(shù)據(jù)庫(kù)和特定數(shù)據(jù)訪問(wèn)框架技術(shù)(一般支持MySQL數(shù)據(jù)庫(kù),JDBC、MyBatis、Hibernate等框架技術(shù))。

  2. 數(shù)據(jù)庫(kù)中間件,比較典型的像mycat(在阿里開(kāi)源的cobar基礎(chǔ)上做了很多優(yōu)化和改進(jìn),屬于后起之秀,也支持很多新特性),基于Go語(yǔ)言實(shí)現(xiàn)kingSharding,比較老牌的Atlas(由360開(kāi)源)等。這些中間件在互聯(lián)網(wǎng)企業(yè)中大量被使用。另外,MySQL 5.x企業(yè)版中官方提供的Fabric組件也號(hào)稱支持分片技術(shù),不過(guò)國(guó)內(nèi)使用的企業(yè)較少。 

    中間件也可以稱為“透明網(wǎng)關(guān)”,大名鼎鼎的mysql_proxy大概是該領(lǐng)域的鼻祖(由MySQL官方提供,僅限于實(shí)現(xiàn)“讀寫(xiě)分離”)。中間件一般實(shí)現(xiàn)了特定數(shù)據(jù)庫(kù)的網(wǎng)絡(luò)通信協(xié)議,模擬一個(gè)真實(shí)的數(shù)據(jù)庫(kù)服務(wù),屏蔽了后端真實(shí)的Server,應(yīng)用程序通常直接連接中間件即可。而在執(zhí)行SQL操作時(shí),中間件會(huì)按照預(yù)先定義分片規(guī)則,對(duì)SQL語(yǔ)句進(jìn)行解析、路由,并對(duì)結(jié)果集做二次計(jì)算再最終返回。引入數(shù)據(jù)庫(kù)中間件的技術(shù)成本更低,對(duì)應(yīng)用程序來(lái)講侵入性幾乎沒(méi)有,可以滿足大部分的業(yè)務(wù)。增加了額外的硬件投入和運(yùn)維成本,同時(shí),中間件自身也存在性能瓶頸和單點(diǎn)故障問(wèn)題,需要能夠保證中間件自身的高可用、可擴(kuò)展。

總之,不管是使用分布式數(shù)據(jù)訪問(wèn)層還是數(shù)據(jù)庫(kù)中間件,都會(huì)帶來(lái)一定的成本和復(fù)雜度,也會(huì)有一定的性能影響。所以,還需讀者根據(jù)實(shí)際情況和業(yè)務(wù)發(fā)展需要慎重考慮和選擇。

作者介紹

丁浪,技術(shù)架構(gòu)師。關(guān)注高并發(fā)、高可用的架構(gòu)設(shè)計(jì),對(duì)系統(tǒng)服務(wù)化、分庫(kù)分表、性能調(diào)優(yōu)等方面有深入研究和豐富實(shí)踐經(jīng)驗(yàn)。熱衷于技術(shù)研究和分享。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多