铸造车间 - CouchDB
Web 开发者现在已经无法忽视对非关系型数据库(通常统称为 NoSQL)日益增长的兴趣。无论您是出于可扩展性、可用性、成本、性能原因,还是仅仅因为它是一个闪亮的新玩具而关注非关系型数据库,都不可否认,认真的 Web 开发者在设计应用程序时需要考虑非关系型选项。在过去的几个月中,我参与的每个项目都至少考虑过非关系型解决方案,即使最终决定是使用关系型数据库。
在本专栏的前两期中,我研究了 MongoDB,这是一种具有一定关系型感觉的对象(或“文档”)数据库。 MongoDB 存储对象,但其查询语言对于长期使用关系型数据库的人来说应该看起来有些熟悉。如果您愿意考虑更彻底地脱离关系型数据库和查询语法的世界,而不是使用 MapReduce 范例、简单的复制和直接的 RESTful API,您可能需要考虑 CouchDB,它现在是 Apache 软件基金会的一部分。即使您不在生产环境中使用 CouchDB,您也可能会发现(就像我一样)它对 JavaScript 的使用,以及其 MapReduce 的实现,可以帮助您以新的和不同的方式思考旧问题。
下载和安装 CouchDB 非常容易。如果它不能通过简单的apt-get install(或 yum 等效命令)在您的系统上使用,或者如果您只是喜欢安装源代码版本,您可以从 CouchDB 首页 couchdb.apache.org 下载。我运行的版本(0.10)略有过时,而不是撰写本文时的最新生产版本(0.11)。然而,差异并不大,特别是对于此处显示的简单示例。
在使用 apt-get 安装 CouchDB 后,我在我的服务器上使用以下标准命令启动了它
/etc/init.d/couchdb start
这将在端口 5984 上启动 CouchDB 服务器。默认情况下,这意味着我可以按以下方式访问 CouchDB 服务器
http://127.0.0.1:5984/
如果您有兴趣从另一个系统访问您的 CouchDB 服务器,您可以通过转到“httpd”部分并替换名称-值对来修改 CouchDB 配置文件(我机器上的 /etc/couchdb/default.ini)
bind_address = 127.0.0.1
用您的 IP 地址而不是 127.0.0.1(即 localhost)。重新启动 CouchDB,它不仅可以被本地 HTTP 客户端访问,还可以跨 Internet 访问。
显然,在其众所周知的端口上启动 CouchDB 服务器且没有任何安全限制是自找麻烦。如果您正在运行 CouchDB 的生产实例,则应确保普通大众无法访问或修改它。 CouchDB 带有基本的身份验证选项,可以限制对数据库的访问,您应该在将系统部署到公共服务器之前研究这些选项。
如果您将 Web 浏览器指向端口 5984 上的 CouchDB 服务器,您将看到以下内容
{"couchdb":"Welcome","version":"0.10.0"}
此响应告诉您几件事。首先,您看到 CouchDB 中的所有通信都使用 JSON 进行,JSON 是一种 JavaScript 对象表示法,已成为 Internet 应用程序之间通信的轻量级方法。尽管 CouchDB 是用 Erlang 编写的,Erlang 是一种为分布式处理设计的开源语言,但几乎所有与之相关的东西都使用 JavaScript。函数(您很快就会看到)是用 JavaScript 编写的,输入和输出都使用 JSON 发送。
CouchDB 也是 RESTful 的——它使用 HTTP 动词的整个词汇表来描述应该发生什么,并使用 URL 来指示应该在其上执行操作的对象。大多数人熟悉 HTTP 的 GET 和 POST 动词,但对 PUT 和 DELETE 则不太熟悉。 CouchDB 使用所有这些,结合 HTTP、JSON 和 REST 以获得丰富的效果。
因此,当您将 Web 浏览器指向端口 5984 上的 CouchDB 服务器,请求文档 / 时,您实际上是在对名为 / 的文档发出 GET 请求。 CouchDB 的响应描述了服务器,而不是单个文档。响应是一个对象(相当于 Perl、Ruby 或 Python 等语言中的“哈希”或“字典”),带有两个键。第一个“couchdb”只是说“Welcome”。第二个名为“version”的键告诉您正在运行的服务器版本——在本例中为 0.10.0。
让我们稍微更改 URL,改为 URL /_utils。如果您转到该文档,您将看到更令人感兴趣的响应。实际上,您将获得一个完整的 Web 页面,而不是接收 JSON,页面右上角带有 CouchDB 徽标。这就是 Futon,CouchDB 基于 Web 的界面。有时它被称为管理界面,但它对于试验数据库也非常有用。
Futon 主页的右侧是主要的“工具”菜单。它通常以概览模式出现,但您可以通过单击它们切换到许多其他屏幕。我最感兴趣的是测试套件,它提供了一个基于 Web 的界面,以确保您的 CouchDB 安装正常工作。尽管您的系统不太可能出现任何问题,但您可能仍然希望运行测试套件,只是为了个人满意和彻底性。
回到概览屏幕,您应该在顶部看到一个提示,提示创建数据库。与大多数关系型数据库系统一样,单个服务器可能包含多个数据库。然后,每个数据库可能包含任意数量的文档,每个文档都有一个唯一的 ID 和任意数量的名称-值对。
因此,要开始使用,您需要创建一个新数据库。单击链接,将打开一个 AJAX 对话框,询问数据库的名称。在本专栏中,我将假设数据库名称为“atf”,尽管您可能希望选择更接近您自己的姓名或兴趣的名称。您可以对数据库名称使用任何字母数字字符(以及一些符号),但请记住,前导下划线由内部 CouchDB 系统使用,这意味着您应该避免对自己的工作使用此类名称。
创建数据库后,您将被带到浏览数据库页面。单击“新建文档”按钮以创建新文档。 CouchDB 会自动为新文档提供唯一的 ID 值(键名“\_id”)。如果您有自己喜欢的唯一编号或命名方案,您可以将 ID 更改为您喜欢的 ID。
然后,您可以通过单击“添加字段”按钮来添加任意数量的名称-值对。名称假定为字符串,但值可以是任何合法的 JSON 值——数字、字符串、数组或对象。如果您在交互式 Futon 界面中输入一个数组(在方括号内),完成后,它将以数组的可视方式表示。 JSON 对象也是如此。输入后,名称-值对将以易于阅读的格式显示。
将一些字段添加到文档后,单击“保存”按钮。
我向描述我的文档添加了许多字段。 Futon 中的“字段”选项卡以美观、易于编辑的格式向我显示这些值。如果我想以其原生 JSON 格式查看文档,我可以单击“源”选项卡并在那里查看它
{ "_id": "0534ca63b70beb02d24b62ec4fe72566", "_rev": "4-bea8364f4536833c1fd7de5781ea8a08", "first_name": "Reuven", "last_name": "Lerner", "children": [ "Atara", "Shikma", "Amotz" ] }
请注意,除了我已经提到的字段之外,还有一个“\_rev”字段。这是因为当您保存文档时,旧版本不会消失。相反,CouchDB 会保留旧版本,就像垃圾收集器处理 Ruby 和 Python 等高级语言中的内存一样。这意味着可能存在多个具有相同“\_id”字段的文档,但只有一个被认为是当前的——具有最新“\_rev”字段值的文档。修订版包含一个整数以及一个 MD5 哈希值。您通常可以仅查看整数来识别修订版,而忽略字符串的十六进制部分。
不要将修订标记误认为是保留备份或进行版本控制的手段。一旦有人压缩数据库,所有旧的修订版都将被删除。
与其他非关系型数据库一样,CouchDB 允许您随时添加、删除和重命名字段。数据库中的每个文档都可能有自己唯一的字段名称,尽管在实践中,这种情况相当少见。更常见的情况是,每个文档都有一组通用的字段,可能在特殊情况下有一些变化。通常说 CouchDB 是“无模式”的,但我认为更安全的说法是 CouchDB(和其他 NoSQL 存储设施)允许程序员在运行时而不是提前决定模式——就像动态编程语言允许您在运行时而不是在编译时确定变量的类型一样。
基于 JSON 的数据库明显缺少的一个东西是外键的概念——从一个文档或记录到另一个文档或记录的指针。没有用于将一个文档链接到另一个文档的内置工具,尽管肯定有方法可以使用一个文档中的信息来查看另一个文档。
CouchDB 附带一个易于使用的、基于浏览器的界面,这非常好。但是,此界面显然不是您希望从应用程序中使用的界面。正如我上面所写,CouchDB 使用 HTTP 上的 JSON 与外部世界通信。您刚刚通过浏览器执行的任何操作也应该可以通过 HTTP 客户端执行。您可以使用编程语言的库;每种主要语言至少有一个 CouchDB 客户端。但一个流行且易于使用的选项是 curl 命令行程序。
要向我的 CouchDB 服务器发送一个简单的 GET 请求,我可以编写
curl http://atf.lerner.co.il:5984/
果然,我收到了与之前相同的响应
{"couchdb":"Welcome","version":"0.10.0"}
不幸的是,如果出现问题,curl 不会说太多。因此,我通常更喜欢使用-v选项到 curl(以及大多数其他程序,就此而言),这向我显示了 HTTP 请求和响应的发生情况。指定您要使用的 HTTP 动词(在本例中为 GET)也很方便,因此我将使用 -X 选项来完成此操作。因此,我可以写
~$ curl -vX GET http://atf.lerner.co.il:5984/
我看到了
* About to connect() to atf.lerner.co.il port 5984 (#0) * Trying 69.55.225.93... connected * Connected to atf.lerner.co.il (69.55.225.93) port 5984 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.19.4 (universal-apple-darwin10.0) libcurl/7.19.4 > OpenSSL/0.9.8l zlib/1.2.3 > Host: atf.lerner.co.il:5984 > Accept: */* > < HTTP/1.1 200 OK < Server: CouchDB/0.10.0 (Erlang OTP/R13B) < Date: Mon, 12 Apr 2010 12:03:05 GMT < Content-Type: text/plain;charset=utf-8 < Content-Length: 41 < Cache-Control: must-revalidate < {"couchdb":"Welcome","version":"0.10.0"} * Connection #0 to host atf.lerner.co.il left intact * Closing connection #0
您可能会注意到“Content-type”响应标头指示服务器发回的内容是 text/plain 格式。因此,尽管您可能会将内容视为 JSON,但 CouchDB 本身表明它正在发送纯文本。这没什么大不了的,除非您正在编写一个专门等待 JSON 的程序,因此您可能需要稍微修改其预期。
您也可以请求您的 Futon URL,使用 HEAD 以避免冗长的响应
~$ curl -vX HEAD http://atf.lerner.co.il:5984/_utils/ * About to connect() to atf.lerner.co.il port 5984 (#0) * Trying 69.55.225.93... connected * Connected to atf.lerner.co.il (69.55.225.93) port 5984 (#0) > HEAD /_utils/ HTTP/1.1 > User-Agent: curl/7.19.4 (universal-apple-darwin10.0) libcurl/7.19.4 > OpenSSL/0.9.8l zlib/1.2.3 > Host: atf.lerner.co.il:5984 > Accept: */* > < HTTP/1.1 200 OK < Server: CouchDB/0.10.0 (Erlang OTP/R13B) < last-modified: Fri, 23 Oct 2009 12:40:09 GMT < Date: Mon, 12 Apr 2010 12:04:43 GMT < Content-Type: text/html < Content-Length: 3158
在这种情况下,您会收到 text/HTML 响应。当然,您知道 Futon 为其响应发送 HTML,因为您已经从 Web 浏览器中使用过它。
现在,让我们尝试查看我之前创建的 atf 数据库,其中包含一个文档(即记录)。我该如何检索该信息?
好吧,我可以先请求数据库(现在为了节省空间,省略 -v 选项)
~$ curl -X GET http://atf.lerner.co.il:5984/atf {"db_name":"atf","doc_count":1,"doc_del_count":0,"update_seq":4, "purge_seq":0,"compact_running":false,"disk_size":16473, "instance_start_time":"1271067859057749","disk_format_version":4}
换句话说,请求数据库会提供有关该数据库的基本信息,从文档数量到它在磁盘上占用的空间量。
您可以使用其 ID 检索单个文档
~$ curl -X GET ↪http://atf.lerner.co.il:5984/atf/0534ca63b70beb02d24b62ec4fe72566 {"_id":"0534ca63b70beb02d24b62ec4fe72566", "_rev":"4-bea8364f4536833c1fd7de5781ea8a08", "first_name":"Reuven", "last_name":"Lerner", "children":["Atara","Shikma","Amotz"]}
如果我想修改此文档中的一个或多个字段,甚至添加另一个字段,我可以使用 PUT 命令来完成。 curl 的 -d 选项允许我在命令行上指定文档
~$ curl -X PUT ↪http://atf.lerner.co.il:5984/atf/0534ca63b70beb02d24b62ec4fe72566 -d '{"first_name": "Superman", "middle_initial": "M." }' {"error":"conflict","reason":"Document update conflict."}
嗯,这很令人惊讶。 CouchDB 抱怨说它无法执行我需要的更新,因为存在冲突。请注意,它没有使用 HTTP 代码(例如 500)报告错误,而是向我发回一个 JSON 对象,其中包含“error”键。
CouchDB 在这里给出错误消息的原因是我没有指示我尝试更新的修订版。如果没有这样的修订版指示器,CouchDB 会假定我拥有过时的数据,因此,不会允许我更新文档。只有当我使用当前的“\_rev”值发送更新时,更新才会成功。例如
~$ curl -X PUT ↪http://atf.lerner.co.il:5984/atf/0534ca63b70beb02d24b62ec4fe72566 -d '{"_rev": "4-bea8364f4536833c1fd7de5781ea8a08", "first_name": "Superman", "middle_initial": "M." }'
CouchDB 响应
{"ok":true,"id":"0534ca63b70beb02d24b62ec4fe72566","rev": ↪"5-fe6fccb89b9512d26120fbd63dbb15c4"}
换句话说,更新成功,修订版递增。如果您再次尝试相同的更新,您将收到与之前相同的“更新冲突”错误消息,因为对于给定的修订版,只能进行一次更新。
请注意,当您PUT对文档进行更新时,您必须一次更新整个文档。与关系型数据库中的 UPDATE 命令不同,向 CouchDB 文档添加新修订版不会修改单个字段。相反,它存储一个全新的文档,该文档具有相同的 ID 和递增的修订号。这意味着在本例中,我确实已成功添加了“middle\_initial”字段。但是,我也有效地删除了“children”字段,因为我在 PUT 语句中没有指定它。
您可以使用 HTTP 中的 POST 动词向数据库添加全新的文档。例如
~$ curl -X POST http://atf.lerner.co.il:5984/atf -d '{"first_name" : "Atara", "last_name" : "Lerner-Friedman"}'
果然,我得到了以下响应,表明已创建了一个新文档
{"ok":true,"id":"aeb6925eb23278f1b8e530ba67b0172d", "rev":"1-f0e336978a368f679ee7b280107bc2fb"}
我应该补充一点,我在尝试使用 curl 创建文档时遇到了非常糟糕的经历,所有这些都是因为引号。似乎您必须在 JSON 请求中使用双引号(在键和值的名称周围)。单引号会导致奇怪的错误消息,指示 JSON 的 UTF-8 编码无效,但这并没有真正将我引向正确的方向。
CouchDB 是一种越来越流行的非关系型数据库,在存储和检索方面提供了极大的灵活性。本月,我解释了如何在 CouchDB 中创建数据库,以及如何使用基于 Web 的 Futon 界面和 curl 进行基本存储和检索。下个月,我将演示编写 JavaScript 函数来处理和显示数据,从而展示 CouchDB 的真正威力。
资源
CouchDB 的主页位于 Apache 项目(couchdb.apache.org)。在那里,您不仅可以下载软件,还可以阅读文档,从教程到活跃的 Wiki。 CouchDB 网站还提供了 CouchDB 工作时可能使用的各种语言的驱动程序链接。
如果您对 CouchDB 使用的 JSON 格式感兴趣,您可以在主网站上了解更多信息:json.org。
最后,关于 CouchDB 的两本好书在过去几个月中发布。Beginning CouchDB 由 Joe Lennon 撰写并由 Apress 出版,更适合初学者,但它对 CouchDB、Futon 以及您如何使用该系统进行了可靠的介绍。CouchDB: The Definitive Guide 由 J. Chris Anderson、Jan Lehnardt 和 Noah Slater 撰写,由 O'Reilly 出版,内容更高级、更充实,但可能不适合非关系型数据库的初学者。
Reuven M. Lerner 是一位长期从事 Web 开发、架构和培训的专家。他是西北大学学习科学专业的博士候选人,研究协作在线社区的设计和分析。 Reuven 与他的妻子和三个孩子住在以色列的莫迪因。