了解 Oracle Berkeley DB 可以為您的應用程序帶來 NoSQL 優(yōu)勢的原因及方式。
2011 年 2 月發(fā)布
“NoSQL”是在開發(fā)人員、架構師甚至技術經(jīng)理中新流行的一個詞匯。盡管這個術語最近很流行,但令人驚訝的是,它并沒有一個普遍認可的定義。
通常來說,任何非 RDBMS 且遵循無模式結(jié)構的數(shù)據(jù)庫一般都不能完全支持 ACID 事務,并且因高可用性的承諾以及在橫向伸縮環(huán)境中支持大型數(shù)據(jù)集而普遍被歸類為“NoSQL 數(shù)據(jù)存儲”。鑒于這些共同特征(與傳統(tǒng)的 RDBMS 的特征形成鮮明對比),有人提議非關系(或者簡稱為 NonRel)是比 NoSQL 更為恰當?shù)男g語。
盡管定義沖突仍然存在,但很多人已經(jīng)意識到將 NoSQL 加到其應用程序體系中的好處。其他人正保持密切關注并評估 NoSQL 是否適合他們。
NoSQL作為一個類別的發(fā)展還導致了大量新數(shù)據(jù)存儲的出現(xiàn)。其中某些新的 NoSQL 產(chǎn)品擅長持久保存像 JSON這樣的文檔,某些按照列家族存儲排序,其他的則持久保存分布式鍵值對。盡管更新的產(chǎn)品令人興奮并且提供了很多好用的功能,但一些現(xiàn)有產(chǎn)品也在奮起直追履行新的承諾。
Oracle Berkeley DB就是這樣一個數(shù)據(jù)存儲。在文本中,我將解釋并說明為什么可以將 Berkeley DB 作為 NoSQL解決方案包括在體系中以及具體實現(xiàn)方式。本文重點關注 Berkeley DB 圍繞 NoSQL 的特性,因此不會詳盡涵蓋 Berkeley DB的所有功能和特性。
基本上,鍵值存儲 Berkeley DB 有三種不同風格:
Berkeley DB — 用 C 編寫的鍵值存儲。(Berkeley DB 官方文檔使用術語鍵-數(shù)據(jù) 代替鍵值。)這是“經(jīng)典”風格。
Berkeley DB Java 版 (JE) — 用 Java 重新編寫的鍵值存儲??梢暂p松包含在 Java 堆棧中。
Berkeley DB XML — 用 C 編寫,此版本將鍵值存儲進行包裝,使其行為類似于一個已建立索引并且經(jīng)過優(yōu)化的 XML 存儲系統(tǒng)。
(注意:盡管本文沒有明確涉及 Berkeley DB JE 或 Berkeley DB XML,但是包括了一些使用 Java API 和基于 Java 的持久性框架來說明 Berkeley DB 功能的示例。)
Berkeley DB 的核心可能很簡單,可以將它配置為提供并行非阻塞訪問或支持事務,橫向擴展為一個主從副本的高可用集群或者以多種其他方式橫向擴展。
BerkeleyDB 是一個純存儲引擎,不對鍵值對的隱式模式或結(jié)構做任何假設。因此,Berkeley DB 輕松允許在底層鍵值存儲上實現(xiàn)更高級別的API、查詢和建模抽象。這有助于快速高效地存儲應用程序特定數(shù)據(jù),而不會產(chǎn)生將其轉(zhuǎn)換為抽象數(shù)據(jù)格式的開銷。這種簡單卻精致的設計所提供的靈活性能夠在Berkeley DB 中同時存儲結(jié)構化和半結(jié)構化數(shù)據(jù)。
Berkeley DB 可作為內(nèi)存中存儲來運行,以保存少量數(shù)據(jù),也可通過快速的內(nèi)存中緩存配置為大型數(shù)據(jù)存儲。在更高級別抽象(稱作環(huán)境)的幫助下,可以在一個物理安裝中配置多個數(shù)據(jù)庫。一個環(huán)境可以有多個數(shù)據(jù)庫。您需要打開一個環(huán)境,然后打開一個數(shù)據(jù)庫,向其中寫入數(shù)據(jù)或者從中讀取數(shù)據(jù)。建議您在完成交互后關閉數(shù)據(jù)庫和環(huán)境,從而以最佳方式使用資源。
數(shù)據(jù)庫中的每一項都是一個鍵值對。鍵通常是唯一的,但是您可以有重復的項。值是通過鍵來訪問的。可以更新檢索值并將其保存回到數(shù)據(jù)庫。通過游標對多個值進行訪問和迭代。游標使您可以循環(huán)遍歷值的集合以及同時操縱整個值集合。另外,還支持事務和并發(fā)訪問。
鍵值對的鍵幾乎總是充當建立索引的主鍵。值中的其他屬性可充當次索引。在輔助數(shù)據(jù)庫中單獨維護次索引。因此,具有鍵值對的主要數(shù)據(jù)庫有時候也被稱作主數(shù)據(jù)庫。
Berkeley DB 作為一個進程中數(shù)據(jù)存儲運行,因此在使用 C、C++、C#、Java 或腳本語言 API 從相應程序中訪問它時,您會以靜態(tài)或動態(tài)方式鏈接到它。
簡要介紹之后,下面將就 Berkeley DB 圍繞 NoSQL 的特性進行介紹。
NoSQL 存儲的第一個優(yōu)勢是其對定義明確的數(shù)據(jù)庫模式的寬松態(tài)度。我們來看看 Berkeley DB 如何實現(xiàn)此特性。
為了理解 Berkeley DB 的功能,建議您試用一下。因此,建議將 Berkeley DB 和 Berkeley DB JE 下載并安裝到您的計算機上,這樣您能親自嘗試一些示例并跟隨本文中其余例證的操作。此處在線提供了下載鏈接和安裝說明。(在本文中,我使用 --enable-java、--enable-sql 和 --prefix=/usr/local對 Berkeley DB 進行了編譯。)與存儲、訪問機制和 API 有關的基本概念在 Berkeley DB 和 Berkeley DBJE 之間沒有太大區(qū)別,因此我后面涉及到的大部分內(nèi)容同樣適用于這兩者。
除了數(shù)據(jù)項必須是鍵值對集合之外,Berkeley DB本身對數(shù)據(jù)項的限制非常少。這就使得應用程序可以靈活地使用 Berkeley DB 管理各種格式的數(shù)據(jù),包括 SQL、XML 和 Java對象。您可以通過基礎 API、SQL API、Java Collections API 以及 Java Direct PersistenceLayer (DPL) 訪問 Berkeley DB 中的數(shù)據(jù)。它允許幾種不同存儲配置:B 樹、散列、隊列和 Recno。(BerkeleyDB 文檔將不同存儲機制稱作“訪問方法”。散列、隊列和 Recno 訪問方法僅在 Berkeley DB 中可用,在 Berkeley DBJE 或 Berkeley DB XML 中不可用。)
您可以根據(jù)具體用例來選擇訪問機制和存儲配置。選擇特定的訪問方法和存儲配置會影響模式。要了解您的選擇所造成的影響,您需要先了解您所選的內(nèi)容。我接下來要談到訪問方法和存儲配置。
基礎 API 是低級別 API,使您可以存儲、檢索和更新數(shù)據(jù)(即鍵值對)。這種 API 在幾種不同語言綁定之間是類似的。因此,C、C++ 和Java 的基礎 API 是完全相同的。另一方面,DPL 和 Java Collections API 僅作為抽象在 Java API 中提供。
基礎 API 可放置、獲取和刪除鍵值對。鍵和值均為字節(jié)數(shù)組。在存儲所有鍵和數(shù)據(jù)值之前,會將其序列化為字節(jié)數(shù)組。您可以使用 Java的內(nèi)置序列化程序或 Berkeley DB 的 BIND API 將各種數(shù)據(jù)類型序列化為字節(jié)數(shù)組。Java的內(nèi)置序列化程序通常執(zhí)行速度較慢,因此用戶必定更喜歡 BIND API。(jvm-serializers項目對各種替代序列化程序進行基準測試,是用于在 JVM 的不同序列化機制之間分析相對性能的一個很好的參照點。)BIND API 可通過每個序列化類來避免冗余存儲類信息,將該信息放在單獨的數(shù)據(jù)庫中。通過編寫您自己的自定義字節(jié)組綁定來提高 BIND API 性能,您可以潛在地提高速度。
作為一個基本示例,您可以定義如下數(shù)據(jù)值:
import java.io.Serializable;public class DataValue implements Serializable {private long prop1;private double prop2;DataValue() {prop1 = 0;prop2 = 0.0;}public void setProp1(long data) {prop1 = data;}public long getProp1() {return prop1;}public void setProp2(double data) {prop2 = data;}public double getProp2() {return prop2;}}
現(xiàn)在,您可以使用兩個數(shù)據(jù)庫來存儲此數(shù)據(jù)值,一個數(shù)據(jù)庫存儲帶有鍵的值,另一個數(shù)據(jù)庫存儲類信息。
使用四個不同步驟來存儲數(shù)據(jù):
首先,除了用于存儲鍵值對的數(shù)據(jù)庫之外的另一個數(shù)據(jù)庫配置為存儲類數(shù)據(jù),如下所示:
Database aClassDB = new Database("classDB", null, aDbConfig);
StoredClassCatalog storedClassCatalog = new StoredClassCatalog(aClassDb);
EntryBinding binding = new SerialBinding(storedClassCatalog, DataValue.class);
DataValue val = new DataValue(); val.setProp1(123456789L); val.setProp2(1234.56789);
使用您剛創(chuàng)建的綁定映射到 Berkeley DB DatabaseEntry(充當鍵和值的包裝器),如下所示:
DatabaseEntry deKey = new DatabaseEntry(aKey.getBytes("UTF-8")); DatabaseEntry deVal = new DatabaseEntry(); binding.objectToEntry(val, deVal);
現(xiàn)在,您可以將鍵值對放入 Berkeley DB 中。
基礎 API 支持 put 和 get 方法的幾種變體,以允許或不允許重復項和覆蓋。(該示例以及本文都不是為了要教您有關如何使用基礎 API 的詳細語法或語義,因此我將不會涉及更多細節(jié);請參閱這里的文檔)。一個要點是,基礎 API 允許就存儲、檢索和刪除鍵值對進行低級操作和自定義序列化。
如果偏向于使用更高級的 API 與 Berkeley DB 進行交互,那么您應使用 DPL。
直接持久層 (DPL) 提供了熟悉的 Java 持久性框架語義來操縱對象。您可以將 Berkeley DB視作一個實體存儲,對象在其中持久保存,并可對其中的對象進行檢索以便更新和刪除。DPL 使用批注將類標記為@Entity。使用實體進行存儲的相關聯(lián)的類被注釋為 @Persistent。特定屬性或變量可以注釋為 @PrimaryKey 和@SecondaryKey。一個簡單的實體可能如下所示:
@Entitypublic class AnEntity {@PrimaryKeyprivate int myPrimaryKey;@SecondaryKey(relate=ONE_TO_ONE)private String mySecondaryKey;...}
DPL 將類定義用作定義明確的模式。通過基礎 API,我們知道 Berkeley DB 不要求必須符合模式。但對于某些用例,正式的實體定義很有幫助并可為數(shù)據(jù)建模提供結(jié)構化方法。
正如前面所提到的,可以通過四種不同類型的數(shù)據(jù)結(jié)構存儲鍵值對:B 樹、散列、隊列和 Recno。我們來看看它們的效果如何。
盡 管 B 樹和散列均支持復雜鍵,但是當數(shù)據(jù)集遠超過可用內(nèi)存大小時,散列數(shù)據(jù)庫的性能通常優(yōu)于 B 樹。這是因為 B 樹比散列保存更多的元數(shù)據(jù),更大的數(shù)據(jù)集意味著 B 樹元數(shù)據(jù)可能無法存儲在內(nèi)存中緩存內(nèi)。在這種極端情況下,B 樹元數(shù)據(jù)以及實際數(shù)據(jù)記錄本身通常必須取自文件,而這會導致每個操作有多個 I/0。散列訪問方法旨在最大程度減少訪問數(shù)據(jù)記錄所需的 I/O 數(shù)量,因此在這些極端情況下,性能可能會優(yōu)于 B 樹。
不同配置使您可以在集合中存儲任意類型的數(shù)據(jù)。與 NoSQL類似,沒有固定模式(除了您的模型實施的模式)。在極端情況中,您可以在集合中針對兩個鍵分別存儲不同的值類型。值類型可以是復雜類,就參數(shù)而言,可以表示 JSON 文檔、復雜數(shù)據(jù)結(jié)構或結(jié)構化數(shù)據(jù)集。真正的唯一限制是,值應該序列化為字節(jié)數(shù)組。單個鍵或單個值最大可達 4GB。
次索引的出現(xiàn)允許根據(jù)值屬性進行篩選。主數(shù)據(jù)庫不會以表格格式來存儲數(shù)據(jù),因此不會為稀疏數(shù)據(jù)集存儲非現(xiàn)有屬性。如果鍵值對缺少用于創(chuàng)建索引的屬性,次索引會跳過所有此類鍵值對。一般來說,這種存儲方式既緊湊又高效。
BerkeleyDB 是一個非常靈活的數(shù)據(jù)庫,可以打開和關閉許多特性。Berkeley DB 可以在不支持事務的情況下運行,也可以編譯為支持 ACID事務完整性。也許,Berkeley DB 的可塑性使其成為非常適合許多情況的數(shù)據(jù)存儲。在典型的 NoSQL數(shù)據(jù)存儲中,對事務完整性的支持最差。在不期望 ACID 事務合規(guī)性的具有較高可用性的系統(tǒng)中,Berkeley DB 可以關閉事務,像典型的NoSQL 產(chǎn)品那樣工作。但是在其他系統(tǒng)中,它可能很靈活并且支持事務完整性。
盡管我并不打算涉及有關事務的細節(jié),但值得注意的是,像傳統(tǒng) RDBMS 系統(tǒng)一樣,支持事務的 Berkeley DB允許定義事務邊界。一旦提交,數(shù)據(jù)會持久保存到磁盤。為提高性能,您可以使用非持久性提交,這會將寫操作提交到內(nèi)存中日志文件,隨后與底層文件系統(tǒng)進行同步。還支持隔離級別和鎖定機制。
在數(shù)據(jù)庫關閉之前,同步操作可保證持久文件副本在系統(tǒng)中具有最新的內(nèi)存中信息。這種同步操作與 Berkeley DB 的事務恢復子系統(tǒng)的組合(假定您已啟用了事務)可確保數(shù)據(jù)庫始終返回到一致的事務狀態(tài),即使是在應用程序或系統(tǒng)發(fā)生故障時。
理論上,Berkeley DB 具有 256TB 的上限,但實際上,通常受運行 Berkeley DB的計算機的大小限制。截至撰寫本文時,Berkeley DB 未證實可在分布式文件系統(tǒng)的幫助下支持跨多臺計算機的極大文件。(可借助 Hadoop分布式文件系統(tǒng) (HDFS) 等分布式文件系統(tǒng)的幫助管理超過單個節(jié)點大小的文件。)Berkeley DB在本地文件系統(tǒng)上的性能優(yōu)于在網(wǎng)絡文件系統(tǒng)上的性能。更準確地說,Berkeley DB 依賴文件系統(tǒng)的 POSIX 兼容屬性。例如,當Berkeley DB 調(diào)用 fsync() 并且文件系統(tǒng)返回時,Berkeley DB假定數(shù)據(jù)已寫入到持久介質(zhì)。出于性能原因,分布式文件系統(tǒng)通常不保證自始至終完成向持久介質(zhì)的寫入。
所支持的最大 B 樹深度為 255。鍵和值的長度通常受可用內(nèi)存的限制。
BerkeleyDB復制遵循主從模式。在此類模式中,有一個主節(jié)點和多個從屬節(jié)點(或副本)。但是,主節(jié)點的選擇不是靜態(tài)的,并且不建議手動選擇。復制集群中的所有參與節(jié)點都要經(jīng)歷一個選舉過程以選出主節(jié)點。具有最新日志記錄的參與節(jié)點將成為獲勝者。如果具有綁定,那么優(yōu)先級用于選擇主節(jié)點。選舉過程基于行業(yè)標準的符合 Paxos 的算法。
復制具有很多好處,包括:
提高讀性能 — 可從多個副本節(jié)點中讀取數(shù)據(jù)極大提高了讀性能。
提高可靠性 — 有了副本實例,就可以在發(fā)生節(jié)點故障和數(shù)據(jù)損壞時提供更好的故障轉(zhuǎn)移選擇。
提高持久性 — 您可以放寬對主節(jié)點的持久性保證以避免過多地對磁盤進行寫入操作,這通常需要昂貴的 I/O。在集群環(huán)境中,通過將寫入提交到多個節(jié)點(即使未寫入到磁盤)這一事實增強了持久性。
提高可用性 — 由于有多個節(jié)點并且對磁盤進行異步寫入,即使在主節(jié)點負載過高的情況下,副本節(jié)點仍可繼續(xù)提供服務。
毫無疑問,Berkeley DB 作為一個強健、可伸縮的 NoSQL 鍵值存儲非常合格;Amazon 的 Dynamo、Project Voldemort、MemcacheDB 和 GenieDB 使用 Berkeley DB 作為底層存儲就是支持這一觀點的進一步證據(jù)。圍繞 Berkeley DB 性能一直存在一些恐懼,尤其是下面這兩個在線發(fā)布的比較基準測試:
http://www.dmo.ca/blog/benchmarking-hash-databases-on-large-data/
http://stackoverflow.com/questions/601348/berkeleydb-vs-tokyo-cabinet
但是,很多運行中的系統(tǒng)證明了 Berkeley DB 的強大。其中許多系統(tǒng)經(jīng)過了仔細的調(diào)整和應用程序編碼改進,已經(jīng)獲得了出色的可伸縮性、吞吐量和可靠性結(jié)果。效法這些系統(tǒng),Berkeley DB 無疑可用作可伸縮的 NoSQL 解決方案。
Shashank Tiwari 是 Treasury of Ideas(一家技術驅(qū)動的創(chuàng)新和價值優(yōu)化公司)的創(chuàng)始人兼CEO。作為一名經(jīng)驗豐富的軟件開發(fā)人員和架構師,他精通大量技術。他是國際認可的演講者、作者和導師。作為數(shù)種 JCP (JavaCommunity Process) 規(guī)范的專家組成員,他一直積極參與規(guī)劃 Java 的未來。他還代表了 NoSQL 和云計算領域的心聲,是RIA 社區(qū)公認的專家。