Riak简介(1)
第 1 部分: 與語言無關的 HTTP API ??http://www.ibm.com/developerworks/cn/opensource/os-riak1/
簡介
典型的現代關系數據庫在某些類型的應用程序中表現平平,難以滿足如今的互聯網應用程序的性能和可擴展性要求。因此,需要采用不同的方法。在過去幾年中,一種新的數據存儲類型變得非常流行,通常稱為 NoSQL,因為它可以直接解決關系數據庫的一些缺陷。Riak 就是這類數據存儲類型中的一種。
Riak 并不是惟一的一種 NoSQL 數據存儲。另外兩種較流行的數據存儲是 MongoDB 和 Cassandra。盡管在許多方面十分相似,但是它們之間也存在明顯的不同。例如,Riak 是一種分布式系統,而 MongoDB 是一種單獨的系統數據庫,也就是說,Riak 沒有主節點的概念,因此在處理故障方面有更好的彈性。盡管 Cassandra 同樣是基于 Amazon 的 Dynamo 描述,但是它在組織數據方面摒棄了向量時鐘和相容散列等特性。Riak 的數據模型更加靈活。在 Riak 中,在第一次訪問 bucket 時會動態創建這些 bucket;Cassandra 的數據模型是在 XML 文件中定義的,因此在修改它們過后需要重啟整個群集。
Riak 的另一個優勢是它是用 Erlang 編寫的。而 MongoDB 和 Cassandra 是用通用語言(分別為 C++和 Java)編寫,因此 Erlang 從一開始就支持分布式、容錯應用程序,所以更加適用于開發 NoSQL 數據存儲等應用程序,這些應用程序與使用 Erlang 編寫的應用程序有一些共同的特征。
Map/Reduce 作業只能使用 Erlang 或 JavaScript 編寫。對于本文呢,我們選擇使用 JavaScript 編寫?map?和?reduce?函數,但是也可以用 Erlang 編寫它們。雖然 Erlang 代碼的執行速度可能稍快一些,然而我們選擇 JavaScript 代碼的理由是它的受眾更廣。參閱?參考資料?中的鏈接,詳細了解 Erlang。
開始
如果您希望嘗試本文中的一些示例,則需要在您的系統中安裝 Riak(參閱?參考資料)和 Erlang。
您還需要構建一個包含三個節點的群集并在您的本地機器上運行它。Riak 中保存的所有數據都被復制到群集的大量節點中。數據所在的 bucket 的一個屬性 (n_val) 決定了將要復制的節點的數量。該屬性的默認值為 3,因此,要完成本示例,我們需要創建一個至少包含三個節點的群集(之后您可以創建任意數量的節點)。
下載了源代碼后,您需要進行構建。基本步驟如下:
這將構建 Riak (./rel/riak)。要在本地運行多個節點,則需要生成 ./rel/riak 的副本,對每個額外的節點使用一個副本。將 ./rel/riak 復制到 ./rel/riak2、./rel/riak3 等地方,然后對每個副本執行下面的修改:
- 在 riakN/etc/app.config 中,修改下面的值:http{} 部分中指定的端口,handoff_port 和 pb_port,將它們修改為惟一值
- 打開 riakN/etc/vm.args 并修改名稱,同樣是修改為惟一值,例如?-name riak2@127.0.0.1
現在依次啟動每個節點,如?清單 1?所示。
清單 1. 清單 1. 啟動每個節點
$ cd rel $ ./riak/bin/riak start $ ./riak2/bin/riak start $ ./riak3/bin/riak start最后,將節點連接起來形成群集,如?清單 2?所示。
清單 2. 清單 2. 形成群集
$ ./riak2/bin/riak-admin join riak@127.0.0.1 $ ./riak3/bin/riak-admin join riak@127.0.0.1您現在應該創建了一個在本地運行的 3 節點群集。要進行測試,運行如下命令:?$ ./riak/bin/riak-admin status | grep ring_members。
您應當看到,每個節點都是剛剛創建的群集的一部分,例如?ring_members : ['riak2@127.0.0.1','riak3@127.0.0.1','riak@127.0.0.1']。
Riak API
目前有三種方式可以訪問 Riak:HTTP API(RESTful 界面)、Protocol Buffers 和一個原生 Erlang 界面。提供多個界面使您能夠選擇如何集成應用程序。如果您使用 Erlang 編寫應用程序,那么應當使用原生的 Erlang 界面,這樣就可以將二者緊密地集成在一起。其他一些因素也會影響界面的選擇,比如性能。例如,使用 Protocol Buffers 界面的客戶端的性能要比使用 HTTP API 的客戶端性能更高一些;從性能方面講,數據通信量變小,解析所有這些 HTTP 標頭的開銷相對更高。然而,使用 HTTP API 的優點是,如今的大部分開發人員(特別是 Web 開發人員)非常熟悉 RESTful 界面,再加上大多數編程語言都有內置的原語,支持通過 HTTP 請求資源,例如,打開一個 URL,因此不需要額外的軟件。在本文中,我們將重點介紹 HTTP API。
所有示例都將使用 curl 通過 HTTP 界面與 Riak 交互。這樣做是為了更好地理解底層的 API。許多語言都提供了大量客戶端庫,在開發使用 Riak 作為數據存儲的應用程序時,應當考慮使用這些客戶端庫。客戶端庫提供了與 Riak 連接的 API,可以輕松地與應用程序集成;您不必親自編寫代碼來處理在使用 curl 時出現的響應。
API 支持常見的 HTTP 方法:GET、PUT、POST、DELETE,它們將分別用于檢索、更新、創建和刪除對象。我們稍后將依次介紹每一種方法。
存儲對象
您可以將 Riak 看成是創建鍵(字符串)與值(對象)的分布式映射。Riak 將值保存在 bucket 中。在保存對象之前,不需要顯式地創建 bucket;如果將對象保存到一個不存在的 bucket 中,則會自動創建該 bucket。
Bucket 在 Riak 中是一個虛擬概念,主要是為了對相關對象分組而存在。bucket 還具有其他一些屬性,這些屬性的值定義了 Riak 對存儲在其中的對象的處理。下面是 bucket 屬性的一些示例:
- n_val:對象在群集內進行復制的次數
- allow_mult:是否允許并發更新
您可以通過對 bucket 發出?GET?請求查看 bucket 的屬性(及其當前值)。
要存儲對象,我們將對?清單 3?所示的其中一個 URL 執行 HTTP?POST。
清單 3. 清單 3. 存儲對象
POST -> /riak/<bucket> (1) POST -> /riak/<bucket>/<key> (2)鍵可以由 Riak (1)自動分配,或由用戶 (2) 定義。
當使用用戶定義的鍵存儲對象時,也可以向 (2) 執行一個 HTTP?PUT?操作來創建對象。
Riak 的最新版本還支持以下 URL 格式:/buckets/<bucket>/keys/<key>,但是在本文中,我們將使用更舊的格式來維持與早期 Riak 版本的向后兼容性。
如果沒有指定鍵,Riak 會自動為對象分配一個鍵。例如,我們將在 bucket “foo” 中存儲一個明文對象,并且不會顯式指定鍵(參見?清單 4)。
清單 4. 清單 4. 在不顯式指定鍵的情況下存儲一個明文對象
$ curl -i -H "Content-Type: plain/text" -d "Some text" \ http://localhost:8098/riak/foo/HTTP/1.1 201 Created Vary: Accept-Encoding Location: /riak/foo/3vbskqUuCdtLZjX5hx2JHKD2FTK Content-Type: plain/text Content-Length: ...通過檢查 Location 標頭,您可以看到 Riak 分配給對象的鍵。這樣做不容易記憶,因此另一種選擇是讓用戶提供鍵。讓我們創建一個藝術家 bucket,并添加一個叫做 Bruce 的藝術家(參見?清單 5)。
清單 5. 清單 5. 創建一個藝術家 bucket 并添加一個藝術家
$ curl -i -d '{"name":"Bruce"}' -H "Content-Type: application/json" \ http://localhost:8098/riak/artists/BruceHTTP/1.1 204 No Content Vary: Accept-Encoding Content-Type: application/json Content-Length: ...如果使用我們指定的鍵成功存儲了對象,我們將從服務器得到一個 204 No Content 響應。
在本例中,我們將對象的值保存為 JSON,但是它既可以是明文格式,也可以是其他格式。在存儲對象時,需要注意正確設置 Content-Type 標頭。例如,如果希望存儲一個 JPEG 圖像,那么您必須將內容類型設置為 image/jpeg。
檢索對象
要檢索已存儲的對象,使用您希望檢索的對象的鍵對 bucket 運行?GET?方法。如果對象存在,則會在響應的正文中返回對象,否則服務器會返回 404 Object Not Found 響應(參見?清單 6)。
清單 6. 清單 6. 在 bucket 上執行一個?GET?方法
$ curl http://localhost:8098/riak/artists/BruceHTTP/1.1 200 OK ... { "name" : "Bruce" }更新對象
在更新對象時,和存儲對象一樣,需要用到 Content-Type 標頭。例如,讓我們來添加 Bruce 的別名,如?清單 7?所示。
清單 7. 清單 7. 添加 Bruce 的別名
$ curl -i -X PUT -d '{"name":"Bruce", "nickname":"The Boss"}' \ -H "Content-Type: application/json" http://localhost:8098/riak/artists/Bruce如前所述,Riak 自動創建了 bucket。這些 bucket 擁有一些屬性,其中一個屬性為 allow_mult,用于確定是否允許執行并發寫操作。默認情況下,該屬性被設置為 false;但是,如果允許進行并發更新,則需要向每個更新發送 X-Riak-Vclock 標頭。應該將該標頭的值設置為與客戶端最后一次讀取對象時看到的值相同。
Riak 使用向量時鐘 (vector clock) 判斷修改對象的原因。向量時鐘的工作原理超出了本文的討論范圍,但是,在允許執行并發寫操作時,可能會出現沖突,這時需要使用應用程序來解決這些沖突(參閱?參考資料)。
刪除對象
刪除對象的操作使用了一個與前面的命令類似的模式,我們只需要對希望刪除的對象所對應的 URL 執行一個 HTTP?DELETE?方法:?$ curl -i -X DELETE http://localhost:8098/riak/artists/Bruce。
如果成功刪除對象,我們會從服務器獲得一個 204 No Content 響應;如果試圖刪除的對象不存在,那么服務器會返回一個 404 Object Not Found 響應。
鏈接
目前為止,我們已經了解了如何通過將對象與特定鍵相關聯來存儲對象,稍后可以使用此特定鍵來檢索對象。如果能夠將這個簡單的模型進行擴展以表示對象如何(以及是否)與其他對象相關,那么這會非常有用。我們當然可以實現這一點,并且 Riak 是使用鏈接實現的。
那么,什么是鏈接?鏈接允許用戶創建對象之間的關系。如果熟悉 UML 類圖的話,您可以將鏈接看作是對象之間的某種關聯,并用一個書簽說明這種關系;在關系數據庫中,該關系被表示為一個外鍵。
通過 “Link” 標頭,以將鏈接 “依附” 到對象上。下面演示了鏈接標頭看起來是什么樣子。例如,關系的目標(即我們準備進行鏈接的對象)是尖括號中的內容。關系內容(本例中為 “performer”)是通過 riaktag 屬性來表示的:Link: </riak/artists/Bruce>; riaktag="performer"。
現在讓我們添加一些專輯,并將它們與專輯的表演者藝術家 Bruce 關聯起來(參見?清單 8)。
清單 8. 清單 8. 添加一些專輯
$ curl -H "Content-Type: text/plain" \ -H 'Link: </riak/artists/Bruce> riaktag="performer"' \ -d "The River" http://localhost:8098/riak/albums/TheRiver$ curl -H "Content-Type: text/plain" \ -H 'Link: </riak/artists/Bruce> riaktag="performer"' \ -d "Born To Run" http://localhost:8098/riak/albums/BornToRun現在我們已經設置了一些關系,接下來要通過 link walking 查詢它們,link walking 是一個用于查詢對象關系的進程。例如,要查找表演 River 專輯的藝術家,您應當這樣做:$ curl -i http://localhost:8098/riak/albums/TheRiver/artists,performer,1。
末尾的位是鏈接說明。鏈接查詢的外觀就是這個樣子。第一個部分(artists)指定我們應當執行查詢的 bucket。第二個部分(performer)指定了我們希望用于限制結果的標簽,最后的?1?部分表示我們希望包含這個查詢階段的結果。
還可以發出過渡性查詢。假設我們在專輯和藝術家之間建立了關系,如?圖 1?所示。
圖 1. 圖 1. 專輯和藝術家之間的關系
通過執行下面的命令,可以發出 “哪些藝術家與表演 The River 專輯的藝術家合作過” 之類的查詢:$ curl -i http://localhost:8098/riak/albums/TheRiver/artists,_,0/artists,collaborator,1。鏈接說明中的下劃線的作用類似于通配符,表示我們不關心具體的關系是什么。
運行 Map/Reduce 查詢
Map/Reduce 是一個由 Google 推廣的框架,用于在大型數據集上同時運行分布式計算。Riak 還提供 Map/Reduce 支持,它允許對群集中的數據運行功能更強大的查詢。
Map/Reduce 函數包括一個 map 階段和一個 reduce 階段。map 階段應用于某些數據并生成 0 個或多個結果;這在編程中類似于通過列表中的每一項映射函數。map 階段是并行發生的。reduce 階段將獲取 map 階段的所有結果,并將它們組合起來。
例如,計算某個單詞在大量文檔中出現的次數。每個 map 階段都將計算每個單詞在特定文檔中出現的次數。這些中間計數在計算完后將發送到 reduce 函數,然后計算總數并得出在所有文檔中的次數。參見?參考資料,獲得有關 Google 的 Map/Reduce 文章的鏈接。
示例:分布式 grep
對于本文,我們將開發一個 Map/Reduce 函數,該函數將對 Riak 中存儲的一組文檔執行一次分布式 grep。和 grep 一樣,最終的輸出是一些匹配所提供模式的行。此外,每個結果還將表示文檔中出現匹配時所在位置的行號。
要執行一個 Map/Reduce 查詢,我們將對 /mapred 資源執行?POST?操作。請求的內容是查詢的 JSON 表示;和前面的例子一樣,必須提供 Content-Type 標頭,并且始終將其設置為 application/json。清單 9?顯示了我們為執行分布式 grep 而做的查詢。后面將依次討論查詢的每一個部分。
清單 9. 清單 9. 示例 Map/Reduce 查詢
{"inputs": [["documents","s1"],["documents","s2"]],"query": [{ "map": { "language": "javascript", "name": "GrepUtils.map", "keep": true, "arg": "[s|S]herlock" } },{ "reduce": { "language": "javascript", "name": "GrepUtils.reduce" } }] }每個查詢都包含若干輸入,例如,我們希望對之執行計算的文檔,在 map 和 reduce 階段運行的函數的名稱。也可以直接在查詢中包含?map?和reduce?函數的源代碼,只需要使用源屬性替代名稱即可,但是我在本例中沒有這樣做;然而,要使用指定的函數,則需要對 Riak 的默認配置進行一些修改。將清單 9 中的代碼保存到某個目錄中。對于群集中的每個節點,找到文件 etc/app.config,打開它并將屬性 property js_source_dir 設置為您用于保存代碼的目錄。您需要重啟群集中的所有節點使變更生效。
清單 10?中的代碼包含將在 map 和 reduce 階段執行的函數。map?函數將查看文檔的每一行,確定是否與提供的模式(arg?參數)匹配。本例中的?reduce?函數并不會執行太多操作;它類似于一個恒等函數,僅僅用于返回輸入。
清單 10. 清單 10. GrepUtils.js
var GrepUtils = { map: function (v, k, arg) {var i, len, lines, r = [], re = new RegExp(arg);lines = v.values[0].data.split(/\r?\n/); for (i = 0, len = lines.length; i < len; i += 1) {var match = re.exec(lines[i]);if (match) {r.push((i+1) + “. “ + lines[i]);}}return r;}, reduce: function (v) {return [v];} };在運行查詢之前,我們需要一些數據。我從 Project Gutenberg Web 站點下載了 Sherlock Holmes 電子圖書(參見?參考資料)。第一個文本存儲在鍵 “s1” 下的 “documents” bucket 中;第二個文本位于同一個 bucket 中,鍵為 “s2”。
清單 11?展示了如何將這類文檔上傳到 Riak。
清單 11. 清單 11. 將文檔上傳到 Riak
$ curl -i -X POST http://localhost:8098/riak/documents/s1 \ -H “Content-Type: text/plain” --data-binary @s1.txt上傳文檔后,我們現在可以對文檔執行搜索。在本例中,我們想輸出匹配常規表達式?"[s|S]herlock"(參見?清單 12)的所有行。
清單 12. 清單 12. 搜索文檔
$ curl -X POST -H "Content-Type: application/json" \ http://localhost:8098/mapred --data @-<<\EOF {"inputs": [["documents","s1"],["documents","s2"]],"query": [{ "map": { "language":"javascript", "name":"GrepUtils.map", "keep":true, "arg": "[s|S]herlock" } },{ "reduce": { "language": "javascript", "name": "GrepUtils.reduce" } }] } EOF查詢中的?arg?屬性包含我們希望在文檔中對其執行 grep 查詢的模式;該值被作為?arg?參數傳遞給?map?函數。
清單 13?中顯示了對樣例數據運行 Map/Reduce 作業所產生的輸出。
清單 13. 清單 13. 運行 Map/Reduce 作業的樣例輸出
[["1. Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle","9. Title: The Adventures of Sherlock Holmes","62. To Sherlock Holmes she is always THE woman. I have seldom heard","819. as I had pictured it from Sherlock Holmes' succinct description,","1017. \"Good-night, Mister Sherlock Holmes.\"","1034. \"You have really got it!\" he cried, grasping Sherlock Holmes by" …]]流化 Map/Reduce
在關于 Map/Reduce 的最后部分中,我們將簡單地了解 Riak 的 Map/Reduce 流化 (streaming) 特性。該特性對于包含 map 階段并需要花一些時間完成這些階段的作業非常有用,因為對結果進行流化允許您在生成每個 map 階段的結果后立即訪問它們,并且在執行 reduce 階段之前訪問它們。
我們可以對分布式 grep 查詢應用這個特性。本例中的 reduce 步驟并沒有多少實際操作。事實上,我們完全可以去掉 reduce 階段,只需要將每個 map 階段的結果直接發送到客戶端即可。為了實現此目標,需要對查詢進行修改,刪除 reduce 步驟,將??chunked=true?添加到 URL 末尾,表示我們希望對結果進行流化(參見?清單 14)。
清單 14. 清單 14. 修改查詢以流化結果
$ curl -X POST -H "Content-Type: application/json" \ http://localhost:8098/mapred?chunked=true --data @-<<\EOF { "inputs": [["documents","s1"],["documents","s2"]],"query": [{ "map": {"language": "javascript", "name": "GrepUtils.map","keep": true, "arg": "[s|S]herlock" } }] } EOF在完成 map 階段后,會將每個 map 階段的結果(在本例中為匹配查詢字符串的行)返回給客戶端。該方法可用于需要在查詢的中間結果可用時就對它們進行處理的應用程序。
結束語
Riak 是基于 Amazon 的 Dynamo 文件中記載的規則的一種開源的、高度可擴展的鍵值存儲庫。Riak 非常易于部署和擴展。可以無縫地向群集添加額外的節點。link walking 之類的特性以及對 Map/Reduce 的支持允許實現更加復雜的查詢。除了 HTTP API 外,Riak 還提供了一個原生 Erlang API 以及對 Protocol Buffer 的支持。在本系列的第 2 部分中,我們將探討各種不同語言中的大量客戶端庫,并展示如何將 Riak 用作一種高度可擴展的緩存。
參考資料
學習
- 參見?Basic Cluster Setup and Building a Development Environment,獲得有關設置一個 3 節點群集的詳細信息。
- 閱讀 Google 的?MapReduce: Simplified Data Processing on Large Clusters。
- Erlang 編程簡介,第 1 部分(Martin Brown,developerWorks,2011 年 5 月):對 Erlang 的函數性編程風格與其他編程模式進行了比較,如命令式、過程式和面向對象的編程。
- 強烈建議閱讀?Amazon 的 Dynamo 文檔,以了解 Riak 的基礎知識。
- 閱讀文章?How To Analyze Apache Logs,了解如何使用 Riak 處理您的服務器日志。
- 了解?向量時鐘,以及為什么它們要比您想象的更加容易理解。
- 在 Riak wiki 上找到有關?向量時鐘?的出色介紹,以及更多有關?link walking?的信息。
- 如果您需要一些文本資源進行測試的話,Project Gutenberg 站點?是一個不錯的選擇。
- developerWorks 中國網站 Web 開發專區?提供了涵蓋各種基于 Web 的解決方案的文章。
- 要收聽面向軟件開發人員的有趣訪談和討論,請參閱?developerWorks podcasts。
- IBM Rational Twitter:立即加入并關注 developerWorks tweets。
- 觀看?演示如何用 WebSphere Studio 快速開發Web Services,其中包括面向初學者的產品安裝和設置演示,以及為經驗豐富的開發人員提供的高級功能。
- 隨時關注 developerWorks?技術活動和網絡廣播。
- 訪問 developerWorks?Open source 專區獲得豐富的 how-to 信息、工具和項目更新以及最受歡迎的文章和教程,幫助您用開放源碼技術進行開發,并將它們與 IBM 產品結合使用。
獲得產品和技術
- 從 basho.com 下載?Riak。
- 下載?Erlang?編程語言。
- 使用專門面向開發人員的軟件創新您的下一個開源開發項目;訪問?IBM 產品評估試用版軟件,可以通過下載或從 DVD 獲得它。
討論
- 加入?developerWorks 中文社區,developerWorks 社區是一個面向全球 IT 專業人員,可以提供博客、書簽、wiki、群組、聯系、共享和協作等社區功能的專業社交網絡社區。
- 加入?IBM 軟件下載與技術交流群組,參與在線交流。
總結
- 上一篇: 数字信号处理与高频电路基础知识与实训QY
- 下一篇: win 10更新后 ArcGIS 启动错