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