過去幾年來,出現了一種稱爲 NoSQL 的新型數據庫管理系統。設計這些數據存儲是爲了克服在擴展傳統關係數據庫來處理一些應用程序時必須處理的數據負載類型的難題,好比說 Amazon。這種可伸縮性的實現須要必定的代價:NoSQL 系統一般不符合 ACID(原子性、一致性、隔離和耐久性);它們最終一致地代表,只要給定必定量的時間,全部數據更新最終都會經過該系統傳播。這不符合某些類型的應用程序的要求。html
過去用於在線事務處理 (OLTP) 的關係數據庫管理系統確實提供了一致性保證(它們符合 ACID),但其擴展難度更大,且成本更高。研究還代表,它們的效率也不是特別高:CPU 花費了大約 10% 的時間來檢索和更新記錄,剩餘 90% 用於處理緩衝區管理、鎖定、閂鎖 (Latch) 和日誌記錄等任務。java
傳統的關係數據庫(好比 MySQL)和大多數 NoSQL 系統將其數據存儲在磁盤上。VoltDB 將全部內容存儲在主要內存中。若是能夠避免進入磁盤,就能夠得到顯著的性能提高,訪問內存比訪問磁盤快一個數量級。現在的 RAM 成本比過去廉價得多,再結合 64 位計算的出現,您可配備一個具備數百 GB 主要內存的、標準的、即買即用的服務器。sql
VoltDB 數據庫由大量分散在多個站點(服務器)上的分區組成。一個站點上運行的每一個分區是單線程的,這消除了與典型多線程環境中的鎖定和閂鎖有關的開銷,事務請求會按順序執行。數據庫
NoSQL 數據庫(顧名思義)沒有使用 SQL 做爲查詢語言。例如,MongoDB 查詢使用 JSON 表達。Riak 和 CouchDB 都支持使用 map/reduce 功能進行查詢。VoltDB 使用 SQL 做爲其查詢語言,大多數使用過數據庫的開發人員都熟悉 SQL,從這個意義上講,這是一種優點。但一些 NoSQL 數據庫所提供的查詢接口並不是如此。json
對 VoltDB 中存儲的數據的訪問是經過使用 Java 語言編寫的存儲過程來完成的;SQL 語句嵌入在一個存儲過程當中。相對於 JDBC 等協議,從存儲過程內執行 SQL 查詢的一個優點是:每一個事務只需在客戶端與服務器之間往返一次。這消除了與經過網絡在應用程序與數據庫之間進行屢次調用相關聯的延遲。api
VoltDB 有兩個版本:一個開源社區版本和一個付費企業版本。本文將重點介紹社區版本。一些特性只有企業版中才有,這裏不會介紹這些特性。緩存
回頁首服務器
要嘗試使用本文中的一些示例,您須要下載並安裝 VoltDB。本文中使用的版本是 2.5 版的社區版本。網絡
VoltDB 須要一個基於 64 位 Linux 的操做系統;此要求也適用於 Mac OSX 10.6。您還須要安裝 Java 開發工具包 (JDK 6)。可使用 Eclipse 來編輯源代碼。請參閱 參考資料,獲取下載頁面的連接和完整的系統需求列表。多線程
Amazon EC2 和 VMware 映像也可供下載,下載它們以後就能夠當即正常使用它們。
VoltDB 以 tar 壓縮文件的形式分發,因此在下載它以後,可使用如下命令解壓:$ tar -zxvf voltdb-2.5.tar.gz -C ~/
。
在此實例中,我選擇將它安裝在個人主目錄中,這很是適用於開發用途,您還能夠將它解壓到您選擇的目錄中。
解壓以後,將 bin 目錄添加到您的路徑中:$ export PATH=$PATH:~/voltdb-2.5/bin
。
bin 目錄包含一些命令,這些命令在您部署示例應用程序時會頗有用。
接下來,下載本文配套的源代碼。將它解壓到您選擇的目錄中。示例應用程序主要處理一家虛構公司 Acme Inc 的員工。
一個典型的 VoltDB 應用程序由如下文件組成:
一個項目定義文件 (project.xml),其中包含哪些存儲過程可用、數據庫模式文件的位置、分區信息等信息。
一個部署文件 (deployment.xml),其中包含每一個主機的站點數等信息。
數據庫模式 (ddl.sql)。
源代碼,例如:存儲過程和客戶端。
本文將更詳細地介紹每一個文件。
要將項目導入 Eclipse 中,請打開 Eclipse,而後執行如下操做:
選擇 File>New>Project。
選擇 Java Project from Existing Ant Buildfile,而後單擊 Next。
勾選複選框 Link to the build file in the file system。
從您剛安裝示例應用程序的目錄選擇 build.xml 做爲 Ant 生成文件,而後選擇 Finish。
若是但願建立您本身的應用程序,VoltDB 提供了一個工具來爲您生成框架項目;該項目用於生成本文中附帶的應用程序的文件夾結構。
清單 1 展現瞭如何調用它。
$ cd $HOME/voltdb-2.5/tools $ ./generate app acme $HOME/Projects/app
該工具按如下順序接受多個參數:
應用程序的名稱
(Java 代碼的)包名稱
建立項目的位置
運行 清單 1 中的命令並查看新建立的文件夾。您將看到該工具生成了一個框架項目,其中包含構建一個 VoltDB 應用程序所需的文件。
簡介中已經提到,數據訪問是使用 Java 代碼編寫的存儲過程來實現的。相似於傳統的 RDBMS,您仍然必須編寫 SQL 查詢,以便從適當的表中獲取所需的數據。剛纔已從存儲過程內完成此任務。對存儲過程的每次調用都是一個事務;若是調用成功,則會提交存儲過程,不然它們會回滾。
因爲事務的順序性質,在建立存儲過程時必定要記住,應儘量快地執行它們;不然它們將阻止其餘等待運行的事務。例如,在存儲過程當中避免發送電子郵件或對數據執行復雜分析等任務。
清單 2 提供了一個存儲過程示例,它將一個條目插入 employee 表中。
@ProcInfo ( partitionInfo = "EMPLOYEE.EMAIL: 0", singlePartition = true ) public class AddEmployee extends VoltProcedure { public final SQLStmt addemployee = new SQLStmt( "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?);" ); public VoltTable[] run(String email, String firstname, String lastname, int department) throws VoltAbortException { voltQueueSQL(addemployee, email, firstname, lastname, department); voltExecuteSQL(true); return null; } }
一個存儲過程必須擴展類 VoltProcedure
並實現 run
方法,該本例中,這會使用傳遞給該方法的參數將一個條目插入 employee 表中。選擇、更新和刪除也遵循相似的模式。
調用一個存儲過程以前,應用程序須要建立與數據庫的鏈接。在創建鏈接時,須要指定運行數據庫的主機名稱;若是運行一個集羣,則能夠指定該集羣中的任何節點。清單 3 展現瞭如何建立與數據庫的鏈接。
// Create a client and connect to the database org.voltdb.client Client client; client = ClientFactory.createClient(); client.createConnection("localhost");
在創建與數據庫的鏈接後,就能夠查詢數據庫。清單 4 中的代碼展現瞭如何調用 AddEmployee
存儲過程,該存儲過程向 employee 表添加了一些條目。
client.callProcedure("AddEmployee", "wile@acme.com", "Wile", "Coyote", 1); client.callProcedure("AddEmployee", "larry@acme.com", "Larry", "Merchant", 2);
請注意,要調用的過程名稱 (AddEmployee
) 與實現存儲過程的 Java 類的名稱是匹配的。
從 AddEmployee
存儲過程當中能夠看到,這裏使用了 SQL 來查詢數據庫。VoltDB 僅支持 SQL 的一個子集。若是但願將現有應用程序遷移到 VoltDB,那麼有可能須要重寫一些 SQL 查詢。請參閱 參考資料,獲取描述 VoltDB 支持的 SQL 子集的頁面連接。
存儲過程當中的 SQL 語句必須提早聲明,但能夠在查詢中使用綁定變量。可在運行時對數據庫運行臨時查詢,例如一個具備動態字段的 SQL 查詢,只需調用 @AdHoc 系統過程(參見 清單 5)。咱們不建議這樣作,由於查詢沒有優化,而且會以多分區事務的形式執行,這可能會影響性能。
String tableName = "EMPLOYEE"; VoltTable[] count = client.callProcedure("@AdHoc", "SELECT COUNT(*) FROM " + tableName).getResults(); System.out.printf("Found %d employees.\n", count[0].fetchRow(0).getLong(0));
最後,存儲過程必須在項目文件 (project.xml) 中聲明。若是打開本文隨帶的 項目文件,就會看到一些相似 清單 6 的條目。
<procedures> <procedure class="acme.procedures.AddEmployee" /> ... </procedures>
存儲過程的另外一個重要部分是註釋 @ProcInfo,它向 VoltDB 告知數據在數據庫中的存儲方式。這稱爲分區,接下來咱們將會討論它。
分區指表數據分散在整個集羣中;一個表中的每一行在各個分區中分開存儲。表基於您(開發人員)指定的一個主鍵進行分區。分區的主要目的是讓儘量多的查詢在當站點上運行。
就像存儲過程同樣,您還必須在項目定義文件中聲明分區信息。例如,清單 7 中所示的條目代表,employee 表中的條目分區在 email 列上。
<partitions> <partition table='EMPLOYEE' column='EMAIL' /> ... </partitions>
回頭看一個將某位員工的數據插入數據庫中的存儲過程,存儲過程上的註釋相似於 清單 8。
@ProcInfo ( partitionInfo = "EMPLOYEE.EMAIL: 0", singlePartition = true )
這告訴 VoltDB 使用 employee 表的 email 列做爲分區鍵,而且它是傳遞給 run 方法的第一個參數。在引用參數時使用以 0 開頭的編號。它還代表該條目位於單個分區上。
選擇用來對數據進行分區的鍵很重要,由於不使用分區鍵的查詢會在多個分區上執行;在一個分區上運行的查詢使其餘分區可以(並行)執行其餘查詢,實現更高的吞吐量。
例如,假設您決定使用員工的 EMAIL(分區鍵)對 employee 表進行分區。如下查詢將在單個分區上運行:SELECT FIRSTNAME, LASTNAME FROM EMPLOYEE WHERE EMAIL = "bob@acme.com";
。
由於每一個員工的電子郵件地址是唯一的,全部只有一位員工擁有指定的電子郵件地址。可是,若是某個查詢使用了不是分區鍵的字段,那麼該查詢將會在全部分區上執行(一種多分區查詢),這會帶來更低的總吞吐量:SELECT EMAIL FROM EMPLOYEE WHERE LASTNAME = "Smith"
。
這是由於多個員工可能擁有名字 「Smith」,這個名字並非唯一的,因此必須查詢全部分區。
出於這個緣由,首先應該設計一組查詢(和執行它們的頻率),而後對錶進行相應的分區,以便可以在一個站點上執行儘量多的查詢。
除了分區表,也能夠跨全部站點複製表。例如,將一個表添加到模式中,以記錄虛構公司 Acme Inc 中存在的部門。表定義 (ddl.sql) 相似於 清單 9。
CREATE TABLE DEPARTMENT ( DEPARTMENT_ID INTEGER NOT NULL, NAME VARCHAR(100) NOT NULL, PRIMARY KEY (DEPARTMENT_ID) );
還須要向 employee 表添加一個表示員工所在部門的列。
department 表是跨全部站點複製的理想的候選表,由於(至少本文中的)公司中擁有較少的部門且該表一般是隻讀的。經過複製表而不是對其分區,您可回答 「擁有電子郵件地址 ‘bob@acme.com’ 的員工所工做的部門叫什麼?」 這樣的查詢,還能夠避免跨多個分區執行聯接 (join) 的須要 — 回想一下員工數據分隔到員工電子郵件字段上的狀況。
請參見本文隨帶的 源代碼 中的 EmployeeDetails.java,得到一個對 department 表執行聯接的查詢示例。
要告訴 VoltDB 複製一個表而不是對其分區,必須將該表的名稱從項目定義文件 (project.xml) 的分區節中排除,在存儲過程上聲明 @ProcInfo 註釋,就像 清單 10 同樣。
@ProcInfo ( singlePartition = false )
這會獲得一個複製的表,而不是一個分區的表。
要運行本文隨帶的應用程序,首先須要編譯源代碼並生成運行時目錄。在項目文件夾的 root 目錄中,運行命令 $ ant compile
。
此命令將編譯源代碼並生成運行時目錄 (acme.jar)。
要啓動數據庫,請從項目的 root 目錄運行 清單 11 中的命令。
$ voltdb start \ leader localhost \ catalog acme.jar \ deployment deployment.xml
還能夠運行如下命令一次性地編譯源代碼,生成運行時目錄和啓動服務器:$ ant server
。
如今數據庫服務器正在運行,能夠對它執行一些查詢。打開一個新終端窗口,從項目目錄中啓動客戶端應用程序:$ ant client
。
這會啓動將對數據庫運行一些查詢的客戶端。請查看實現客戶端的代碼 (Client.java),以瞭解它的用途。
在運行數據庫以後,能夠經過分別關閉集羣中的每一個節點來中止它。因爲本文中的應用程序僅在本地機器上運行,因此這沒什麼問題,您只須要在啓動數據庫的終端窗口中按下 Control-C 便可。若是您有一個集羣包含幾個節點,分別關閉每一個節點可能會很麻煩。VoltDB 提供了一個 @Shutdown 過程,它將爲您關閉整個數據庫集羣(參見 清單 12)。
try { client.callProcedure("@Shutdown"); } catch (Exception e) { // An exception is expected here as // when the database server is shut down // it breaks the database connection to the client. System.out.println("Shutdown request has been sent."); }
要中止數據庫,能夠打開一個新終端窗口,從項目目錄運行如下命令:$ ant shutdown
。
注意:關閉任務已添加到本文的 build.xml 文件中。若是您未使用本文隨帶的代碼,則必須將它添加到您的生成文件中。
本節將探討 VoltDB 如何實現耐久性,介紹如何備份數據庫來防止發生故障時丟失數據。
簡介中已經提到,VoltDB 符合 ACID。耐久性需求(ACID 中的 「D」)表示,在提交一個事務以後,它的狀態將保持不變,甚至在停電或發生系統故障時也是如此。換句話說,您仍然擁有您的數據。
值得一提的是,VoltDB 如何實現了耐久性,它畢竟是一個內存數據庫。若是數據庫出於某種緣由而關閉,那麼全部數據都會從內存中刪除;畢竟內存是一種易失性存儲媒介。VoltDB 使用快照來保存數據。
快照的名稱準確表達了它的用途:數據庫中存儲的數據在給定時間點的快照。能夠將 VoltDB 配置爲以固定時間間隔自動建立快照,並將這些快照持久存儲到磁盤上。當數據庫出於某種緣由而關閉時,可以使用快照將數據庫返回到它關閉前的狀態。爲此,在項目目錄中打開部署文件 deployment.xml 並編輯它,相似於 清單 13:
<?xml version="1.0"?> <deployment> <cluster hostcount="1" sitesperhost="2" /> <paths> <voltdbroot path="/tmp" /> <snapshots path="/tmp/autobackup" /> </paths> <httpd enabled="true"> <jsonapi enabled="true" /> </httpd> <snapshot prefix="acmesave" frequency="2m" retain="3" /> </deployment>
清單 13 告訴 VoltDB 每隔兩分鐘在 /tmp/autobackup 中建立一個備份並保留最後 3 個快照:在達到指定的保留限制時,會刪除舊快照。在實際中,快照很是適合保存到網絡掛載的位置(使用 NFS),以確保它們存儲在一個不一樣的位置。
保存該文件,而後從新啓動數據庫。這時快照還沒有啓用,因此全部最新數據(運行客戶端時添加的數據)都將丟失。您須要在數據庫再次正常運行時再次運行客戶端從新加載數據。幾分鐘後,文件夾 /tmp/autobackup 應包含數據庫的快照。
再次關閉數據庫,但這一次在啓動它時使用恢復選項。當啓用快照並指定了恢復選項時,VoltDB 會自動使用快照路徑中最新的快照,將數據庫還原到之前的狀態。可是,請注意,若是嘗試使用恢復選項啓動數據庫但沒有找到快照,那麼數據庫不會啓動。
要告訴 VoltDB 將數據庫還原到之前的狀態,能夠運行 清單 14 中的命令。
$ voltdb recover \ leader localhost \ catalog acme.jar \ deployment deployment.xml
若是再次運行客戶端 (ant client),這一次它總共會找到 5 個員工記錄。當快照還沒有啓用且數據庫已啓動的時候,客戶端第一次運行時員工總數爲 0。
如今快速看一下一個構建於 VoltDB 之上的真實應用程序:VoltCache。
VoltDB 發行版帶來了許多示例,其中一個就是 VoltCache。VoltCache 是一個在 VoltDB 上實現的鍵值存儲,可經過一個兼容 memcached 的 API 訪問它,memcached 是一種流行的分佈式緩存系統。只需執行兩個步驟就能讓它正常運行。
首先,啓動 VoltDB 應用程序。要啓動服務器,能夠執行 清單 15 中的命令。
$ cd ~/voltdb-2.5/examples/voltcache $ ./run.sh server
若是有必要的話,這會生成源代碼並啓動 VoltDB。接下來,在來自不一樣終端窗口的相同目錄中運行如下命令:$ ./run.sh memcached-interface
。
這將啓動模仿 memcached API(文本協議)的應用程序,它在端口 11211(memcached 服務器的默認端口)上進行監聽。
清單 16 給出了一個 telnet 會話示例,您應在其中將鍵 foo
與值 bar
相關聯,並再次檢索它。
$ telnet localhost 11211 set foo 0 60 3 bar STORED get foo VALUE foo 0 3 0 bar END quit
還可使用已提供的許多 memcache 客戶端庫中的某個庫。
該應用程序的源代碼包含在 VoltDB 發行版中,咱們能夠看看它的工做原理。
VoltDB 是一個內存數據庫,它提供了可伸縮性,但並無在數據一致性上妥協。本文簡要探討了 VoltDB 的一些特性。您能夠體驗它的更多特性,好比導出實時數據、異步過程調用和 JSON API,這些特性支持您直接將 VoltDB 與一個 Web 應用程序進行集成。