11 | NoSQL:在高并發(fā)場景下,數(shù)據(jù)庫和NoSQL如何做到互補(bǔ)?

前面,如何將傳統(tǒng)的關(guān)系型數(shù)據(jù)庫改造成分布式存儲(chǔ)服務(wù),以抵抗高并發(fā)和大流量的沖擊。

對于存儲(chǔ)服務(wù)來說,我們一般會(huì)從兩個(gè)方面對它做改造:

1、提升它的讀寫性能,尤其是讀性能,因?yàn)槲覀兠鎸Φ亩嗍且恍┳x多寫少的產(chǎn)品。比方說,你離不開的微信朋友圈、微博和淘寶,都是查詢 QPS 遠(yuǎn)遠(yuǎn)大于寫入 QPS。

2、增強(qiáng)它在存儲(chǔ)上的擴(kuò)展能力,從而應(yīng)對大數(shù)據(jù)量的存儲(chǔ)需求。

我之前帶你學(xué)習(xí)的讀寫分離和分庫分表就是從這兩方面出發(fā),改造傳統(tǒng)的關(guān)系型數(shù)據(jù)庫的,但仍有一些問題無法解決。

比如,在微博項(xiàng)目中關(guān)系的數(shù)據(jù)量達(dá)到了千億,那么即使分隔成 1024 個(gè)庫表,每張表的數(shù)據(jù)量也達(dá)到了億級(jí)別,并且關(guān)系的數(shù)據(jù)量還在以極快的速度增加,即使你分隔成再多的庫表,數(shù)據(jù)量也會(huì)很快增加到瓶頸。這個(gè)問題用傳統(tǒng)數(shù)據(jù)庫很難根本解決,因?yàn)樗跀U(kuò)展性方面是很弱的,這時(shí),就可以利用 NoSQL,因?yàn)樗兄焐植际降哪芰Γ軌蛱峁﹥?yōu)秀的讀寫性能,可以很好地補(bǔ)充傳統(tǒng)關(guān)系型數(shù)據(jù)庫的短板。那么它是如何做到的呢?

這節(jié)課,我就還是以你的垂直電商系統(tǒng)為例,帶你掌握如何用 NoSQL 數(shù)據(jù)庫和關(guān)系型數(shù)據(jù)庫互補(bǔ),共同承擔(dān)高并發(fā)和大流量的沖擊。

NoSQL,No SQL?

NoSQL 想必你很熟悉,它指的是不同于傳統(tǒng)的關(guān)系型數(shù)據(jù)庫的其他數(shù)據(jù)庫系統(tǒng)的統(tǒng)稱,它不使用 SQL 作為查詢語言,提供優(yōu)秀的橫向擴(kuò)展能力和讀寫性能,非常契合互聯(lián)網(wǎng)項(xiàng)目高并發(fā)大數(shù)據(jù)的特點(diǎn)。所以一些大廠,比如小米、微博、陌陌都很傾向使用它來作為高并發(fā)大容量的數(shù)據(jù)存儲(chǔ)服務(wù)。

NoSQL 數(shù)據(jù)庫發(fā)展到現(xiàn)在,十幾年間,出現(xiàn)了多種類型,我來給你舉幾個(gè)例子:

  • Redis、LevelDB 這樣的 KV 存儲(chǔ)。這類存儲(chǔ)相比于傳統(tǒng)的數(shù)據(jù)庫的優(yōu)勢是極高的讀寫性能,一般對性能有比較高的要求的場景會(huì)使用。
  • Hbase、Cassandra 這樣的列式存儲(chǔ)數(shù)據(jù)庫。這種數(shù)據(jù)庫的特點(diǎn)是數(shù)據(jù)不像傳統(tǒng)數(shù)據(jù)庫以行為單位來存儲(chǔ),而是以列來存儲(chǔ),適用于一些離線數(shù)據(jù)統(tǒng)計(jì)的場景。
  • 像 MongoDB、CouchDB 這樣的文檔型數(shù)據(jù)庫。這種數(shù)據(jù)庫的特點(diǎn)是 Schema Free(模式自由),數(shù)據(jù)表中的字段可以任意擴(kuò)展,比如說電商系統(tǒng)中的商品有非常多的字段,并且不同品類的商品的字段也都不盡相同,使用關(guān)系型數(shù)據(jù)庫就需要不斷增加字段支持,而用文檔型數(shù)據(jù)庫就簡單很多了。

在 NoSQL 數(shù)據(jù)庫剛剛被應(yīng)用時(shí),它被認(rèn)為是可以替代關(guān)系型數(shù)據(jù)庫的銀彈,在我看來,也許因?yàn)橐韵聨讉€(gè)方面的原因:

  • 彌補(bǔ)了傳統(tǒng)數(shù)據(jù)庫在性能方面的不足;
  • 數(shù)據(jù)庫變更方便,不需要更改原先的數(shù)據(jù)結(jié)構(gòu);
  • 適合互聯(lián)網(wǎng)項(xiàng)目常見的大數(shù)據(jù)量的場景;

不過,這種看法是個(gè)誤區(qū),因?yàn)槁匚覀儼l(fā)現(xiàn)在業(yè)務(wù)開發(fā)的場景下還是需要利用 SQL 語句的強(qiáng)大的查詢功能以及傳統(tǒng)數(shù)據(jù)庫事務(wù)和靈活的索引等功能,NoSQL 只能作為一些場景的補(bǔ)充。

那么接下來,我就帶你了解 NoSQL 數(shù)據(jù)庫是如何做到與關(guān)系數(shù)據(jù)庫互補(bǔ)的。了解這部分內(nèi)容,你可以在實(shí)際項(xiàng)目中更好地使用 NoSQL 數(shù)據(jù)庫補(bǔ)充傳統(tǒng)數(shù)據(jù)庫的不足。

使用 NoSQL 提升寫入性能

數(shù)據(jù)庫系統(tǒng)大多使用的是傳統(tǒng)的機(jī)械磁盤,對于機(jī)械磁盤的訪問方式有兩種:一種是隨機(jī) IO;另一種是順序 IO。隨機(jī) IO 就需要花費(fèi)時(shí)間做昂貴的磁盤尋道,一般來說,它的讀寫效率要比順序 IO 小兩到三個(gè)數(shù)量級(jí),所以我們想要提升寫入的性能就要盡量減少隨機(jī) IO。

以 MySQL 的 InnoDB 存儲(chǔ)引擎來說,更新 binlog、redolog、undolog 都是在做順序 IO,而更新 datafile 和索引文件則是在做隨機(jī) IO,而為了減少隨機(jī) IO 的發(fā)生,關(guān)系數(shù)據(jù)庫已經(jīng)做了很多的優(yōu)化,比如說寫入時(shí)先寫入內(nèi)存,然后批量刷新到磁盤上,但是隨機(jī) IO 還是會(huì)發(fā)生。

索引在 InnoDB 引擎中是以 B+ 樹(上一節(jié)課提到了 B+ 樹,你可以回顧一下)方式來組織的,而 MySQL 主鍵是聚簇索引(一種索引類型,數(shù)據(jù)與索引數(shù)據(jù)放在一起),既然數(shù)據(jù)和索引數(shù)據(jù)放在一起,那么在數(shù)據(jù)插入或者更新的時(shí)候,我們需要找到要插入的位置,再把數(shù)據(jù)寫到特定的位置上,這就產(chǎn)生了隨機(jī)的 IO。而且一旦發(fā)生了頁分裂,就不可避免會(huì)做數(shù)據(jù)的移動(dòng),也會(huì)極大地?fù)p耗寫入性能。

NoSQL 數(shù)據(jù)庫是怎么解決這個(gè)問題的呢?

它們有多種的解決方式,這里我給你講一種最常見的方案,就是很多 NoSQL 數(shù)據(jù)庫都在使用的基于 LSM 樹的存儲(chǔ)引擎,這種算法使用最多,所以在這里著重剖析一下。

LSM 樹(Log-Structured Merge Tree)犧牲了一定的讀性能來換取寫入數(shù)據(jù)的高性能,Hbase、Cassandra、LevelDB 都是用這種算法作為存儲(chǔ)的引擎。

它的思想很簡單,數(shù)據(jù)首先會(huì)寫入到一個(gè)叫做 MemTable 的內(nèi)存結(jié)構(gòu)中,在 MemTable 中數(shù)據(jù)是按照寫入的 Key 來排序的。為了防止 MemTable 里面的數(shù)據(jù)因?yàn)闄C(jī)器掉電或者重啟而丟失,一般會(huì)通過寫 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)?SSTable 都是有序的,所以合并的速度也很快。

當(dāng)從 LSM 樹里面讀數(shù)據(jù)時(shí),我們首先從 MemTable 中查找數(shù)據(jù),如果數(shù)據(jù)沒有找到,再從 SSTable 中查找數(shù)據(jù)。因?yàn)榇鎯?chǔ)的數(shù)據(jù)都是有序的,所以查找的效率是很高的,只是因?yàn)閿?shù)據(jù)被拆分成多個(gè) SSTable,所以讀取的效率會(huì)低于 B+ 樹索引。

img

和 LSM 樹類似的算法有很多,比如說 TokuDB 使用的名為 Fractal tree 的索引結(jié)構(gòu),它們的核心思想就是將隨機(jī) IO 變成順序的 IO,從而提升寫入的性能。

在后面的緩存篇中,我也將給你著重介紹我們是如何使用 KV 型 NoSQL 存儲(chǔ)來提升讀性能的。所以你看,NoSQL 數(shù)據(jù)庫補(bǔ)充關(guān)系型數(shù)據(jù)庫的第一種方式就是提升讀寫性能。

場景補(bǔ)充

除了可以提升性能之外,NoSQL 數(shù)據(jù)庫還可以在某些場景下作為傳統(tǒng)關(guān)系型數(shù)據(jù)庫的補(bǔ)充,來看一個(gè)具體的例子。

假設(shè)某一天,CEO 找到你并且告訴你,他正在為你的垂直電商項(xiàng)目規(guī)劃搜索的功能,需要支持按照商品的名稱模糊搜索到對應(yīng)的商品,希望你盡快調(diào)研出解決方案。

一開始,你認(rèn)為這非常的簡單,不就是在數(shù)據(jù)庫里面執(zhí)行一條類似:“select * from product where name like ‘%***%’”的語句嗎?可是在實(shí)際執(zhí)行的過程中,卻發(fā)現(xiàn)了問題。

你發(fā)現(xiàn)這類語句并不是都能使用到索引,只有后模糊匹配的語句才能使用索引。比如語句“select * from product where name like ‘% 電冰箱’”就沒有使用到字段“name”上的索引,而“select * from product where name like ‘索尼 %’”就使用了“name”上的索引。而一旦沒有使用索引就會(huì)掃描全表的數(shù)據(jù),在性能上是無法接受的。

于是你在谷歌上搜索了一下解決方案,發(fā)現(xiàn)大家都在使用開源組件 Elasticsearch 來支持搜索的請求,它本身是基于“倒排索引”來實(shí)現(xiàn)的,那么什么是倒排索引呢?

倒排索引是指將記錄中的某些列做分詞,然后形成的分詞與記錄 ID 之間的映射關(guān)系。比如說,你的垂直電商項(xiàng)目里面有以下記錄:

img

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

img

這樣,如果用戶搜索電冰箱,就可以給他展示商品 ID 為 1 和 3 的兩件商品了。

而 Elasticsearch 作為一種常見的 NoSQL 數(shù)據(jù)庫,就以倒排索引作為核心技術(shù)原理,為你提供了分布式的全文搜索服務(wù),這在傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中使用 SQL 語句是很難實(shí)現(xiàn)的。所以你看,NoSQL 可以在某些業(yè)務(wù)場景下代替?zhèn)鹘y(tǒng)數(shù)據(jù)庫提供數(shù)據(jù)存儲(chǔ)服務(wù)

提升擴(kuò)展性

另外,在擴(kuò)展性方面,很多 NoSQL 數(shù)據(jù)庫也有著先天的優(yōu)勢。還是以你的垂直電商系統(tǒng)為例,你已經(jīng)為你的電商系統(tǒng)增加了評論系統(tǒng),開始你的評估比較樂觀,覺得電商系統(tǒng)的評論量級(jí)不會(huì)增長很快,所以就為它分了 8 個(gè)庫,每個(gè)庫拆分成 16 張表。

  • 但是評論系統(tǒng)上線之后,存儲(chǔ)量級(jí)增長的異常迅猛,你不得不將數(shù)據(jù)庫拆分成更多的庫表,而數(shù)據(jù)也要重新遷移到新的庫表中,過程非常痛苦,而且數(shù)據(jù)遷移的過程也非常容易出錯(cuò)。

這時(shí),你考慮是否可以考慮使用 NoSQL 數(shù)據(jù)庫來徹底解決擴(kuò)展性的問題,經(jīng)過調(diào)研你發(fā)現(xiàn)它們在設(shè)計(jì)之初就考慮到了分布式和大數(shù)據(jù)存儲(chǔ)的場景,比如像 MongoDB 就有三個(gè)擴(kuò)展性方面的特性。

  • 其一是 Replica,也叫做副本集,你可以理解為主從分離,也就是通過將數(shù)據(jù)拷貝成多份來保證當(dāng)主掛掉后數(shù)據(jù)不會(huì)丟失。同時(shí)呢,Replica 還可以分擔(dān)讀請求。Replica 中有主節(jié)點(diǎn)來承擔(dān)寫請求,并且把數(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,也叫做分片,你可以理解為分庫分表,即將數(shù)據(jù)按照某種規(guī)則拆分成多份,存儲(chǔ)在不同的機(jī)器上。MongoDB 的 Sharding 特性一般需要三個(gè)角色來支持,一個(gè)是 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 中獲取元信息后,將請求路由到正確的 Shard Server 中。
img
  • 其三是負(fù)載均衡,就是當(dāng) MongoDB 發(fā)現(xiàn) Shard 之間數(shù)據(jù)分布不均勻,會(huì)啟動(dòng) Balancer 進(jìn)程對數(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ù)庫中內(nèi)置的擴(kuò)展性方面的特性可以讓我們不再需要對數(shù)據(jù)庫做分庫分表和主從分離,也是對傳統(tǒng)數(shù)據(jù)庫一個(gè)良好的補(bǔ)充。

你可能會(huì)覺得,NoSQL 已經(jīng)成熟到可以代替關(guān)系型數(shù)據(jù)庫了,但是就目前來看,NoSQL 只能作為傳統(tǒng)關(guān)系型數(shù)據(jù)庫的補(bǔ)充而存在,彌補(bǔ)關(guān)系型數(shù)據(jù)庫在性能、擴(kuò)展性和某些場景下的不足,所以你在使用或者選擇時(shí)要結(jié)合自身的場景靈活地運(yùn)用。

NoSQL 數(shù)據(jù)庫在性能、擴(kuò)展性上的優(yōu)勢,以及它的一些特殊功能特性,主要有以下幾點(diǎn):
  1. 在性能方面,NoSQL 數(shù)據(jù)庫使用一些算法將對磁盤的隨機(jī)寫轉(zhuǎn)換成順序?qū)懀嵘藢懙男阅埽?/li>
  2. 在某些場景下,比如全文搜索功能,關(guān)系型數(shù)據(jù)庫并不能高效地支持,需要 NoSQL 數(shù)據(jù)庫的支持;
  3. 在擴(kuò)展性方面,NoSQL 數(shù)據(jù)庫天生支持分布式,支持?jǐn)?shù)據(jù)冗余和數(shù)據(jù)分片的特性。
  4. 這些都讓它成為傳統(tǒng)關(guān)系型數(shù)據(jù)庫的良好的補(bǔ)充,你需要了解的是,NoSQL 可供選型的種類很多,每一個(gè)組件都有各自的特點(diǎn)。你在做選型的時(shí)候需要對它的實(shí)現(xiàn)原理有比較深入的了解,最好在運(yùn)維方面對它有一定的熟悉,這樣在出現(xiàn)問題時(shí)才能及時(shí)找到解決方案。否則,盲目跟從地上了一個(gè)新的 NoSQL 數(shù)據(jù)庫,最終可能導(dǎo)致會(huì)出了故障無法解決,反而成為整體系統(tǒng)的拖累。

我在之前的項(xiàng)目中曾經(jīng)使用 Elasticsearch 作為持久存儲(chǔ),支撐社區(qū)的 feed 流功能,初期開發(fā)的時(shí)候確實(shí)很爽,你可以針對 feed 中的任何字段做靈活高效地查詢,業(yè)務(wù)功能迭代迅速,代碼也簡單易懂??墒堑搅撕笃诹髁可蟻碇?,由于缺少對于 Elasticsearch 成熟的運(yùn)維能力,造成故障頻出,尤其到了高峰期就會(huì)出現(xiàn)節(jié)點(diǎn)不可用的問題,而由于業(yè)務(wù)上的巨大壓力又無法分出人力和精力對 Elasticsearch 深入的學(xué)習(xí)和了解,最后不得不做大的改造切回熟悉的 MySQL。所以,對于開源組件的使用,不能只停留在只會(huì)“hello world”的階段,而應(yīng)該對它有足夠的運(yùn)維上的把控能力。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容