架構基礎之NoSQL介紹(筆記類)

筆記類,指在學習課程、專欄過程中對個人有用內容的記錄和少許理解。

一、NoSQL的發展歷史

在這裏插入圖片描述
定義:只要是爲了彌補關係數據庫的缺陷的方案,都可算nosql

二、關係型數據的缺點

關係數據庫經過幾十年的發展後已經非常成熟,強大的 SQL 功能和 ACID 的屬性,使得關係數據庫廣泛應用於各式各樣的系統中,但這並不意味着關係數據庫是完美的,關係數據庫存在如下缺點。

  • 關係數據庫存儲的是行記錄,無法存儲數據結構

    以微博的關注關係爲例,“我關注的人”是一個用戶 ID 列表,使用關係數據庫存儲只能將列表拆成多行,然後再查詢出來組裝,無法直接存儲一個列表。

  • 關係數據庫的 schema 擴展很不方便

    關係數據庫的表結構 schema 是強約束,操作不存在的列會報錯,業務變化時擴充列也比較麻煩,需要執行 DDL(data definition language,如 CREATE、ALTER、DROP 等)語句修改,而且修改時可能會長時間鎖表(例如,MySQL 可能將表鎖住 1 個小時)。

  • 係數據庫在大數據場景下 I/O 較高

    如果對一些大量數據的表進行統計之類的運算,關係數據庫的 I/O 會很高,因爲即使只針對其中某一列進行運算,關係數據庫也會將整行數據從存儲設備讀入內存。

  • 關係數據庫的全文搜索功能比較弱

    關係數據庫的全文搜索只能使用 like 進行整表掃描匹配,性能非常低,在互聯網這種搜索複雜的場景下無法滿足業務要求。

針對上述問題,分別誕生了不同的 NoSQL 解決方案,這些方案與關係數據庫相比,在某些應用場景下表現更好。但世上沒有免費的午餐,NoSQL 方案帶來的優勢,本質上是犧牲 ACID 中的某個或者某幾個特性,關係型數據庫仍然承擔了各種應用程序的核心數據存儲,而NoSQL數據庫作爲SQL數據庫的補充,兩者不再是二選一的問題,而是主從相互輔助的關係。

三、常見的NoSQL的分類

常見的 NoSQL 方案分爲 4 類。

  • K-V 存儲:解決關係數據庫無法存儲數據結構的問題,以 Redis 爲代表。
  • 文檔數據庫:解決關係數據庫強 schema 約束的問題,以MongoDB 爲代表。
  • 列式數據庫:解決關係數據庫大數據場景下的 I/O 問題,以 HBase 爲代表。
  • 全文搜索引擎:解決關係數據庫的全文搜索性能問題,以 Elasticsearch 爲代表。

3.1 K-V存儲

K-V 存儲的全稱是 Key-Value 存儲,其中 Key 是數據的標識,和關係數據庫中的主鍵含義一樣,Value 就是具體的數據。

Redis 是 K-V 存儲的典型代表,它是一款開源(基於 BSD 許可)的高性能 K-V 緩存和存儲系統。Redis 的 Value 是具體的數據結構,包括 string、hash、list、set、sorted set、bitmap 和 hyperloglog,所以常常被稱爲數據結構服務器。

以 List 數據結構爲例,Redis 提供了下面這些典型的操作(更多請參考鏈接:redis操作)

  • LPOP key 從隊列的左邊出隊一個元素。
  • LINDEX key index 獲取一個元素,通過其索引列表。
  • LLEN key 獲得隊列(List)的長度。
  • RPOP key 從隊列的右邊出隊一個元素。

以上這些功能,如果用關係數據庫來實現,就會變得很複雜。例如,LPOP 操作是移除並返回 key 對應的 list 的第一個元素。如果用關係數據庫來存儲,爲了達到同樣目的,需要進行下面的操作:

  1. 每條數據除了數據編號(例如,行 ID),還要有位置編號,否則沒有辦法判斷哪條數據是第一條。注意這裏不能用行 ID 作爲位置編號,因爲我們會往列表頭部插入數據。
  2. 查詢出第一條數據。
  3. 刪除第一條數據。
  4. 更新從第二條開始的所有數據的位置編號。

可以看出關係數據庫的實現很麻煩,而且需要進行多次 SQL 操作,性能很低。

Redis 的缺點主要體現在並不支持完整的 ACID 事務,Redis 雖然提供事務功能,但 Redis 的事務和關係數據庫的事務不可同日而語,Redis 的事務只能保證隔離性和一致性(I 和 C),無法保證原子性和持久性(A 和 D)。

雖然 Redis 並沒有嚴格遵循 ACID 原則,但實際上大部分業務也不需要嚴格遵循 ACID 原則。以上面的微博關注操作爲例,即使系統沒有將 A 加入 B 的粉絲列表,其實業務影響也非常小,因此我們在設計方案時,需要根據業務特性和要求來確定是否可以用 Redis,而不能因爲 Redis 不遵循 ACID 原則就直接放棄。

這類數據庫包含:

  • Aerospike
  • Apache Ignite
  • ArangoDB
  • Berkeley DB
  • Couchbase
  • Dynamo
  • FoundationDB
  • InfinityDB
  • MemcacheDB
  • MUMPS
  • Oracle NoSQL
  • OrientDB
  • Redis
  • Riak
  • SciDB
  • SDBM /Flat File dbm
  • ZooKeeper

3.2 文檔數據庫

爲了解決關係數據庫 schema 帶來的問題,文檔數據庫應運而生。文檔數據庫最大的特點就是 no-schema,可以存儲和讀取任意的數據。目前絕大部分文檔數據庫存儲的數據格式是 JSON(或者 BSON),因爲 JSON 數據是自描述的,無須在使用前定義字段,讀取一個 JSON 中不存在的字段也不會導致 SQL 那樣的語法錯誤。

文檔數據庫的 no-schema 特性,給業務開發帶來了幾個明顯的優勢。

  1. 新增字段簡單
    業務上增加新的字段,無須再像關係數據庫一樣要先執行 DDL 語句修改表結構,程序代碼直接讀寫即可。
  2. 歷史數據不會出錯
    對於歷史數據,即使沒有新增的字段,也不會導致錯誤,只會返回空值,此時代碼進行兼容處理即可。
  3. 可以很容易存儲複雜數據
    JSON 是一種強大的描述語言,能夠描述複雜的數據結構。例如,我們設計一個用戶管理系統,用戶的信息有 ID、姓名、性別、愛好、郵箱、地址、學歷信息。其中愛好是列表(因爲可以有多個愛好);地址是一個結構,包括省市區樓盤地址;學歷包括學校、專業、入學畢業年份信息等。如果我們用關係數據庫來存儲,需要設計多張表,包括基本信息(列:ID、姓名、性別、郵箱)、愛好(列:ID、愛好)、地址(列:省、市、區、詳細地址)、學歷(列:入學時間、畢業時間、學校名稱、專業),而使用文檔數據庫,一個 JSON 就可以全部描述。

 {                    
    "id": 10000, 
    "name": "James", 
    "sex": "male", 
    "hobbies": [  
        "football", 
        "playing", 
        "singing"
    ], 
    "email": "[email protected]", 
    "address": {  
        "province": "GuangDong", 
        "city": "GuangZhou", 
        "district": "Tianhe", 
        "detail": "PingYun Road 163"
    }, 
    "education": [  
        {  
            "begin": "2000-09-01", 
            "end": "2004-07-01", 
            "school": "UESTC", 
            "major": "Computer Science & Technology"
        }, 
        {  
            "begin": "2004-09-01", 
            "end": "2007-07-01", 
            "school": "SCUT", 
            "major": "Computer Science & Technology"
        }
    ]
 }

通過這個樣例我們看到,使用 JSON 來描述數據,比使用關係型數據庫表來描述數據方便和容易得多,而且更加容易理解。

文檔數據庫的這個特點,特別適合電商和遊戲這類的業務場景。以電商爲例,不同商品的屬性差異很大。例如,冰箱的屬性和筆記本電腦的屬性差異非常大,如下圖所示。

在這裏插入圖片描述
在這裏插入圖片描述
即使是同類商品也有不同的屬性。例如,LCD 和 LED 顯示器,兩者有不同的參數指標。這種業務場景如果使用關係數據庫來存儲數據,就會很麻煩,而使用文檔數據庫,會簡單、方便許多,擴展新的屬性也更加容易。

文檔數據庫 no-schema 的特性帶來的這些優勢也是有代價的,最主要的代價就是不支持事務。例如,使用 MongoDB 來存儲商品庫存,系統創建訂單的時候首先需要減扣庫存,然後再創建訂單。這是一個事務操作,用關係數據庫來實現就很簡單,但如果用 MongoDB 來實現,就無法做到事務性。異常情況下可能出現庫存被扣減了,但訂單沒有創建的情況。因此某些對事務要求嚴格的業務場景是不能使用文檔數據庫的。

文檔數據庫另外一個缺點就是無法實現關係數據庫的 join 操作。例如,我們有一個用戶信息表和一個訂單表,訂單表中有買家用戶 id。如果要查詢“購買了蘋果筆記本用戶中的女性用戶”,用關係數據庫來實現,一個簡單的 join 操作就搞定了;而用文檔數據庫是無法進行 join 查詢的,需要查兩次:一次查詢訂單表中購買了蘋果筆記本的用戶,然後再查詢這些用戶哪些是女性用戶。

這類數據庫包含:

  • Apache CouchDB
  • ArangoDB
  • BaseX
  • Clusterpoint
  • Couchbase
  • Cosmos DB
  • eXist-db
  • IBM Domino
  • MarkLogic
  • MongoDB
  • OrientDB
  • Qizx
  • RethinkDB

3.3 列式數據庫

顧名思義,列式數據庫就是按照列來存儲數據的數據庫,與之對應的傳統關係數據庫被稱爲“行式數據庫”,因爲關係數據庫是按照行來存儲數據的。
關係數據庫按照行式來存儲數據,主要有以下幾個優勢:

  • 業務同時讀取多個列時效率高,因爲這些列都是按行存儲在一起的,一次磁盤操作就能夠把一行數據中的各個列都讀取到內存中。
  • 能夠一次性完成對一行中的多個列的寫操作,保證了針對行數據寫操作的原子性和一致性;否則如果採用列存儲,可能會出現某次寫操作,有的列成功了,有的列失敗了,導致數據不一致。

我們可以看到,行式存儲的優勢是在特定的業務場景下才能體現,如果不存在這樣的業務場景,那麼行式存儲的優勢也將不復存在,甚至成爲劣勢,典型的場景就是海量數據進行統計。例如,計算某個城市體重超重的人員數據,實際上只需要讀取每個人的體重這一列並進行統計即可,而行式存儲即使最終只使用一列,也會將所有行數據都讀取出來。如果單行用戶信息有 1KB,其中體重只有 4 個字節,行式存儲還是會將整行 1KB 數據全部讀取到內存中,這是明顯的浪費。而如果採用列式存儲,每個用戶只需要讀取 4 字節的體重數據即可,I/O 將大大減少。

除了節省 I/O,列式存儲還具備更高的存儲壓縮比,能夠節省更多的存儲空間。普通的行式數據庫一般壓縮率在 3:1 到 5:1 左右,而列式數據庫的壓縮率一般在 8:1 到 30:1 左右,因爲單個列的數據相似度相比行來說更高,能夠達到更高的壓縮率。

同樣,如果場景發生變化,列式存儲的優勢又會變成劣勢。典型的場景是需要頻繁地更新多個列。因爲列式存儲將不同列存儲在磁盤上不連續的空間,導致更新多個列時磁盤是隨機寫操作;而行式存儲時同一行多個列都存儲在連續的空間,一次磁盤寫操作就可以完成,列式存儲的隨機寫效率要遠遠低於行式存儲的寫效率。此外,列式存儲高壓縮率在更新場景下也會成爲劣勢,因爲更新時需要將存儲數據解壓後更新,然後再壓縮,最後寫入磁盤。

基於上述列式存儲的優缺點,一般將列式存儲應用在離線的大數據分析和統計場景中,因爲這種場景主要是針對部分列單列進行操作,且數據寫入後就無須再更新刪除。

這類數據庫包含:

  • Accumulo
  • Cassandra
  • Scylla
  • HBase

3.4 全文搜索引擎

傳統的關係型數據庫通過索引來達到快速查詢的目的,但是在全文搜索的業務場景下,索引也無能爲力,主要體現在:

  • 全文搜索的條件可以隨意排列組合,如果通過索引來滿足,則索引的數量會非常多。
  • 全文搜索的模糊匹配方式,索引無法滿足,只能用 like 查詢,而 like 查詢是整表掃描,效率非常低。

3.4.1 全文搜索基本原理

全文搜索引擎的技術原理被稱爲“倒排索引”(Inverted index),也常被稱爲反向索引、置入檔案或反向檔案,是一種索引方法,其基本原理是建立單詞到文檔的索引。之所以被稱爲“倒排”索引,是和“正排“索引相對的,“正排索引”的基本原理是建立文檔到單詞的索引。我們通過一個簡單的樣例來說明這兩種索引的差異。

假設我們有一個技術文章的網站,裏面收集了各種技術文章,用戶可以在網站瀏覽或者搜索文章。

正排索引示例:
在這裏插入圖片描述
正排索引適用於根據文檔名稱來查詢文檔內容。例如,用戶在網站上單擊了“面向對象葵花寶典是什麼”,網站根據文章標題查詢文章的內容展示給用戶。

倒排索引示例:
表格僅爲示範,不是完整的倒排索引表格,實際上的倒排索引有成千上萬行,因爲每個單詞就是一個索引

3.4.2 全文搜索的使用方式

全文搜索引擎的索引對象是單詞和文檔,而關係數據庫的索引對象是鍵和行,兩者的術語差異很大,不能簡單地等同起來。因此,爲了讓全文搜索引擎支持關係型數據的全文搜索,需要做一些轉換操作,即將關係型數據轉換爲文檔數據。

目前常用的轉換方式是將關係型數據按照對象的形式轉換爲 JSON 文檔,然後將 JSON 文檔輸入全文搜索引擎進行索引。我同樣以程序員的基本信息表爲例,看看如何轉換。

將前面樣例中的程序員表格轉換爲 JSON 文檔,可以得到 3 個程序員信息相關的文檔,我以程序員 1 爲例:


 {
  "id": 1,
  "姓名": "多隆",
  "性別": "男",
  "地點": "北京",
  "單位": "貓廠",
  "愛好": "寫代碼,旅遊,馬拉松",
  "語言": "Java、C++、PHP",
  "自我介紹": "技術專家,簡單,爲人熱情"
 }

全文搜索引擎能夠基於 JSON 文檔建立全文索引,然後快速進行全文搜索。以 Elasticsearch 爲例,其索引基本原理如下:

Elastcisearch 是分佈式的文檔存儲方式。它能存儲和檢索複雜的數據結構——序列化成爲 JSON 文檔——以實時的方式。
在 Elasticsearch 中,每個字段的所有數據都是默認被索引的。即每個字段都有爲了快速檢索設置的專用倒排索引。而且,不像其他多數的數據庫,它能在相同的查詢中使用所有倒排索引,並以驚人的速度返回結果。

四、其他的分類方式

一種來自Stephen Yen更加精細的分類方式
在這裏插入圖片描述

五、寫在最後

本文絕大部分內容出自極客時間-李運華老師的 從0開始學架構.,如有條件歡迎支持購買正版。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章