之前介紹了如何將傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)改造成分布式存儲(chǔ)服務(wù),以抵抗高并發(fā)和大流量沖擊。
對(duì)于存儲(chǔ)服務(wù)來(lái)說(shuō),我們一般會(huì)從兩個(gè)方面對(duì)它做改造:
1.提升它的讀寫性能,尤其是讀性能,因?yàn)槲覀兠鎸?duì)的多是一些讀多寫少的產(chǎn)品。
2.增加它在存儲(chǔ)上的擴(kuò)展能力,從而應(yīng)對(duì)大數(shù)據(jù)量的存儲(chǔ)需求。
之前學(xué)習(xí)的讀寫分離和分庫(kù)分表就是從這兩方面出發(fā),改造傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)的,但是仍有一些問(wèn)題無(wú)法解決。
比如,在微博項(xiàng)目中,關(guān)系的數(shù)據(jù)量大到了千億,即使分隔成1024個(gè)庫(kù)表,每張表的數(shù)據(jù)量也達(dá)到了億級(jí)別,并且關(guān)系的數(shù)據(jù)量還在以極快的速度增加,即使你分割成再多的庫(kù)表,數(shù)據(jù)量也會(huì)很快增加到瓶頸。這個(gè)問(wèn)題用傳統(tǒng)數(shù)據(jù)庫(kù)很難根本解決,因?yàn)樗跀U(kuò)展性方面是很弱的,這時(shí)就可以利用NoSQL,因?yàn)樗兄焐植际降哪芰?,能夠提供?yōu)秀的讀寫性能,可以很好的補(bǔ)充傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的短板,那么它是如何做到的?
帶你掌握如何用NoSQL數(shù)據(jù)庫(kù)和關(guān)系型數(shù)據(jù)庫(kù)互補(bǔ),共同承擔(dān)高并發(fā)和大流量的沖擊。
什么是NoSQL?
NoSQL指的是不同于傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)的其他數(shù)據(jù)庫(kù)系統(tǒng)的統(tǒng)稱,它不使用SQL作為查詢語(yǔ)言,提供優(yōu)秀的橫向擴(kuò)展能力和讀寫性能,非常契合互聯(lián)網(wǎng)項(xiàng)目高并發(fā)大數(shù)據(jù)的特點(diǎn)。所以一些大廠很傾向使用它
NoSQL數(shù)據(jù)庫(kù)發(fā)展到現(xiàn)在,出現(xiàn)了多種類型:
- Redis,levelDB這樣的KV存儲(chǔ)。這類存儲(chǔ)相對(duì)于傳統(tǒng)的數(shù)據(jù)庫(kù)的優(yōu)勢(shì)是極高的讀寫性能,一般對(duì)性能有比較高的要求的場(chǎng)景會(huì)使用。
- Hbase、Cassandra這樣的列式存儲(chǔ)數(shù)據(jù)庫(kù)。這種數(shù)據(jù)庫(kù)的特點(diǎn)是數(shù)據(jù)不像傳統(tǒng)數(shù)據(jù)庫(kù)以行為單位來(lái)存儲(chǔ),而是以列來(lái)存儲(chǔ),適用于一些離線數(shù)據(jù)統(tǒng)計(jì)的場(chǎng)景。
- MongoDB、CouchDB這樣的文檔型數(shù)據(jù)庫(kù)。這種數(shù)據(jù)庫(kù)的特點(diǎn)是Schema Free(模式自由),數(shù)據(jù)表中的字段可以任意擴(kuò)展,比如電商系統(tǒng)中的商品有非常多的字段,并且不同品類的商品的字段也都不盡相同,使用關(guān)系型數(shù)據(jù)庫(kù)就需要不斷增加字段支持,而用文檔型數(shù)據(jù)庫(kù)就簡(jiǎn)單很多了。
在NoSQL數(shù)據(jù)庫(kù)剛剛被應(yīng)用時(shí),它被認(rèn)為是可以替代關(guān)系型數(shù)據(jù)庫(kù)的,也許是因?yàn)橐韵聨讉€(gè)方面的原因:
- 彌補(bǔ)了傳統(tǒng)數(shù)據(jù)庫(kù)在性能方面的不足;
- 數(shù)據(jù)庫(kù)變更方便,不需要更改原先的數(shù)據(jù)結(jié)構(gòu);
- 適合互聯(lián)網(wǎng)項(xiàng)目常見的大數(shù)據(jù)量的場(chǎng)景;
不過(guò)這種看法是個(gè)誤區(qū),因?yàn)槁奈覀儼l(fā)現(xiàn)在業(yè)務(wù)開發(fā)的場(chǎng)景下還是需要利用SQL語(yǔ)句的強(qiáng)大的查詢功能以及事務(wù)和索引等功能。NoSQL只能作為一些場(chǎng)景的補(bǔ)充。
NoSQL數(shù)據(jù)庫(kù)是如何做到與關(guān)系型數(shù)據(jù)庫(kù)互補(bǔ)的?
**使用NoSQL提升寫入性能
數(shù)據(jù)庫(kù)系統(tǒng)大多使用的是傳統(tǒng)的機(jī)械硬盤,對(duì)于機(jī)械硬盤的訪問(wèn)方式有兩種:一種是隨機(jī)IO;另一種是順序IO。隨機(jī)IO就需要花費(fèi)時(shí)間做昂貴的磁盤尋道,一般來(lái)說(shuō),它的讀寫效率要比順序IO小兩到三個(gè)數(shù)量級(jí),所以我們想要提升寫入的性能就要盡量減少隨機(jī)IO。
以MySQL的InnoDB存儲(chǔ)引擎來(lái)說(shuō),更新binlog,redolog,undolog都是在做順序IO,而更新datafile和索引文件則是在做隨機(jī)IO,而為了減少隨機(jī)IO的發(fā)生,關(guān)系型數(shù)據(jù)庫(kù)已經(jīng)做了很多的優(yōu)化,比如說(shuō)寫入時(shí)先寫入內(nèi)存,然后批量刷新到硬盤上,但是隨機(jī)IO還是會(huì)發(fā)生。
索引在InnoDB引擎中是以B+樹方式來(lái)組織的,二MySQL主鍵是聚簇索引(索引類型,數(shù)據(jù)與索引數(shù)據(jù)放在一起),既然數(shù)據(jù)和索引數(shù)據(jù)放在一起,那么在數(shù)據(jù)插入或者更新的時(shí)候,我們需要找到要插入的位置,然后把數(shù)據(jù)寫到特定的位置上,這樣就產(chǎn)生了隨機(jī)的IO。而且一旦發(fā)生了頁(yè)分裂,就不可避免的做數(shù)據(jù)的移動(dòng),也會(huì)極大地?fù)p耗寫入性能。
NoSQ數(shù)據(jù)庫(kù)是怎么解決這個(gè)問(wèn)題的?
它們有多種解決方式,這里講一種最常見的方案,就是很多NoSQL數(shù)據(jù)庫(kù)都在使用基于LSM樹的存儲(chǔ)引擎,這種算法使用最多。
LSM樹犧牲了一定的讀性能來(lái)?yè)Q取寫入數(shù)據(jù)的高性能,Hbase,Cassandra,LevelDB都是用這種算法作為存儲(chǔ)引擎。
它的思想很簡(jiǎn)單,數(shù)據(jù)首先會(huì)寫入到一個(gè)叫做MemTable的內(nèi)存結(jié)構(gòu)中,在MemTable中數(shù)據(jù)是按照寫入的key來(lái)排序的。為了防止MemTable里面的數(shù)據(jù)因?yàn)闄C(jī)器掉電或者重啟而丟失,一把會(huì)通過(guò)寫Write Ahead Log的方式將數(shù)據(jù)備份在磁盤上。
MemTable在積累到一定規(guī)模時(shí),它會(huì)被刷新生成一個(gè)新的文件,我們把這個(gè)文件叫做SSTable(Sorted String Table)。當(dāng)SSTable達(dá)到一定的數(shù)量時(shí),我們會(huì)將這些SSTable合并,減少文件的數(shù)量,因?yàn)镾STable都是有序的,所以合并的速度也很快。
當(dāng)從LSM樹里面讀數(shù)據(jù)時(shí),我們首先從MemTable中查找數(shù)據(jù),如果數(shù)據(jù)沒有找到,再?gòu)腟STable中查找數(shù)據(jù)。因?yàn)榇鎯?chǔ)的書都是有序的,所以查找的效率是很高的,只是因?yàn)閿?shù)據(jù)被拆分成了多個(gè)SSTable,所以讀取的效率會(huì)低于B+樹索引。

和LSM樹類似的算法有很多,比如說(shuō)TokuDB使用的名為Fractal tree的索引結(jié)構(gòu),它們的核心思想就是將隨機(jī)IO變成順序的IO,從而提升寫入的性能。
場(chǎng)景補(bǔ)充
除了可以提升性能之外,NoSQL數(shù)據(jù)庫(kù)還可以在某些場(chǎng)景下作為傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的補(bǔ)充,來(lái)看一個(gè)具體例子。
假設(shè)某一天,項(xiàng)目有新需求:需要支持按照商品的名稱模糊搜索到對(duì)應(yīng)的商品。
不就是在數(shù)據(jù)庫(kù)里執(zhí)行類似“LIKE ‘%商品%’”的語(yǔ)句么?實(shí)際執(zhí)行中,你會(huì)發(fā)現(xiàn)這類語(yǔ)句并不是都能使用到索引,只有后模糊匹配的語(yǔ)句才能使用索引。比如語(yǔ)句“name LIKE '%電冰箱%'”就沒有使用到字name”上的索引,而“name LIKE '索尼%'”就使用了name上的索引。而一旦沒有使用索引就會(huì)掃描全表的數(shù)據(jù),這是無(wú)法接受的。
通過(guò)骨骼搜索發(fā)現(xiàn),大家都在使用開源組件Elasticsearch來(lái)支持搜索的請(qǐng)求,它本身是基于"倒排索引"來(lái)實(shí)現(xiàn)的。什么是倒敘索引呢?
倒排索引是指將記錄中 的某些列做分詞,,然后形成的分詞與記錄ID之間的映射關(guān)系。比如說(shuō),電商項(xiàng)目里有以下記錄:

那么,我們將商品名稱做簡(jiǎn)單的分詞,然后建立起分詞和商品id的對(duì)應(yīng)關(guān)系,就像下面的這樣:

這樣如果用戶搜索電冰箱,就可以給他展示商品ID為1和3的兩件商品了。
而Elasticsearch作為一種常見的NoSQL數(shù)據(jù)庫(kù),就以倒排索引作為核心技術(shù)原理,為你提供了分布式的全文搜索服務(wù),這在傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)中使用SQL語(yǔ)句是很難實(shí)現(xiàn)的。所以NoSQL可以在某些業(yè)務(wù)場(chǎng)景下代替?zhèn)鹘y(tǒng)數(shù)據(jù)庫(kù)提供數(shù)據(jù)存儲(chǔ)服務(wù)。
提升擴(kuò)展性
很多NoSQL數(shù)據(jù)庫(kù)對(duì)于擴(kuò)展性也有著先天的優(yōu)勢(shì)。以電商系統(tǒng)為例,你已經(jīng)為你的電商系統(tǒng)增加了評(píng)論系統(tǒng),開始你的評(píng)估比較樂(lè)觀,覺得電商系統(tǒng)的評(píng)論量級(jí)不會(huì)增長(zhǎng)很快,所以就為它分了8個(gè)庫(kù),每個(gè)庫(kù)查分成16張表。
但是評(píng)論系統(tǒng)上線后,存儲(chǔ)量級(jí)增長(zhǎng)迅猛,你不得不將數(shù)據(jù)庫(kù)拆分成更多的庫(kù)表,而數(shù)據(jù)也要重新遷移到新的庫(kù)表中,過(guò)程痛苦而且容易出錯(cuò)。
這時(shí)你考慮是否可以使用NoSQL數(shù)據(jù)庫(kù)來(lái)徹底解決擴(kuò)展性的問(wèn)題,比如像MongoDB就有三個(gè)擴(kuò)展性方面的特性。
- 其一是Replica,也叫做副本及,可以理解為主從分離,也就是通過(guò)將數(shù)據(jù)拷貝成多份來(lái)保證當(dāng)主掛掉后數(shù)據(jù)不會(huì)丟失。同時(shí)Replica還可以分擔(dān)讀請(qǐng)求。Replica中有主節(jié)點(diǎn)來(lái)承擔(dān)寫請(qǐng)求,并且把對(duì)數(shù)據(jù)變動(dòng)記錄到oplog里(類似于binlog);從節(jié)點(diǎn)接收到oplog后就會(huì)修改自身的數(shù)據(jù)以保持和主節(jié)點(diǎn)的一致。一旦主節(jié)點(diǎn)掛掉,MongoDB會(huì)從從節(jié)點(diǎn)中選取一個(gè)節(jié)點(diǎn)成為主節(jié)點(diǎn),可以繼續(xù)提供寫數(shù)據(jù)服務(wù)。
-
其二是shard,也叫做分片,可以理解為分庫(kù)分表,將數(shù)據(jù)按照某種規(guī)則拆分成多份,存儲(chǔ)在不同的機(jī)器上。MongoDB的Sharding特性一般需要三個(gè)角色來(lái)支持,一個(gè)是Shard Server,它是實(shí)際存儲(chǔ)數(shù)據(jù)的節(jié)點(diǎn),是一個(gè)獨(dú)立的Mongod進(jìn)程;二是Config Server,也是一組Mongod進(jìn)程,主要存儲(chǔ)一些元信息,比如說(shuō)哪些分片存儲(chǔ)了哪些數(shù)據(jù)等;最后是Route Server,它不實(shí)際存儲(chǔ)數(shù)據(jù),僅僅作為路由使用,它從Config Server中獲取元信息后,將請(qǐng)求路由到正確的Shard Server中。
image.png - 其三是負(fù)載均衡,就是當(dāng)MongoDB發(fā)現(xiàn)Shard之間數(shù)據(jù)分配不均勻,會(huì)啟動(dòng)Balancer進(jìn)行對(duì)數(shù)據(jù)做重新的分配,最終讓不同的Shard Server的數(shù)據(jù)可以盡量的均衡。當(dāng)我們的Shard Server存儲(chǔ)空間不足需要擴(kuò)容時(shí),數(shù)據(jù)會(huì)自動(dòng)被移動(dòng)到新的Shard Server上,減少了數(shù)據(jù)遷移和驗(yàn)證的成本。
可以看到,NoSQL數(shù)據(jù)庫(kù)中內(nèi)置的擴(kuò)展性方面的特性可以讓我們不再需要對(duì)數(shù)據(jù)庫(kù)做分庫(kù)分表和主從分離,,也是對(duì)傳統(tǒng)數(shù)據(jù)庫(kù)一個(gè)良好的補(bǔ)充。
就木器啊來(lái)看,NoSQL只能作為傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的補(bǔ)充而存在,彌補(bǔ)關(guān)系型數(shù)據(jù)庫(kù)在性能、擴(kuò)展性和某些場(chǎng)景下的不足,所以使用時(shí)要結(jié)合自身的場(chǎng)景靈活的運(yùn)用。
