數(shù)據(jù)庫 本身功能非常單一,僅可作為數(shù)據(jù)的存儲介質(zhì),但錯誤的數(shù)據(jù)庫選型帶來的代價可能就是項目
性能的大幅下降 ,對于很多企業(yè)應(yīng)用來說這也是致命的傷害,另外,選擇不同數(shù)據(jù)庫類型同樣會決定系統(tǒng)中其他模塊的設(shè)計,因此,數(shù)據(jù)庫選型對于整個項目非常重要,我們通常也稱這種需求為
非功能性需求(NFRs,non-functional requirements) ,對于數(shù)據(jù)庫,主要需要考慮如下三點因素:
數(shù)據(jù)結(jié)構(gòu) 查詢模式 數(shù)據(jù)規(guī)模 目前,市面上已經(jīng)有各類存儲解決方案了,本文我們就來討論一下如何在這些方案中選擇最適合自己的方案。
緩存 如果項目需要
頻繁調(diào)用數(shù)據(jù)庫 API 或者一些高延遲的遠(yuǎn)程服務(wù) ,那么可能需要最先考慮在客戶端和數(shù)據(jù)庫之間使用緩存來降低延遲。目前,常用的緩存方案有 Memcached,Hazelcast,Redis,這些方案大同小異,但 Redis 使用最廣泛且穩(wěn)定,是目前國內(nèi)最常用的數(shù)據(jù)庫緩存解決方案。
緩存圖示 文件存儲 如果需要開發(fā)一款抖音、B 站之類的產(chǎn)品,就
需要存儲大量圖像、視頻等數(shù)據(jù) ,僅僅一個數(shù)據(jù)庫可能并不能滿足我們的需求,因為這時需要存儲的是文件而非一般的數(shù)據(jù)信息,數(shù)據(jù)庫本質(zhì)依然只能用來查詢信息數(shù)據(jù)而已,而文件本身也并不用“查詢”,只需要按需拿到這整個文件即可,這種情況下,符合項目要求的解決方案就是
對象(Blob)存儲方案 ,如 Amazon S3,通常,Blob 存儲方案還可以與 CDN 結(jié)合使用來減少延遲,這樣就可以實現(xiàn)無地理位置差異提供內(nèi)容服務(wù)。
提供文本搜索功能 淘寶、京東這些大型應(yīng)用都會提供內(nèi)容的搜索功能,這樣就可以方便用戶按照商品類型、品牌對數(shù)據(jù)進(jìn)行分類搜索,這種功能通常會使用
Solr 或
Elasticsearch 之類的搜索引擎服務(wù),這類搜索服務(wù)通常也會支持
模糊搜索 ,例如會考慮到用戶拼寫錯誤的情況,這會很大程度上提升用戶體驗。但是,搜索引擎不是是數(shù)據(jù)庫,并不能保證我們的數(shù)據(jù)不會丟失,因此我們不能使用 Elasticsearch 這類搜索引擎作為數(shù)據(jù)源,這里就需要我們配合兩者使用,將數(shù)據(jù)庫中的內(nèi)容加載到 Elasticsearch 中來降低搜索延遲,然后在以此為基礎(chǔ)提供搜索功能。
時序數(shù)據(jù)庫(TSDB,Time series database) 時序數(shù)據(jù)庫全稱為時間序列數(shù)據(jù)庫(Time series database),是關(guān)系型數(shù)據(jù)庫的一種,
主要用于處理帶時間標(biāo)簽(按照時間的順序變化,即時間序列化)的數(shù)據(jù),帶時間標(biāo)簽的數(shù)據(jù)也稱為時間序列數(shù)據(jù) 。如果我們要開發(fā)的系統(tǒng)對時間特別敏感,如股票交易、財務(wù)分析系統(tǒng),此時就需要經(jīng)常對一定時間的內(nèi)數(shù)據(jù)分析,如過去 1周,10天,1個月,1 年等等,TSDB 會以毫秒級的速度給出這些我們需要的數(shù)據(jù),傳統(tǒng)數(shù)據(jù)庫很難做到這一點。
目前,市面上常用的時序數(shù)據(jù)庫有
OpenTSDB、InfluxDB 等。
數(shù)據(jù)倉庫 很多項目也會需要一類能夠
存儲巨量數(shù)據(jù)的數(shù)據(jù)庫 ,如滴滴需要存儲所有訂單信息來分析哪個城市、那個時間段為使用率最高,這些系統(tǒng)通常和常規(guī)用戶可感知的交易不同,可以使用脫機(jī)類型的數(shù)據(jù)倉庫。
Hadoop 是目前主流的數(shù)據(jù)倉庫解決方案。
如文章開頭所述,數(shù)據(jù)結(jié)構(gòu)是我們用來做數(shù)據(jù)庫選型時的重要因素之一,如果我們要存儲結(jié)構(gòu)化或者可以表格形式表示的數(shù)據(jù),則可以使用關(guān)系型數(shù)據(jù)庫。
關(guān)系型數(shù)據(jù)庫 同時,我們還將考慮數(shù)據(jù)庫是否需要擁有
ACID 性質(zhì),即原子性(Atomicity),一致性(Consistency),隔離型(Isolation),持久性(Durability)四大性質(zhì)。
原子性 ,保證所有操作要么全有要么全無。一致性 ,保證操作前后數(shù)據(jù)庫狀態(tài)一致。隔離性 ,意味著多個事務(wù)單獨進(jìn)行,一個事務(wù)將不受另一正在進(jìn)行的并行事務(wù)的影響。這能保證數(shù)據(jù)庫應(yīng)該能夠處理并發(fā)事務(wù)而不會導(dǎo)致數(shù)據(jù)不一致的情況。持久性 ,保證一旦事務(wù)完成,更改將被永久寫入磁盤,并且不會因系統(tǒng)故障而丟失。如果我們的項目需要 ACID,則需要使用關(guān)系數(shù)據(jù)庫(RDBMS),如 MySQL,Oracle,Postgres 等,但是,如果不需要 ACID,那么也可以
非關(guān)系性數(shù)據(jù)庫 。例如,項目中需要為商品建立目錄索引,每個商品通常會有不同的屬性和信息,如藥品有保質(zhì)期,冰箱有節(jié)能等級等等,再例如我們的用戶表單中每位用戶也可能會有不同的屬性值,在這種情況下我們的數(shù)據(jù)就不能夠以表格形式表示,可以選擇使用
NoSQL 數(shù)據(jù)庫。
文檔型數(shù)據(jù)庫 另外,除了儲存,我們通常還需要查詢這些類型的得到數(shù)據(jù),這就需要考慮
查詢模式 這個要素,我們會根據(jù)存儲的數(shù)據(jù)類型和查詢類型來決定最終使用哪種數(shù)據(jù)庫。如果項目中會含有大量數(shù)據(jù),包括各種各樣的屬性和各種各種的查詢請求,就需要使用文檔型數(shù)據(jù)庫(
Document DB ),如
Couchbase、MongoDB 。
Elasticsearch 和 Solr 也是特殊文檔型數(shù)據(jù)庫。 如果我們的數(shù)據(jù)并沒有各種各樣的屬性,查詢類型也很有限,簡單增刪改查足以,但是數(shù)據(jù)庫的存儲量很大,如滴滴司機(jī)的位置,這類數(shù)據(jù)每時每刻都會增加,這種情況下,我們通常會使用柱狀存儲模型的數(shù)據(jù)庫,也稱
列型(Columnar DBs)數(shù)據(jù)庫 ,如
Cassandra、HBase 。這類數(shù)據(jù)庫每個列都有一個 column key 標(biāo)識,每個 column key下對應(yīng)若干 value,可以很輕松的獲得包含某個列的數(shù)據(jù)。
關(guān)系型中的行型數(shù)據(jù)庫與列型數(shù)據(jù)庫 在個人的小型項目我們通常會選擇 Cassandra,因為非常輕量而且部署起來非常方便,性能也完全不亞于 HBase,而 HBase 基于 Hadoop 顯得過于臃腫。因此,我們可以說在數(shù)據(jù)查詢時可以直接通過 where 語句指定 key 查詢時,可以選擇 Cassandra。如果我們將滴滴中與打車相關(guān)的訂單數(shù)據(jù)存儲在 Cassandra 后,司機(jī)的 id 可以作為每個列分區(qū)的 column key,當(dāng)我們想要查詢特定時間段內(nèi)該司機(jī)的路程,Cassandra 就可以立即幫我們在對應(yīng)列中查詢到這些數(shù)據(jù),但這時,當(dāng)我們想要查詢某個日期內(nèi)乘客的乘車記錄,由于客戶 ID 并不是分區(qū) column key,因此 Cassandra 就需要查詢整個分區(qū),這時 Cassandra 性能就會大打折扣!這種情況下,我們可以使用不同的分區(qū) key 將相同的數(shù)據(jù)復(fù)制到另一個表或列中,這時,當(dāng)我們收到有關(guān)客戶 ID 和日期的查詢時,我們可以將其直接定向到分區(qū) kay 為客戶 ID 的表中,這就是
查詢的種類少但數(shù)據(jù)量大 的意思,只要查詢的類型相似,Cassandra(和 HBase)就可以無限擴(kuò)展,但如果查詢的種類非常多的話,我們就必須為每個分區(qū) key 一次又一次地復(fù)制,直到達(dá)到一定的限制。如果我們不能控制查詢的類型,還是選擇采用 MongoDB 之類的方案,但是,如果我們只需要針對少數(shù)幾種查詢的大規(guī)模擴(kuò)展,那么 Cassandra 就是完美的解決方案。
數(shù)據(jù)庫選擇流程圖 現(xiàn)在,我們大概知道了基本的方向了,如果存儲結(jié)構(gòu)化數(shù)據(jù)并且需要 ACID 性質(zhì),則使用關(guān)系數(shù)據(jù)庫(如 MySQL),如果存儲具有許多屬性的海量數(shù)據(jù),則可以使用文檔數(shù)據(jù)庫(如 Mongo DB),如果數(shù)據(jù)非常簡單,查詢種類較少,則使用列式數(shù)據(jù)庫(如 Cassandra),但是實際項目中,還并不這么簡單。
混合使用 我們再以淘寶為例,對于一個商品來說,庫存中只有一件,但是很多用戶想要買,那么最終應(yīng)該只能賣給一個用戶,這就需要我們的數(shù)據(jù)庫擁有 ACID 性質(zhì),因此,需要 MySql 這類關(guān)系型數(shù)據(jù)庫,但是淘寶中的商品數(shù)據(jù)也在不斷增加,屬性也多種多樣,我們也需要使用 Cassandra 這種列存儲模型的
NoSQL 數(shù)據(jù)庫。我們應(yīng)該選擇哪一種?在實際項目中,我們通常會混合使用這兩種數(shù)據(jù)庫,例如,將尚未交付的訂單數(shù)據(jù)存儲在 MySQL 數(shù)據(jù)庫中,一旦訂單完成,我們就可以將其移至 Cassandra 進(jìn)行永久存儲。我們的需求還會變得更復(fù)雜,假如我們需要為購買商品的客戶構(gòu)建一個報告系統(tǒng),淘寶上的商品通常會由不同品牌、不同版本向不同的客戶出售,因此,報告也不能針對單個產(chǎn)品,而應(yīng)針對產(chǎn)品的子集,這類需求可以使用 Cassandra 或 MySQL 實現(xiàn),但是更好的方案是使用 Mongo DB 這類文檔型
數(shù)據(jù)庫 ,我們可以將訂單數(shù)據(jù)的子集保存在 MongoDB 中,這些數(shù)據(jù)可以告訴我們哪些用戶在什么時候,什么日期購買了某種商品的數(shù)量。因此,如果我們要查詢有多少人在上個月購買了 MacBook,我們可以從 MongoDB 中獲得訂單 ID,并使用此訂單 ID 來從 Cassandra 或 MySQL 中查詢其他的數(shù)據(jù)。