NoSQL:在高并發(fā)場(chǎng)景下,數(shù)據(jù)庫(kù)和NoSQL如何做到互補(bǔ)?
針對(duì)存儲(chǔ)服務(wù)的優(yōu)化,我們通常會(huì)著手兩個(gè)方面:
1. 提升讀寫(xiě)性能,特別是優(yōu)化讀取效率。因?yàn)槲覀兊漠a(chǎn)品多數(shù)是讀取頻率高于寫(xiě)入頻率的。例如,微信朋友圈、微博和淘寶等服務(wù),它們的查詢(xún) QPS明顯高于寫(xiě)入 QPS。
2. 加強(qiáng)存儲(chǔ)系統(tǒng)的擴(kuò)展性,以滿(mǎn)足不斷增長(zhǎng)的數(shù)據(jù)存儲(chǔ)需求。
NoSQL,No SQL?
NoSQL是一種非關(guān)系型數(shù)據(jù)庫(kù),與傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)有所不同。NoSQL數(shù)據(jù)庫(kù)不使用SQL作為查詢(xún)語(yǔ)言,而是提供了其他方式來(lái)操作數(shù)據(jù)。它們通常具有出色的橫向擴(kuò)展能力和高性能的讀寫(xiě)操作,非常適合處理互聯(lián)網(wǎng)項(xiàng)目中的大數(shù)據(jù)量和高并發(fā)訪問(wèn)的需求。因此,許多大型公司,如小米、微博和陌陌,都傾向于選擇NoSQL數(shù)據(jù)庫(kù)作為其處理高并發(fā)大容量數(shù)據(jù)的存儲(chǔ)服務(wù)。
Redis、LevelDB等鍵值存儲(chǔ)數(shù)據(jù)庫(kù),以其極高的讀寫(xiě)性能而著稱(chēng),通常在對(duì)性能有較高要求的場(chǎng)景下得到廣泛應(yīng)用。Hbase、Cassandra等列式存儲(chǔ)數(shù)據(jù)庫(kù)則以其以列為存儲(chǔ)單位而聞名,特別適用于離線數(shù)據(jù)統(tǒng)計(jì)等場(chǎng)景。至于MongoDB、CouchDB等文檔型數(shù)據(jù)庫(kù),其主要特點(diǎn)在于Schema Free,即數(shù)據(jù)表的字段可以靈活擴(kuò)展,這使得它們?cè)谔幚砭哂卸嘧冏侄谓Y(jié)構(gòu)的數(shù)據(jù),比如電商系統(tǒng)中的商品信息,更加簡(jiǎn)單和靈活
使用 NoSQL 提升寫(xiě)入性能
數(shù)據(jù)庫(kù)系統(tǒng)通常使用傳統(tǒng)的機(jī)械硬盤(pán)進(jìn)行存儲(chǔ),而機(jī)械硬盤(pán)的訪問(wèn)方式主要有兩種:隨機(jī)IO和順序IO。隨機(jī)IO需要花費(fèi)大量時(shí)間進(jìn)行磁盤(pán)尋道,因此其讀寫(xiě)效率通常比順序IO低兩到三個(gè)數(shù)量級(jí)。為了提升寫(xiě)入性能,需要盡量減少隨機(jī)IO的發(fā)生。
以MySQL的InnoDB存儲(chǔ)引擎為例,更新binlog、redo log、undo log等操作通常采用順序IO,而更新datafile和索引文件則需要進(jìn)行隨機(jī)IO。盡管關(guān)系數(shù)據(jù)庫(kù)進(jìn)行了許多優(yōu)化,比如先將寫(xiě)入數(shù)據(jù)存入內(nèi)存,然后批量刷新到磁盤(pán)上,但隨機(jī)IO仍然難以避免。
在InnoDB引擎中,索引通常采用B+樹(shù)結(jié)構(gòu)組織。由于MySQL的主鍵是聚簇索引,即數(shù)據(jù)和索引數(shù)據(jù)存儲(chǔ)在一起,因此在數(shù)據(jù)插入或更新時(shí)需要找到合適的位置并寫(xiě)入特定位置,這就會(huì)引發(fā)隨機(jī)IO。此外,一旦發(fā)生頁(yè)分裂,就不可避免地會(huì)涉及數(shù)據(jù)的移動(dòng),進(jìn)一步降低寫(xiě)入性能。
NoSQL 數(shù)據(jù)庫(kù)是怎么解決這個(gè)問(wèn)題的呢?
許多NoSQL數(shù)據(jù)庫(kù)都采用基于LSM樹(shù)的存儲(chǔ)引擎,這種算法在性能上取得了很大突破,因此在這里我想深入探討一下LSM樹(shù)的工作原理。LSM樹(shù)(Log-Structured Merge Tree)通過(guò)犧牲一定的讀性能來(lái)實(shí)現(xiàn)高效的寫(xiě)入操作,因此像Hbase、Cassandra和LevelDB等數(shù)據(jù)庫(kù)都采用了這種存儲(chǔ)引擎。
LSM樹(shù)的核心思想很簡(jiǎn)單:數(shù)據(jù)首先寫(xiě)入到一個(gè)稱(chēng)為MemTable的內(nèi)存結(jié)構(gòu)中,并按照寫(xiě)入的鍵(Key)進(jìn)行排序。為了避免因機(jī)器掉電或重啟而丟失MemTable中的數(shù)據(jù),通常會(huì)通過(guò)Write Ahead Log的方式將數(shù)據(jù)備份到磁盤(pán)上。當(dāng)MemTable累積到一定規(guī)模時(shí),會(huì)將其刷新為一個(gè)新文件,我們稱(chēng)之為SSTable(Sorted String Table)。當(dāng)SSTable數(shù)量達(dá)到一定閾值時(shí),會(huì)將它們進(jìn)行合并,以減少文件數(shù)量,因?yàn)镾STable都是有序的,所以合并速度非??臁?/span>
在進(jìn)行LSM樹(shù)的數(shù)據(jù)讀取時(shí),首先從MemTable中查找數(shù)據(jù),如果未找到,則從SSTable中查找。由于數(shù)據(jù)是有序存儲(chǔ)的,因此查詢(xún)效率非常高。然而,由于數(shù)據(jù)被拆分成多個(gè)SSTable,讀取效率可能低于B+樹(shù)索引。
圖片
提升擴(kuò)展性
此外,在可擴(kuò)展性方面,許多NoSQL數(shù)據(jù)庫(kù)具有天然的優(yōu)勢(shì)。以您的垂直電商系統(tǒng)為例,您已經(jīng)添加了評(píng)論系統(tǒng),最初對(duì)系統(tǒng)的評(píng)估比較樂(lè)觀,認(rèn)為評(píng)論量級(jí)不會(huì)迅速增長(zhǎng),因此將數(shù)據(jù)庫(kù)分成了8個(gè)庫(kù),每個(gè)庫(kù)又分成了16張表。但是評(píng)論系統(tǒng)上線后,存儲(chǔ)量迅速增長(zhǎng),您不得不將數(shù)據(jù)庫(kù)進(jìn)一步分割成更多的庫(kù)和表,而數(shù)據(jù)也必須重新遷移到新的庫(kù)表中,這個(gè)過(guò)程非常痛苦且容易出錯(cuò)。在這種情況下,您是否考慮過(guò)使用NoSQL數(shù)據(jù)庫(kù)來(lái)徹底解決可擴(kuò)展性問(wèn)題呢?經(jīng)過(guò)調(diào)查,您會(huì)發(fā)現(xiàn)NoSQL數(shù)據(jù)庫(kù)在設(shè)計(jì)之初就考慮到了分布式和大數(shù)據(jù)存儲(chǔ)的場(chǎng)景,比如像MongoDB就具備三個(gè)關(guān)鍵的擴(kuò)展性特性。
另一個(gè)關(guān)鍵特性是Replica,也稱(chēng)為副本集。您可以將其視為主從復(fù)制,即通過(guò)將數(shù)據(jù)復(fù)制多份來(lái)確保在主節(jié)點(diǎn)故障時(shí)數(shù)據(jù)不會(huì)丟失。同時(shí),副本還可以分擔(dān)讀請(qǐng)求。在副本中,主節(jié)點(diǎn)負(fù)責(zé)處理寫(xiě)請(qǐng)求,并將數(shù)據(jù)變更記錄到oplog中(類(lèi)似于binlog);從節(jié)點(diǎn)接收oplog后,會(huì)修改自身的數(shù)據(jù)以保持與主節(jié)點(diǎn)的一致性。一旦主節(jié)點(diǎn)故障,MongoDB將從從節(jié)點(diǎn)中選取一個(gè)節(jié)點(diǎn)作為主節(jié)點(diǎn),繼續(xù)提供寫(xiě)入數(shù)據(jù)的服務(wù)。
第二個(gè)特性是Shard,也稱(chēng)為分片,您可以將其視為分庫(kù)分表,即根據(jù)某種規(guī)則將數(shù)據(jù)拆分成多份,存儲(chǔ)在不同的機(jī)器上。MongoDB的Sharding特性通常需要三個(gè)角色來(lái)支持:Shard Server,實(shí)際存儲(chǔ)數(shù)據(jù)的節(jié)點(diǎn),是一個(gè)獨(dú)立的Mongod進(jìn)程;Config Server,也是一組Mongod進(jìn)程,主要存儲(chǔ)一些元信息,例如哪些分片存儲(chǔ)了哪些數(shù)據(jù)等;最后是Route Server,它不實(shí)際存儲(chǔ)數(shù)據(jù),僅用作路由,從Config Server獲取元信息后,將請(qǐng)求路由到正確的Shard Server。
圖片
另外一個(gè)關(guān)鍵特性是負(fù)載均衡,即當(dāng)MongoDB發(fā)現(xiàn)Shard之間數(shù)據(jù)分布不均勻時(shí),會(huì)啟動(dòng)Balancer進(jìn)程對(duì)數(shù)據(jù)進(jìn)行重新分配,以確保不同Shard Server的數(shù)據(jù)盡可能均衡。當(dāng)Shard Server的存儲(chǔ)空間不足需要擴(kuò)容時(shí),數(shù)據(jù)會(huì)自動(dòng)遷移到新的Shard Server上,從而減少了數(shù)據(jù)遷移和驗(yàn)證的成本。
在性能方面,NoSQL數(shù)據(jù)庫(kù)利用一些算法將磁盤(pán)上的隨機(jī)寫(xiě)操作轉(zhuǎn)換為順序?qū)懀瑥亩嵘藢?xiě)入性能。在某些場(chǎng)景下,如全文搜索功能,傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)無(wú)法有效支持,而需要借助NoSQL數(shù)據(jù)庫(kù)的特性。
就擴(kuò)展性而言,NoSQL數(shù)據(jù)庫(kù)天生支持分布式架構(gòu),具備數(shù)據(jù)冗余和數(shù)據(jù)分片的特性。這些特點(diǎn)使得NoSQL成為傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的有效補(bǔ)充。在選擇NoSQL數(shù)據(jù)庫(kù)時(shí),需要深入了解各種組件的實(shí)現(xiàn)原理,并具備一定的運(yùn)維經(jīng)驗(yàn)。否則,盲目地引入新的NoSQL數(shù)據(jù)庫(kù)可能導(dǎo)致故障無(wú)法解決,成為整個(gè)系統(tǒng)的負(fù)擔(dān)。
我曾在以前的項(xiàng)目中使用Elasticsearch作為持久存儲(chǔ),支持社區(qū)的動(dòng)態(tài)流功能。初期開(kāi)發(fā)進(jìn)展順利,Elasticsearch提供了靈活高效的查詢(xún)功能,業(yè)務(wù)功能得以快速迭代,代碼也簡(jiǎn)潔易懂。然而,隨著流量的增加,由于缺乏成熟的Elasticsearch運(yùn)維能力,頻繁出現(xiàn)故障。尤其是在高峰期,節(jié)點(diǎn)不穩(wěn)定的問(wèn)題更加突出。由于業(yè)務(wù)壓力巨大,無(wú)法投入足夠的人力和時(shí)間深入學(xué)習(xí)和理解Elasticsearch,最終不得不做出重大改動(dòng),回歸熟悉的MySQL。因此,對(duì)于開(kāi)源組件的使用,不應(yīng)止步于簡(jiǎn)單的入門(mén)階段,而應(yīng)具備足夠的運(yùn)維能力。