發(fā)布于:2021-01-11 10:10:22
0
1228
0
JSON文檔存儲(chǔ)庫RethinkDB專為跨多臺(tái)計(jì)算機(jī)的可伸縮性而構(gòu)建,它是一種使用簡單查詢語言的分布式數(shù)據(jù)庫。這是入門方法。
RethinkDB 是用于構(gòu)建實(shí)時(shí)Web應(yīng)用程序的開源數(shù)據(jù)庫。開發(fā)人員可以將查詢變成實(shí)時(shí)供稿,而不是輪詢更改,而實(shí)時(shí)供稿將實(shí)時(shí)更新不斷推送到應(yīng)用程序。RethinkDB的流式更新簡化了實(shí)時(shí)后端架構(gòu),通過使更改傳播成為應(yīng)用程序持久層的本機(jī)部分,消除了多余的麻煩。
除了為實(shí)時(shí)應(yīng)用程序開發(fā)提供獨(dú)特功能外,RethinkDB還受益于一些有用的特性,這些特性有助于提供愉悅的開發(fā)人員體驗(yàn)。RethinkDB是一種無模式JSON文檔存儲(chǔ),旨在實(shí)現(xiàn)可伸縮性和易用性,具有易于分片,支持分布式聯(lián)接和表達(dá)性查詢語言的功能。
本教程將演示如何使用RethinkDB和Node.js構(gòu)建實(shí)時(shí)Web應(yīng)用程序。它將使用Socket.io將實(shí)時(shí)更新傳達(dá)給前端。如果您想繼續(xù),可以 安裝RethinkDB 或 在云中運(yùn)行它。
ReQL的第一步
RethinkDB查詢語言(ReQL)將自身嵌入用于構(gòu)建應(yīng)用程序的編程語言中。ReQL被設(shè)計(jì)為流利的API,您可以將它們鏈接在一起以組成查詢。
在開始構(gòu)建應(yīng)用程序之前,讓我們花一些時(shí)間來探索查詢語言。嘗試查詢的最簡單方法是使用RethinkDB的管理控制臺(tái),該控制臺(tái)通常在端口8080上運(yùn)行。您可以在Data Explorer選項(xiàng)卡的文本字段中鍵入RethinkDB查詢,然后運(yùn)行它們以查看輸出。數(shù)據(jù)資源管理器提供自動(dòng)完成和語法突出顯示功能,在學(xué)習(xí)ReQL時(shí)可能會(huì)有所幫助。
默認(rèn)情況下,RethinkDB創(chuàng)建一個(gè)名為test的數(shù)據(jù)庫。讓我們從向testdatabase添加表開始:
r.db("test").tableCreate("fellowship")
現(xiàn)在,讓我們向該表添加一組九個(gè)JSON文檔:
r.table("fellowship").insert([ { name: "Frodo", species: "hobbit" }, { name: "Sam", species: "hobbit" }, { name: "Merry", species: "hobbit" }, { name: "Pippin", species: "hobbit" }, { name: "Gandalf", species: "istar" }, { name: "Legolas", species: "elf" }, { name: "Gimili", species: "dwarf" }, { name: "Aragorn", species: "human" }, { name: "Boromir", species: "human" } ])
當(dāng)您運(yùn)行上面的命令時(shí),數(shù)據(jù)庫將輸出一個(gè)數(shù)組,該數(shù)組包含為所有新文檔生成的主鍵。它還將告訴您成功插入了多少新記錄。現(xiàn)在我們已經(jīng)在數(shù)據(jù)庫中有了一些記錄,讓我們嘗試使用ReQL的filter命令來獲取團(tuán)契的霍比特人:
r.table("fellowship").filter({species:"hobbit"})
filter命令檢索與提供的布爾表達(dá)式匹配的文檔。在這種情況下,我們特別希望文件中物種屬性等于霍比特人。如果要執(zhí)行更多操作,可以將其他命令鏈接到查詢。例如,您可以使用以下查詢來更改所有霍比特人的種類屬性的值:
r.table("fellowship").filter({species: "hobbit"}) .update({species: "halfling"})
ReQL甚至有一個(gè)內(nèi)置的http命令,您可以使用該命令從公共Web API中獲取數(shù)據(jù)。在以下示例中,我們使用http命令從受歡迎的subreddit中獲取當(dāng)前帖子。完整查詢檢索帖子,按分?jǐn)?shù)排序,然后顯示前五個(gè)條目中的幾個(gè)屬性:
r.http("http://www.reddit.com/r/aww.json")("data")("children")("data") .orderBy(r.desc("score")).limit(5).pluck("score", "title", "url")
如您所見,ReQL對(duì)于許多特殊數(shù)據(jù)分析非常有用。您可以使用它以多種有趣的方式對(duì)復(fù)雜的JSON數(shù)據(jù)結(jié)構(gòu)進(jìn)行切片和切塊。如果您想了解更多關(guān)于ReQL,你可以參考 API參考文檔,則 ReQL引進(jìn) 的RethinkDB網(wǎng)站上。
在Node.js和Express中使用RethinkDB
既然您已經(jīng)具備ReQL的基本工作知識(shí),那么該開始構(gòu)建應(yīng)用程序了。我們將首先研究如何使用Node.js和Express制作一個(gè)API后端,該后端將ReQL查詢的輸出提供給最終用戶。
npm中的rethinkdb模塊提供RethinkDB的官方JavaScript客戶端驅(qū)動(dòng)程序。您可以在Node.js應(yīng)用程序中使用它來編寫和發(fā)送查詢。以下示例顯示如何執(zhí)行簡單查詢并顯示輸出:
var r = require("rethinkdb"); r.connect().then(function(conn) { return r.db("test").table("fellowship") .filter({species: "halfling"}).run(conn) .finally(function() { conn.close(); }); }) .then(function(cursor) { return cursor.toArray(); }) .then(function(output) { console.log("Query output:", output); }) .error(function(err) { console.log("Failed:", err); });
connect方法建立與RethinkDB的連接。它返回一個(gè)連接句柄,您要在執(zhí)行查詢時(shí)將其提供給run命令。上面的示例在研究金表中找到所有半身人,然后在控制臺(tái)中顯示它們各自的JSON文檔。它使用promise處理異步執(zhí)行流,并確保在操作完成后正確關(guān)閉連接。
讓我們在上面的示例中進(jìn)行擴(kuò)展,添加具有API端點(diǎn)的Express服務(wù)器,該端點(diǎn)使用戶能夠獲取所需物種的所有研究金成員:
var app = require("express")(); var r = require("rethinkdb"); app.listen(8090); console.log("App listening on port 8090"); app.get("/fellowship/species/:species", function(req, res) { r.connect().then(function(conn) { return r.db("test").table("fellowship") .filter({species: req.params.species}).run(conn) .finally(function() { conn.close(); }); }) .then(function(cursor) { return cursor.toArray(); }) .then(function(output) { res.json(output); }) .error(function(err) { res.status(500).json({err: err}); }) });
如果您以前使用過Express,則上面的代碼應(yīng)該看起來非常直觀。URL路徑中的最后一個(gè)路徑段表示一個(gè)變量,我們將其傳遞給ReQL查詢中的filter命令,以便僅獲取所需的文檔。查詢完成后,應(yīng)用程序會(huì)將JSON輸出中繼給用戶。如果查詢未能完成,則應(yīng)用程序?qū)⒎祷貭顟B(tài)代碼500并提供錯(cuò)誤。
實(shí)時(shí)更新與更新
RethinkDB專為構(gòu)建實(shí)時(shí)應(yīng)用程序而設(shè)計(jì)。通過將changes命令附加到ReQL查詢的末尾,可以獲得實(shí)時(shí)的連續(xù)查詢更新流。changes命令創(chuàng)建一個(gè)changefeed,當(dāng)查詢結(jié)果更改時(shí),它將為您提供一個(gè)接收新記錄的游標(biāo)。以下代碼演示了如何使用變更供稿來顯示表更新:
r.connect().then(function(c) { return r.db("test").table("fellowship").changes().run(c); }) .then(function(cursor) { cursor.each(function(err, item) { console.log(item); }); });
每當(dāng)研究金表中的數(shù)據(jù)更改時(shí),都會(huì)執(zhí)行cursor.each回調(diào)。您可以通過進(jìn)行任意更改來自己測試。例如,在Boromir被獸人殺死之后,我們可以將其從團(tuán)契中刪除:
r.table("fellowship").filter({name:"Boromir"}).delete()
當(dāng)查詢從團(tuán)契中刪除Boromir時(shí),演示應(yīng)用程序?qū)⒃趕tdout中顯示以下JSON數(shù)據(jù):
{ new_val: null, old_val: { id: '362ae837-2e29-4695-adef-4fa415138f90', name: 'Boromir', species: 'human' } }
當(dāng)變更供稿提供更新通知時(shí),它們會(huì)告訴您記錄的先前值和記錄的新值。您可以將兩者進(jìn)行比較,以查看發(fā)生了什么變化。刪除現(xiàn)有記錄后,新值將為null。同樣,當(dāng)表接收新記錄時(shí),舊值是null。
changes命令當(dāng)前可用于以下幾種查詢:get,between,filter,map,orderBy,min和max。計(jì)劃在將來支持其他類型的查詢,例如組操作。
實(shí)時(shí)計(jì)分板
讓我們考慮一個(gè)更復(fù)雜的示例:帶有排行榜的多人游戲。您想顯示得分最高的前五名用戶,并在列表更改時(shí)實(shí)時(shí)更新。RethinkDB更改提要使此操作變得容易。您可以將changefeed附加到包含orderBy和limit命令的查詢。每當(dāng)排名前五名的用戶的得分或總體組成發(fā)生變化時(shí),changefeed都會(huì)為您提供更新。
在開始設(shè)置變更提要之前,讓我們開始使用數(shù)據(jù)資源管理器創(chuàng)建一個(gè)新表并用一些示例數(shù)據(jù)填充它:
r.db("test").tableCreate("players") r.table("players").indexCreate("score") r.table("players").insert([ {name: "Bill", score: 33}, {name: "Janet", score: 42}, {name: "Steve", score: 68} ... ])
創(chuàng)建索引有助于數(shù)據(jù)庫對(duì)指定屬性進(jìn)行更有效的排序-在本例中為得分。目前,如果您對(duì)索引進(jìn)行訂購,則只能將orderBy命令與changefeeds一起使用。
要檢索當(dāng)前的前五名球員及其得分,可以使用以下ReQL表達(dá)式:
r.db("test").table("scores").orderBy({index: r.desc("score")}).limit(3)
我們可以在最后添加changes命令,以獲取更新流。為了使這些更新到達(dá)前端,我們將使用Socket.io,這是一個(gè)用于在服務(wù)器和客戶端之間實(shí)現(xiàn)實(shí)時(shí)消息傳遞的框架。它支持多種傳輸方法,包括WebSockets。Socket.io用法的詳細(xì)信息不在本文討論范圍之內(nèi),但是您可以通過訪問Socket.io官方文檔了解更多信息 。
以下代碼使用sockets.emit將來自更新的更新廣播到所有連接的Socket.io客戶端:
var sockio = require("socket.io"); var app = require("express")(); var r = require("rethinkdb"); var io = sockio.listen(app.listen(8090), {log: false}); console.log("App listening on port 8090"); r.connect().then(function(conn) { return r.table("scores").orderBy({index: r.desc("score")}) .limit(5).changes().run(conn); }) .then(function(cursor) { cursor.each(function(err, data) { io.sockets.emit("update", data); }); });
在前端,您可以使用Socket.io客戶端庫來設(shè)置接收updateevent的處理程序:
var socket = io.connect(); socket.on("update", function(data) { console.log("Update:", data); });
這是一個(gè)很好的開始,但是我們需要一種在用戶第一次加載頁面時(shí)填充初始列表值的方法。為此,讓我們擴(kuò)展服務(wù)器,以便在用戶首次連接時(shí)通過Socket.io廣播當(dāng)前排行榜:
var getLeaders = r.table("scores").orderBy({index: r.desc("score")}).limit(5); r.connect().then(function(conn) { return getLeaders.changes().run(conn); }) .then(function(cursor) { cursor.each(function(err, data) { io.sockets.emit("update", data); }); }); io.on("connection", function(socket) { r.connect().then(function(conn) { return getLeaders.run(conn) .finally(function() { conn.close(); }); }) .then(function(output) { socket.emit("leaders", output); }); });
在兩種情況下,該應(yīng)用程序都使用相同的基礎(chǔ)ReQL表達(dá)式,因此我們可以將其存儲(chǔ)在變量中以方便重用。ReQL的方法鏈?zhǔn)蛊浞浅S兄谶@種可組合性。
為了結(jié)束演示,讓我們構(gòu)建一個(gè)完整的前端。為簡單起見,我將使用Polymer的數(shù)據(jù)綁定系統(tǒng)。讓我們從定義模板開始:
<template id="scores" is="auto-binding"> <ul> <template repeat="{{user in users}}"> <li><strong>{{user.name}}:</strong> {{user.score}}</li> </template> </ul> </template>
它使用repeat屬性為每個(gè)用戶插入一個(gè)li標(biāo)簽。li標(biāo)簽的內(nèi)容顯示用戶名及其當(dāng)前分?jǐn)?shù)。接下來,讓我們編寫JavaScript代碼:
var scores = document.querySelector("#scores"); var socket = io.connect(); socket.on("leaders", function(data) { scores.users = data; }); socket.on("update", function(data) { for (var i in scores.users) if (scores.users[i].id === data.old_val.id) { scores.users[i] = data.new_val; scores.users.sort(function(x,y) { return y.score - x.score }); break; } });
Leader事件的處理程序僅從服務(wù)器獲取數(shù)據(jù)并將其分配給存儲(chǔ)用戶的模板變量。更新處理程序有點(diǎn)復(fù)雜。它在排行榜中找到與old_val相關(guān)的條目,然后將其替換為新數(shù)據(jù)。
當(dāng)排行榜中某個(gè)用戶的分?jǐn)?shù)發(fā)生變化時(shí),它將用一個(gè)具有更新編號(hào)的新記錄替換舊記錄。如果排行榜中的某個(gè)用戶被以前不存在的用戶所取代,它將用一個(gè)用戶的記錄替換另一個(gè)用戶的記錄。上面的代碼將正確處理這兩種情況。
當(dāng)然,changefeed更新并不能幫助我們維持用戶的實(shí)際訂單。為了解決該問題,我們只需在每次更新后對(duì)用戶數(shù)組進(jìn)行排序。Polymer的數(shù)據(jù)綁定系統(tǒng)將確保實(shí)際的DOM表示始終反映所需的順序。
演示應(yīng)用程序現(xiàn)已完成,您可以通過運(yùn)行查詢來更改用戶分?jǐn)?shù)來對(duì)其進(jìn)行測試。在數(shù)據(jù)資源管理器中,您可以嘗試運(yùn)行以下內(nèi)容:
r.table("scores").filter({name: "Bill"}) .update({score: r.row("score").add(100)})
當(dāng)您更改用戶分?jǐn)?shù)的值時(shí),您將看到排行榜更新以反映這些更改。
下一步
常規(guī)數(shù)據(jù)庫主要圍繞查詢/響應(yīng)工作流進(jìn)行設(shè)計(jì),該工作流很好地映射到Web的傳統(tǒng)請(qǐng)求/響應(yīng)模型。但是,像WebSockets這樣的現(xiàn)代技術(shù)使構(gòu)建實(shí)時(shí)流更新的應(yīng)用程序成為可能,而沒有HTTP請(qǐng)求的延遲或開銷。
RethinkDB是第一個(gè)專門為實(shí)時(shí)Web設(shè)計(jì)的開源數(shù)據(jù)庫。變更提要提供了一種構(gòu)建查詢的方法,該查詢可以連續(xù)推出實(shí)時(shí)更新,而無需進(jìn)行常規(guī)輪詢。
作者介紹