何為 NoSQL?NoSQL 不是一個工具,而是由一些具有互補性和競爭性的工具組成的一個概念,是一個生態圈。這些被稱為 NoSQL 的工具,在儲存資料的方式上,提供了一種與(基於 SQL 語言的)關係型資料庫截然不同的思路。要想了解 NoSQL,必須先了解現有的這些工具,去理解那些引導它們開拓出新的儲存領域的設計思路。
NoSQL 其名
在給 NoSQL 下定義之前,我們先來試著從它的名字上做一下解讀。顧名思義,NoSQL 系統的資料操作介面應該是非 SQL 型別的。但在 NoSQL 社群,NoSQL 被賦予了更具有包容性的含義,其意為 Not Only SQL,即 NoSQL 提供了一種與傳統關係型資料庫不同的儲存模式,這為開發者提供了關係型資料庫之外的另一種選擇。
NoSQL 的啟示
NoSQL 運動受到了很多相關研究論文的啟示,在所有資料中,最核心的有兩個:Google 的 BigTable 論文和 Amazon 的 Dynamo 論文。
特性概述
NoSQL 系統捨棄了一些 SQL 標準中的功能,取而代之的是一些簡單靈活的功能。NoSQL 的構建思想就是儘量簡化資料操作,儘量讓操作的執行效率可預估。當你去考查一個 NoSQL 系統時,下面的幾點是值得注意的。
資料模型及操作模型:你的應用層資料模型是行、物件還是文件型的呢?這個系統是否能支援你進行一些統計工作呢?
可靠性:當你更新資料時,新的資料是否立刻寫到持久化儲存中去了?新的資料是否同步到多臺機器上了?
擴充套件性:你的資料量有多大,單機是否能容下?你的讀寫量需求單機是否能支援?
分割槽策略:考慮到對擴充套件性、可用性或者永續性的要求,你是否需要一份資料被存在多臺機器上?你是否需要知道或者說你能否知道資料在哪臺機器上?
一致性:你的資料是否被複制到了多臺機器上?這些不同節點的資料如何保證一致性?
事務機制:業務是否需要 ACID 事務機制?
單機效能:如果你打算持久化的將資料存在磁碟上,哪種資料結構能滿足你的需求(你的需求是讀多還是寫多)?寫操作是否會成為磁碟瓶頸?
負載可評估:對於一個讀多寫少的應用,諸如響應使用者請求的網路應用,我們總會花很多精力來關注負載情況。你可能需要進行資料規模的監控,對多個使用者的資料進行彙總統計。你的應用場景是否需要這樣的功能呢?
NoSQL 資料模型及操作模型
資料庫的資料模型指的是資料在資料庫中的組織方式,資料庫的操作模型指的是存取這些資料的方式。通常資料模型包括關係模型、鍵值模型以及各種圖 結構模型。操作語言可能包括 SQL、鍵值查詢及 MapReduce 等。NoSQL 通常結合了多種資料模型和操作模型,提供不一樣的架構方式。
基於 Key 值儲存的 NoSQL 資料模型
在鍵值型系統中,複雜的聯合查詢以及滿足多個條件的資料查詢操作就不那麼容易實現了,需要換一種思維來建立和使用鍵名。比如要獲取部門號為 20 的所有員工的資訊,應用層可以先獲取 Key 為 employee_departments:20的這個列表,然後再迴圈地拿這個列表中的 ID 通過獲取 employee:ID 得到所有員工的資訊。
Key-Value 儲存
Key-Value 儲存可以說是最簡單的 NoSQL 儲存,每個 Key 值對應一個任意的資料值。對 NoSQL 系統來說,這個任意的資料值是什麼,它並不關心。比如在員工信念資料庫裡,employee:30這個 Key 對應的可能就是一段包含員工所有資訊的二進位制資料。這個二進位制的格式可能是 Protocol Buffer、Thrift 或者 Avro 都無所謂。
Key-結構化資料儲存
Key-結構化資料儲存的典型代表是 Redis,Redis 將 Key-Value 儲存的 Value 變成了結構化的資料型別。Value 的型別包括數字、字串、列表、集合以及有序集合。除了 set/get/delete 操作以為,Redis 還提供了很多針對以上資料型別的特殊操作,比如針對數字可以執行增、減操作,對 list 可以執行 push/pop 操作,通過提供這種針對單個 Value 進行的特定型別的操作,Redis 可以說實現了功能與效能的平衡。
Key-文件儲存
Key-文件儲存的代表有 CouchDB、MongoDB 和 Riak。這種儲存結構下 Key-Value 的 Value 是結構化的文件,通常這些文件是被轉換成 JSON 或者類似於 JSON 的結構進行儲存。文件可以儲存列表,鍵值對以及層次結構複雜的文件。
BigTable 的列簇式儲存
HBase 和 Cassandra 的資料模型都借鑑自 Google 的 BigTable。這種資料模型的特點是列式儲存,每一行資料的各項被儲存在不同的列中(這些列的集合稱作列簇)。而每一列中每一個資料都包含一個時間戳 屬性,這樣列中的同一個資料項的多個版本都能儲存下來。
列式儲存可以這樣理解:將行 ID、列簇號,列號以及時間戳一起,組成一個 Key,然後將 Value 按 Key 的順序進行儲存。Key 值的結構化使這種資料結構能夠實現一些特別的功能,最常用的就是將一個資料的多個版本存成時間戳不同的幾個值,這樣就能方便地儲存歷史資料。這種結構也能 天然地進行高效的鬆雜湊資料(在很多行中並沒有某列的資料)儲存。當然,對於那些很少有某一行有 NULL 值的列,由於每一個資料必須包含列標識,這又會造成空間的浪費。
圖結構儲存
圖結構儲存是 NoSQL 的另一種儲存實現。其指導思想是:資料並非對等的,關係型的儲存或者鍵值對的儲存,可能都不是最好的儲存方式。圖結構是電腦科學的基礎結構之一,Neo4j 和 HyperGraphDB 是當前最流行的圖結構資料庫。
複雜查詢
在 NoSQL 儲存系統中,有很多比鍵值查詢更復雜的操作。比如 MongoDB 可以在任意資料行上建立索引,可以使用 Javascript 語法設定複雜的查詢條件。BigTable 型的系統通常支援對單獨某一行的資料進行遍歷,允許對單列的資料進行按特定條件的篩選。CouchDB 允許你建立同一份資料的多個檢視,通過執行 MapReduce 任務來實現一些更為複雜的查詢或者更新操作。很多 NoSQL 系統都支援與 Hadoop 或者其他 MapReduce 框架結合來進行一些大規模資料分析工作。
事務機制
與關係型資料庫不同的是,NoSQL 系統通常注重效能和擴充套件性,而非事務機制。傳統的 SQL 資料庫的事務通常都是支援 ACID 的強事務機制。ACID 的支援使得應用者能夠很清楚他們當前的資料狀態。對很多 NoSQL 系統來說,對效能的考慮遠在 ACID 的保證之上。通常 NoSQL 系統僅提供行級別的原子性保證,也就是說同時對同一個 Key 下的資料進行的兩個操作,在實際執行時是會序列的,保證了每一個 Key-Value 對不會被破壞。
Schema-free 的儲存
還有一個很多 NoSQL 的共同點,就是它通常並沒有強制的資料結構約束。即使是在文件型儲存或者列式儲存上,也不會要求某一個資料列在每一行資料上都必須存在。
資料可靠性
最理想的狀態是,資料庫會把所有寫操作立刻寫到持久化儲存的裝置,同時複製多個副本到不同地理位置的不同節點上,以防止資料丟失。但這種對資料安全性的要求對效能是有影響的,所以不同的 NoSQL 系統在自身效能的考慮下,在資料安全上採取了不太一樣的策略。
單機可靠性
單機可靠性理解起來非常簡單,它的定義是寫操作不會由於機器重啟或者斷電而丟失。通常單機可靠性的保證是通過把資料寫到磁碟來完成的,而這通常會造成磁碟I/O成為整個系統的瓶頸。下面我們談談一些在單機可靠性的保證下提高效能的方法。
控制 fsync 的呼叫頻率
Redis 提供了幾種對 fsync 呼叫頻率的控制方法。應用開發者可以配置 Redis 在每次更新操作後都執行一次 fsync,這樣會比較安全,當然也就比較慢。Redis 也可以設定成N秒種呼叫一次 fsync,這樣效能會更好一點。但這樣的後果就是一旦出現故障,最多可能導致N秒內的資料丟失。而對一些可靠性要求不太高的場合(比如僅僅把 Redis 當 Cache 用的時候),應用開發者甚至可以直接關掉 fsync 的呼叫:讓作業系統來決定什麼時候需要把資料 flush 到磁碟(譯者注:這只是 Redis append only file 的機制,Redis 是可以關閉 aof 日誌的,另外,Redis 本身支援將記憶體中資料 dump 成 rdb 檔案的機制,和上面說的不是一回事)。
使用日誌型的資料結構
Cassandra、HBase、Redis 和 Riak 都會把寫操作順序的寫入到一個日誌檔案中。相對於儲存系統中的其他資料結構,上面說到的日誌檔案可以頻繁地進行 fsync 操作,這樣就把對磁碟的隨機寫變成順序寫了。
通過合併寫操作提高吞吐效能
Cassandra 有一個機制,它會把一小段時間內的幾個併發的寫操作放在一起進行一次 fsync 呼叫,這種做法叫 group commit。
多機可靠性
由於硬體層面有時會造成無法恢復的損壞,單機可靠性的保證在這時就鞭長莫及了。對於一些重要資料,跨機器做備份儲存是必備的安全措施。一些 NoSQL 系統提供了多機可靠性的支援。
Redis 採用傳統的主從資料同步的方式。
MongoDB 提供了一種叫 Replica Sets 高可用架構。
Riak、Cassandra 和 Voldemort 提供了一些更靈活的可配置策略,並提供一個可配置的引數N,代表每一個資料會被備份的份數。為了應對整個資料中心出現故障的情況,需要實現跨資料中心的多機備份功能。
橫向擴充套件帶來效能提升
橫向擴充套件的目標是達到線性的效果,即如果你增加一倍的機器,那麼負載能力應該也能相應的增加一倍。其主要需要解決的問題是如何讓資料在多臺機器間分佈,這裡面涉及到分片技術。
分片的意思,就是沒有任何一臺機器可以處理所有寫請求,也沒有任何一臺機器可以處理對所有資料的讀請求。下面我們將會對 hash 分片和範圍分片兩種分片方式進行描述。
如非必要,請勿分片
分片會導致系統複雜程度大增,所以,如果沒有必要,請不要使用分片。普通情況下,我們可以使用讀寫分離和構建快取的方式來緩解我們的資料讀壓力。但如果寫操作達到單點無法承擔的程度,那我們可能就真的需要進行分片了。
通過協調器進行資料分片
一種分片策略是通過引入一箇中間代理層來實現,該代理層記錄資料在各個節點的分佈狀況,所有讀寫請求都通過代理層來做路由。比如與 CouchDB 的兩個專案:Lounge 和 BigCouch。類似的,Twitter 自己也實現了一個叫 Gizzard 的協調器,可以實現資料分片和備份功能。
一致性 hash 環演算法
一致性 hash 是一種被廣泛應用的技術,其最早在一個叫 distributed hash tables(DHTs)的系統中進行使用。那些類 Dynamo 的應用,比如 Cassandra、Voldemort 和 Riak,基本上都使用了一致性 hash 環演算法。
如圖 1 所示,一致性 hash 環演算法有一個 hash 函式H,所有儲存資料的節點和資料本身都可以通過這個函式算出一個 hash 值,作為自己在下面環上的位置。然後每個節點會負責儲存其 hash 值到下一個節點間的所有資料的儲存。這樣使得即使節點數變化了,大部分資料並不需要進行遷移。
圖 1 一致性 hash 環演算法的 hash 函式
連續範圍分割槽
使用連續範圍分割槽的方法進行資料分片,需要我們儲存一份對映關係表,標明哪一段 Key 值對應存在哪臺機器上。與一致性 hash 類似,連續範圍分割槽會把 Key 值按連續的範圍分段,每段資料會被指定儲存在某個節點上,然後會被冗餘備份到其他節點。
BigTable 的處理方式
Google BigTable 論文中描述了一種範圍分割槽方式,它將資料切分成一個個的 tablet 資料塊。每個 tablet 儲存一定數量的鍵值對。然後儲存在 Tablet 伺服器上。tablet 塊的大小會保持在一定範圍,太大的塊會分裂成兩個,太小的塊又會合併成一個。BigTable 通過一個叫 Chubby 的模組來實現節點狀態檢測。類似的在 Hadoop 中有一個叫 ZooKeeper 的工具實現此功能。
一致性
上面講到了通過將資料冗餘儲存到不同的節點來保證資料安全和減輕負載,下面我們來看看這樣做引發的一個問題:保證資料在多個節點間的一致性是非常困難的。在多個點間保持資料的一致性的問題,也就是本章的主題。下面我們首先來看一下在著名的 CAP 理論。
一致性(C):在分散式系統中的所有資料備份,在同一時刻是否同樣的值。
可用性(A):在叢集中一部分節點故障後,叢集整體是否還能響應客戶端的讀寫請求。
分割槽容忍性(P):叢集中的某些節點在無法聯絡後,叢集整體是否還能繼續進行服務。
而 CAP 理論就是說在分散式儲存系統中,最多隻能實現上面的兩點。再加之當前的網路硬體肯定會出現延遲丟包等問題,所以分割槽容忍性是我們必須需要實現的。結果就是我們只能在一致性和可用性之間進行權衡,沒有 NoSQL 系統能同時保證這三點。
對一致性的保證,通常有強一致性和弱一致性的選擇,而在弱一致性裡,又以最終一致性的實現較為普遍。
如果我們採用 NRW 的設定,N為資料需要備份的份數,R為讀操作需要讀到的不同節點上的資料份數,W為寫操作需要成功寫到不同節點的資料份數,那麼當R+W>N時,既 是強一致性的保證,當R+W<N時,就是弱一致性。在弱一致性中,可以通過 vector clock 多版本控制等方法,來實現資料的最終一致性。
寫在最後的話
目前 NoSQL 系統來處在它的萌芽期,我們上面討論到的很多 NoSQL 系統,它們的架構、設計和介面可能都會改變。本章的目的,不在於讓你瞭解這些 NoSQL 系統目前是如何工作的,而在於讓你理解這些系統之所以這樣實現的原因。NoSQL 系統把更多的設計工作留給了應用開發工作者來做。理解上面這些元件的架構,不僅能讓你寫出下一個 NoSQL 系統,更讓你對現有系統應用得更好。
(編者注:本文根據 NoSQLFan 網站原載同名文章 http://blog.nosqlfan.com/html/2171.html 整理而成,英文原文連結為 http://www.aosabook.org/en/nosql.html)