数据库分片前的准备
為了解決前面的問題,我們要對(duì)庫中的表進(jìn)行水平拆分,也就是要對(duì)數(shù)據(jù)庫進(jìn)行分片處理,我們通常所說的分庫分表呢,大多數(shù)情況下指的就是這種方式,這和MSYQL的分區(qū)表呢,也有些類似,不同的是MYSQL的分區(qū)表是在同一個(gè)節(jié)點(diǎn)上的,同一個(gè)數(shù)據(jù)庫中建立的,而數(shù)據(jù)庫分片后呢,通常是存在不同的物理節(jié)點(diǎn)上,數(shù)據(jù)庫的分片呢,不像我們前面講的分庫分表一樣容易實(shí)現(xiàn),原來獨(dú)立的數(shù)據(jù)庫進(jìn)行分片,必須要考慮很多問題,通常來說,如果不是萬不得已,真的不建議大家,對(duì)數(shù)據(jù)庫進(jìn)行分片,我們見過很多的項(xiàng)目,業(yè)務(wù)剛剛上線的時(shí)候呢,數(shù)據(jù)庫并發(fā)和負(fù)載,遠(yuǎn)遠(yuǎn)沒有達(dá)到限制時(shí),就對(duì)數(shù)據(jù)庫進(jìn)行分片處理,其實(shí)我覺得這樣做完全是沒有必要的,我們應(yīng)該首先考慮,是否能夠通過性能調(diào)優(yōu),或者更好的應(yīng)用和數(shù)據(jù)庫設(shè)計(jì),來推遲分片,還是那句話,對(duì)數(shù)據(jù)庫進(jìn)行分片呢,并不是那么容易,并且分片后,數(shù)據(jù)庫還會(huì)變的難以維護(hù),如果我們到了真的不得不分片的時(shí)候呢,那也只能對(duì)數(shù)據(jù)庫進(jìn)行分片了,下面這個(gè)圖呢,就是對(duì)訂單表進(jìn)行分片的一個(gè)結(jié)果,在單獨(dú)的節(jié)點(diǎn)中,我們只有一個(gè)訂單表相關(guān)的數(shù)據(jù)庫,結(jié)果分片后呢,我們會(huì)形成多個(gè)相同結(jié)構(gòu)的訂單數(shù)據(jù)庫,當(dāng)然呢,多個(gè)數(shù)據(jù)庫呢,可能會(huì)分布在不同的節(jié)點(diǎn)中,那么下面我們就來看看,能不能對(duì)一個(gè)獨(dú)立的數(shù)據(jù)庫進(jìn)行分片,應(yīng)該提前考慮些什么樣的問題
在對(duì)數(shù)據(jù)庫進(jìn)行分片前,非常重要的一項(xiàng)工作,就是選擇分區(qū)鍵,分區(qū)鍵決定了我們?nèi)绾螌?duì)數(shù)據(jù)庫進(jìn)行分片,以及分片后如何查詢數(shù)據(jù),分區(qū)鍵選擇的是否合適呢,直接決定了分區(qū)后的數(shù)據(jù)庫性能,對(duì)于分區(qū)鍵的選擇,我們應(yīng)該做到以下幾點(diǎn),首先選擇分區(qū)鍵時(shí),要盡可能的避免跨分區(qū)的查詢數(shù)據(jù)的這種可能,如果完全的不做跨分區(qū),查詢數(shù)據(jù)呢,幾乎也是不可能的,因?yàn)槲覀冇泻芏嘟y(tǒng)計(jì)分析類的查詢,都需要跨分區(qū)來處理,和匯總數(shù)據(jù),但是對(duì)于OLTP的應(yīng)用,我們要盡可能減少跨分區(qū)的產(chǎn)生,因?yàn)榭绶制樵儠r(shí),應(yīng)用不得不對(duì)多個(gè)分片進(jìn)行查詢,然后再把查詢的結(jié)果,合并在一起,這通常來說,效率會(huì)很低,甚至比未分片的性能還要差,比如對(duì)于一個(gè)博客類的應(yīng)用來說,如果我們按博客文章的ID,哈希值來分片,就可能不太合適,因?yàn)槲覀冊陲@示每個(gè)人的博客時(shí),都需要跨所有的分區(qū),來進(jìn)行查找,才能得到一個(gè)人所有的文章,而如果我們選擇userId來做分片呢,則能保證同一個(gè)人的博客,全在一個(gè)分片中,如果想要顯示一個(gè)人的所有博客,則只要在這一個(gè)分片中進(jìn)行查詢就可以了,所以在這種情況下,使用userIde和使用文章的id呢,來分片就顯得更為合適一些,其次分區(qū)鍵要盡可能的可以保證各個(gè)分片中的數(shù)據(jù)量,是平均的,我們對(duì)數(shù)據(jù)庫進(jìn)行分片的目的呢,是為了減少主庫的寫負(fù)載,如果分片后,大部分的寫呢,還是集中在某一個(gè)分片中,這樣我們就失去了分片的意義,例如對(duì)于一個(gè)訂單數(shù)據(jù)庫,如果我們使用下單用戶的ID來做分區(qū)鍵,并且在范圍來進(jìn)行分片的話,那么就要看看選擇的ID范圍是否合理,是不是大部分活用的用戶分到一個(gè)片中,當(dāng)然了,如果我們可以保證在分片后,每個(gè)查詢都包括這個(gè)分區(qū)鍵的話,使用哈希函數(shù)來進(jìn)行分區(qū),可能是一種最好的使用分區(qū)的方式了
在一個(gè)應(yīng)用中,我們需要分片的表總是少數(shù)的,換句話說,不可能一個(gè)應(yīng)用中的所有表,都被頻繁的寫入數(shù)據(jù),分片后,那些不要分片的表,如何存儲(chǔ),總的來說呢,也有兩種方式,第一種就是在每個(gè)分片中呢,存儲(chǔ)一份相同的數(shù)據(jù),這種方法呢,通常用于表本身數(shù)據(jù)量不大,而且不會(huì)經(jīng)常被更新的自檢類表,同時(shí)這些表又經(jīng)常需要和分區(qū)表,一起關(guān)聯(lián)查詢,在一個(gè)分區(qū)中存儲(chǔ)一份冗余的數(shù)據(jù)呢,可以更好地提高數(shù)據(jù)庫的查詢效率,如果使用這種方式,對(duì)于維護(hù)每個(gè)分片中,相同表的一致性,就顯得非常重要了,我們一般可以使用多寫的方式,來維護(hù)這些表的數(shù)據(jù),也就是說,利用在對(duì)這些表更新時(shí),會(huì)同時(shí)更新所有分片中相同的表,但是由于這種更新呢,一般不會(huì)放在一個(gè)事務(wù)中,所以我們要定期的對(duì)這些表的數(shù)據(jù)呢,進(jìn)行檢查,以防止由于這些表,數(shù)據(jù)不一致而造成業(yè)務(wù)邏輯上的問題,而另一種方式呢,就是把所有不需要分片的數(shù)據(jù),存儲(chǔ)在一個(gè)公共的位置,這種方法的好處呢,就是在集群中不存在數(shù)據(jù)冗余的問題,應(yīng)用也不用維護(hù)多份相同的數(shù)據(jù),如果分片的表要和這些表進(jìn)行關(guān)聯(lián)查詢的話,就只能由程序分別查詢后,再進(jìn)行操作了,所以這種方法在查詢效率上,比上一種方法要差一些
這種方法在查詢效率上,比上一種方法要差一些,接下來我們還要考慮如何在節(jié)點(diǎn)上部署分片的問題,并不是說一個(gè)節(jié)點(diǎn)上,能部署一個(gè)數(shù)據(jù)庫的分片,對(duì)于分片如何部署到節(jié)點(diǎn)上,有很多種可能可以選擇的方式,其中一些最常用的方法呢,就是下面這些,每個(gè)分片使用單一一個(gè)數(shù)據(jù)庫,并且數(shù)據(jù)庫名也相同,一般來說,每個(gè)分片的數(shù)據(jù)庫結(jié)構(gòu)呢都要和原來單一實(shí)例數(shù)據(jù)庫的結(jié)構(gòu)相同,所以在每個(gè)分片上部署一個(gè)分片數(shù)據(jù)庫,可以保證數(shù)據(jù)庫,表名,和原來單一數(shù)據(jù)庫,是完全一致的,而另一種做法呢,是將多個(gè)分片,存儲(chǔ)在一個(gè)數(shù)據(jù)庫中,由于數(shù)據(jù)庫中的表是不能重名的,所以這時(shí)候呢,就需要在每個(gè)分片表的后面,加上一個(gè)分片號(hào)來作為后綴,以區(qū)分不同的分片表,這種情況下呢,一個(gè)數(shù)據(jù)庫中呢,可以包含多個(gè)分片表,當(dāng)然了,除了對(duì)一個(gè)分片表存儲(chǔ)在一個(gè)數(shù)據(jù)庫中外,我們還可以在一個(gè)節(jié)點(diǎn)中部署多個(gè)數(shù)據(jù)庫,由于一個(gè)數(shù)據(jù)庫節(jié)點(diǎn)的數(shù)據(jù)庫名呢,也是不能夠重復(fù)的,所以我們要對(duì)數(shù)據(jù)庫名來進(jìn)行編號(hào)了,并且在每個(gè)數(shù)據(jù)庫中,包含一套或多套分片表,當(dāng)然了,如果要在一個(gè)數(shù)據(jù)庫中包含多個(gè)分片表的話,我們同時(shí)還要對(duì)分片表再進(jìn)行編號(hào)處理,那么除了以下幾個(gè)方法外呢,我們還可以使用一個(gè)節(jié)點(diǎn)多實(shí)例的方法,來存儲(chǔ)這個(gè)分片,無論使用哪種方法呢,都要考慮我們的應(yīng)用如何修改才能適應(yīng)我們的分片存儲(chǔ)方式,一定還要避免把數(shù)據(jù)寫入錯(cuò)誤的一種分片,或者去錯(cuò)誤分片中去查詢數(shù)據(jù)的情況出現(xiàn),這也是對(duì)數(shù)據(jù)庫進(jìn)行分片處理中,復(fù)雜的一個(gè)部分
在節(jié)點(diǎn)中如何存儲(chǔ)分片后,我們還要決定如何在分片中,分配數(shù)據(jù),數(shù)據(jù)在各個(gè)分片中的分布,對(duì)數(shù)據(jù)庫的性能又很大的影響,首先我們可以盡量平均的在各個(gè)分片中來分片數(shù)據(jù),這個(gè)平均呢不單單指數(shù)據(jù)量的平均,還包括訪問量上的平均,因?yàn)槲覀儼l(fā)現(xiàn),在一些情況下,熱點(diǎn)數(shù)據(jù)呢,很有可能集中在某一個(gè)或者某幾個(gè)分片中,這時(shí)這幾個(gè)分片的負(fù)載呢,都要比其他分片的負(fù)載要高的多,當(dāng)數(shù)據(jù)庫查詢這幾個(gè)負(fù)載高的分片時(shí)呢,性能就會(huì)大幅下降,所以選擇如何在分片中,分配數(shù)據(jù),就一定要結(jié)合我們的業(yè)務(wù)情況來做分析,然后再做決定,一般來說,在分片中分配數(shù)據(jù)呢,可以使用一下幾種方式,第一種是使用分區(qū)鍵的哈希值,然后和計(jì)劃分區(qū)的數(shù)量來取模的方式,來在不同的分片中呢,分配數(shù)據(jù),比如我們要把分區(qū)鍵為101的數(shù)據(jù),分配到10個(gè)分片鍵中的一個(gè),那么就可以通過101對(duì)分片的模,得到分片號(hào)為1的分片,之所以要對(duì)分區(qū)鍵進(jìn)行哈希分片呢,然后再取模呢,因?yàn)榉謪^(qū)鍵呢,并不總是數(shù)值型的數(shù)據(jù),所以我們要利用合適的哈希函數(shù),來吧其他類型的數(shù)據(jù),轉(zhuǎn)換為數(shù)值類型的數(shù)據(jù),然后再取模,當(dāng)然了,如果和上面的例子一樣,本身就是利用自增ID的主鍵來當(dāng)做分區(qū)鍵的,那樣的話呢,就可以使用分區(qū)鍵取模的方式呢,來在各個(gè)分區(qū)中,分配數(shù)據(jù)了,使用這個(gè)方式的好處呢,就是可以相對(duì)平均的在各個(gè)分片中分配數(shù)據(jù),而第二種方式呢,就是利用分區(qū)鍵的范圍,來在各個(gè)分片中分配數(shù)據(jù),還以上面的例子來說,如果我們有10個(gè)分片,第一個(gè)分片中包含1到100的數(shù)據(jù),第二個(gè)分片中呢,包含了101到200的數(shù)據(jù),以此類推,如果我們要保存101的數(shù)據(jù)呢,如果使用這種方式的話呢,這個(gè)數(shù)據(jù)就應(yīng)該存儲(chǔ)在第二個(gè)應(yīng)用中,這種分配分片數(shù)據(jù)的方式呢,常用于分區(qū)鍵為日期類型的數(shù)據(jù),或者數(shù)值類型的數(shù)據(jù)文件,其優(yōu)點(diǎn)呢就是我們可以很清楚的知道,數(shù)據(jù)會(huì)分配到哪個(gè)分區(qū)片中,但是那種方式很容易產(chǎn)生數(shù)據(jù)分配不平均,以及數(shù)據(jù)訪問量的情況,之前我就遇到過這樣的情況,那個(gè)時(shí)候是對(duì)學(xué)生做題的題庫數(shù)據(jù)庫,進(jìn)行分片,當(dāng)時(shí)是選擇學(xué)生課題ID的范圍,來進(jìn)行分片的,但是這個(gè)數(shù)據(jù)庫運(yùn)行了一段時(shí)間之后呢,我們就會(huì)發(fā)現(xiàn)了,選擇某一課程的學(xué)生呢,遠(yuǎn)遠(yuǎn)多于其他課程,所以這門課程所在的數(shù)據(jù)庫壓力呢,也就原高其他分片,所以一旦對(duì)這個(gè)數(shù)據(jù)庫進(jìn)行讀寫操作的時(shí)候呢,應(yīng)用的性能就會(huì)急劇下降,以至于我們后面不得不對(duì)這個(gè)數(shù)據(jù)進(jìn)行分片處理,那么前面兩種分片數(shù)據(jù)的分片方式呢,我們都無法靈活的堆哪些數(shù)據(jù),那個(gè)分片中進(jìn)行控制,而使用第三種方式,可以方便的對(duì)數(shù)據(jù)庫的分片位置呢,來進(jìn)行控制,這種方式就是要建立一個(gè)分區(qū)鍵和對(duì)應(yīng)關(guān)系表,在這張表中呢,可以對(duì)什么數(shù)據(jù),存儲(chǔ)在哪些分片中,進(jìn)行配置,在進(jìn)行數(shù)據(jù)存儲(chǔ)之前呢,先通過幾張表,來獲取具體要存儲(chǔ)的分片中的信息,然后再把數(shù)據(jù)存儲(chǔ)到分片中,大家可能會(huì)發(fā)現(xiàn),使用這種方式時(shí)呢,對(duì)分區(qū)鍵和分區(qū)的對(duì)應(yīng)關(guān)系表,可能會(huì)產(chǎn)生很大的讀壓力,所以我們可以使用緩存這種方式呢,來把這張表進(jìn)行緩存,這一點(diǎn)大家一定要注意,否則這張表可能就會(huì)成為系統(tǒng)的瓶頸
當(dāng)我們把一個(gè)單節(jié)點(diǎn)數(shù)據(jù)庫,轉(zhuǎn)換為多節(jié)點(diǎn)分片數(shù)據(jù)庫,所面臨的最大一個(gè)問題,可能就是分片后如何維護(hù)一個(gè)表中唯一ID的一個(gè)問題,在當(dāng)節(jié)點(diǎn)環(huán)境中呢,我們可以使用mysql的auto_increment屬性,來獲取表中的唯一ID,但是分片后,如果我還使用這種方式,就會(huì)造成每個(gè)分片中的ID相同的情況,所以為了解決這個(gè)問題,我們就不得不更改或者唯一ID的方式了,比較常用的方法呢,有以下幾個(gè),第一種方式呢,還是使用auto_increment屬性,來在表中生成唯一ID,不過我們要修改兩個(gè)參數(shù),一個(gè)是auto_increment_increment,另一個(gè)是auto_increment_offset,關(guān)于這兩個(gè)參數(shù)呢,我們在介紹MYSQL高可用時(shí)呢,也為大家介紹過了,大家只要記住,auto_increment_increment的值呢,要和分片節(jié)點(diǎn)的數(shù)量相同,這個(gè)參數(shù)決定了每次自增ID的增長的步長,就是說,如果我們有6個(gè)節(jié)點(diǎn),來分片集群,那么這個(gè)參數(shù)的值呢就要設(shè)置為9,而另一個(gè)參數(shù)呢,分別設(shè)置1到6不同的值,這樣就能夠保證每一個(gè)節(jié)點(diǎn)中,相同表生成自增ID的值呢,是不沖突的,但是這種方式只適用于,一個(gè)節(jié)點(diǎn)中只保存一套分區(qū)表的情況,如果一個(gè)節(jié)點(diǎn)保存了多套分區(qū)表,那么這個(gè)方式就不能夠使用了,因?yàn)樵谕还?jié)點(diǎn)中,相同的分區(qū)表中,所生成的ID還是會(huì)有沖突,所以我們就不能使用這種方式了,那么你來看看下面這兩種方式,第二種方式呢,是配置一個(gè)全局節(jié)點(diǎn)ID,并在這個(gè)節(jié)點(diǎn)中呢,建立相應(yīng)的表,來使用auto_increment的屬性,來生成這個(gè)自增ID,應(yīng)用程序先通過這張表,獲取唯一ID后,再通過分區(qū)函數(shù),把數(shù)據(jù)插入到不同的分片中,使用這種方式還是比較簡單的,但是這個(gè)全局節(jié)點(diǎn)呢,又可能成為這個(gè)系統(tǒng)的性能瓶頸,所以我們就引出了下面這種方式第三種方式實(shí)際上和第二種方式非常像,只是用了Redis這樣的緩存系統(tǒng)呢,來代替第二種方法中的全局節(jié)點(diǎn),大家知道,Redis的這種讀寫效率呢,要遠(yuǎn)高于MYSQL,使用Redis生成唯一的ID呢,其效率也會(huì)非常的高,就避免了由于生成ID的效率的問題,而造成系統(tǒng)性能下降的發(fā)生,到此我們就知道進(jìn)行數(shù)據(jù)庫分片前,我們要做的準(zhǔn)備工作都有什么,下面我們就以一個(gè)例子一起來看一看
?
總結(jié)
- 上一篇: 数据库分库分表的几种方式
- 下一篇: linux cmake编译源码,linu