使用两个例子玩转NoSQL

1,682 阅读16分钟

初探 Nosql : 列举两个简单的例子

第一个示例创建一个简单的位置首选项存储,第二个示例管理汽车品牌和模型数据库。这两个示例都侧重于与NoSQL上下文相关的数据管理方面。

示例一 : 创建一个简单的位置首选项存储

随着当地企业试图与附近的用户建立联系,以及大公司试图根据人们驻扎的位置定制他们的在线体验和产品,基于位置的服务正变得越来越突出。在流行的应用程序中可以看到一些常见的基于位置的偏好,例如Google地图,它允许本地搜索,以及在线零售商(如 Walmart.com)根据离您最近的沃尔玛商店位置提供产品可用性和促销信息。

有时要求用户输入位置数据,有时推断用户位置。推理可以基于用户的 IP 地址、网络接入点(特别是当用户从移动设备访问数据时)或这些技术的任意组合。无论数据是如何收集的,您都需要有效地存储它,这就是示例开始的地方。

为简单起见,仅在美国为用户维护位置首选项,因此只需用户标识符和邮政编码即可查找用户的位置。让我们从用户名作为其标识符开始。需要维护“John Doe,10001”、“Lee Chang,94129”、“Jenny Gonzalez 33101”和“Srinivas Shastri, 02101”等数据点。

为了以灵活且可扩展的方式存储此类数据,此示例使用名为 MongoDB 的非关系数据库产品。在接下来的几个步骤中,您将创建一个MongoDB数据库并存储一些示例位置数据点。

启动MongoDB并存储数据

假设您已成功安装MongoDB,请启动服务器并连接到它。

您可以通过在发行版的 bin 文件夹中运行 mongod 程序来启动 MongoDB 服务器。发行版因底层环境而异,可以是 Windows、Mac OS X 或 Linux ,但在每种情况下,服务器程序都具有相同的名称,并且它驻留在发行版中名为 bin 的文件夹中。

连接到MongoDB服务器的最简单方法是使用发行版中可用的JavaScript shell。只需从命令行界面运行 mongo。mongo JavaScript shell 命令也可以在 bin 文件夹中找到。

当您通过运行mongod启动MongoDB服务器时,您应该在控制台上看到类似于以下内容的输出:

image.png

当 mongod 通过 Windows PowerShell 运行时,此特定输出是在 Windows 7 64 位计算机上捕获的。根据您的环境,您的输出可能会有所不同。

现在数据库服务器已经启动并运行,请使用 mongo JavaScript shell 连接到它。外壳的初始输出应如下所示:

image.png

默认情况下,mongo shell 连接到本地主机上可用的“test”数据库。从 mongod(服务器守护程序)控制台输出中,您还可以猜测 MongoDB 服务器在端口 27017 上等待连接。要探索一组可能的初始命令,只需在 mongo 交互式控制台上键入 help。键入帮助并按 Enter 键(或 Return)键时,您应该会看到如下命令选项列表:

image.png

自定义 MONGODB 数据目录和端口

默认情况下,MongoDB将数据文件存储在/data/db(Windows上的c:\data\db)目录中,并侦听端口27017上的请求。您可以通过使用 dbpath 选项指定目录路径来指定备用数据目录,如下所示:

mongod--dbpath /path/to/alternative/directory

image.png

如果数据目录尚不存在,请确保已创建该目录。此外,请确保 mongod 有权写入该目录。

此外,您还可以通过显式传递端口来指示MongoDB侦听备用端口上的连接,如下所示:

mongod --port 94301

为避免冲突,请确保端口未在使用中。

要同时更改数据目录和端口,只需同时指定 --dbpath 和 --port 选项以及 mongod 可执行文件的相应替代值。


接下来,您将学习如何在MongoDB实例中创建首选项数据库。

创建首选项数据库

首先,创建一个名为 prefs 的首选项数据库。创建后,将username和zip code的元组(或对)存储在此数据库内的名为 location 的集合中。然后将可用的数据集存储在此定义的结构中。在MongoDB术语中,它将转化为执行以下步骤:

  1. 切换到首选项数据库。

  2. 定义需要存储的数据集。

  3. 将定义的数据集保存在名为位置的集合中。

要执行这些步骤,请在 Mongo JavaScript 控制台上键入以下内容:

use prefs

w = {name: "John Doe", zip: 10001}; 
x = {name: "Lee Chang", zip: 94129};
y = {name:"Jenny Gonzalez", zip: 33101); 
z = {name: "Srinivas Shastri", zip: 02101}; 

db.location.save(w);

db.location.save(x);
db.location.save(y); 
db.location.save(z);

就是这样!几个简单的步骤,数据存储就准备就绪。不过,在继续之前有一些快速说明:

use prefs 命令将当前数据库更改为名为 prefs 的数据库。

但是,从未显式创建数据库本身。同样,通过将数据点传递给数据库,将数据点存储在位置集合中。

location.save()方法.该集合也不是显式创建的。在MongoDB中,只有在将数据插入其中时,才会创建数据库和集合。因此,在此示例中,它是在插入第一个数据点(名称:“John Doe”,zip:10001})时创建的。

现在可以查询新创建的数据库以验证存储的内容。若要获取存储在名为 location 的集合中的所有记录,请运行 db.location.find()。

运行数据库 db.location.find()显示以下输出:

image.png

计算机上的输出应相似。唯一会改变的是 objectId。ObjectId是MongoDB用MongoDB术语唯一标识每个记录或文档的方式。

MongoDB使用Objectid唯一地标识集合中的每个文档。文档的对象标识存储为该文档the_id属性。

插入记录时,可以将任何唯一值设置为对象 Id,该值的唯一性需要由开发人员保证。

还可以避免在插入记录时指定 _id 属性的值。

在这种情况下,MongoDB会创建并插入一个适当的唯一ID。MongoDB中生成的这种id是BSON,二进制JSON的缩写,格式最好总结如下:

    BSON 对象 ID 是一个 12 字节的值。

    前 4 个字节表示创建时间戳。它表示自纪元以来的秒数。此值必须存储在大字节序中,这意味着序列中最重要的值必须存储在最低存储地址中。

    接下来的 3 个字节表示计算机 ID。以下 2 个字节表示进程 ID。

    最后 3 个字节表示计数器。此值必须存储在大端序中。

    BSON 格式除了确保唯一性外,还包括创建时间戳。所有标准MongoDB驱动程序都支持BSON格式ID。

不带参数的 find 方法返回集合中的所有元素。在某些情况下,这可能不可取,可能只需要集合的子集。若要了解查询可能性,请将以下附加记录添加到位置集合:

image.png

您可以通过 mongo shell 完成此操作,如下所示:

image.png

要仅获取 10001 邮政编码中的人员的列表,您可以按如下方式查询:

>db.location.find({zip10001));

{ “_id” : objectId(“4c97053abe67000000003857”), “name”:“John Doe”,“zip”:10001}

{ “_id” : objectid(“4c97a6555c760000000054d8”), “name” : “Don Joe”, “zip”:10001}

要获得所有名称为“John Doe”的人的列表,您可以像这样查询:

>db.location.find({name:“John Doe”});

{“_id” : ObjectId(“4c97053abe67000000003857”), “name” : “John Doe”, “zip”:10001}

{ “_id” : ObjectId(“4c97a7ef5c760000000054da”),“name” : “John Doe”, “zip” : 94129}

在筛选集合的两个查询中,查询文档作为参数传递给 find 方法。查询文档指定需要匹配的键和值的模式。MongoDB支持许多高级查询机制,而不仅仅是简单的过滤器,包括借助正则表达式的模式表示。

由于数据库包含较新的数据集,因此集合的结构可能会成为约束,因此需要修改。在传统的关系数据库意义上,您可能需要更改表架构。在关系数据库中,更改表模式还意味着承担复杂的数据迁移任务,以确保新旧架构中的数据同时存在。

在MongoDB中,修改集合结构是微不足道的。更准确地说,集合(类似于表)是无架构的,因此它允许您在同一集合中存储不同的文档类型。

参考一个示例,您需要存储另一个用户的位置首选项,该用户的名称和邮政编码与数据库中已存在的文档相同,例如另一个({name:“Lee Chang”,zip:94129})。当然,有意和不现实地假设名称和 zip 对是唯一的!

为了从数据库中的第二个Lee Chang中区分出第二个Lee Chang,添加了一个附加属性,即街道地址,如下所示:

image.png

现在,使用 find 获取所有文档,返回以下数据集:

>db.location.find();

{“_id” : ObjectId(“4c97053abe67000000003857”), “name”:“John Doe”, “zip” :10001 }

{ “_id” : objectId(“4c970541be67000000003858”), “name” : “Lee Chang”, “zip”:94129}

(“_id” : objectId(“4c970548be67000000003859”), “name” : “Jenny Gonzalez”, “zip” :33101 }

{“_id” : objectId(“4c970555be6700000000385a”), “name” : “Srinivas Shastri”, “zip”:1089}

{“_id” : objectId(“4c97a6555c760000000054d8”), “name” :“Don Joe”, “zip”:10001}

{ “_id” : objectId(“4c97a7ef5c760000000054da”), “name” :“John Doe”, “zip”:94129}

{“_id” : ObjectId(“4c97add25c760000000054db”), “name” : “Lee Chang”, “zip” : 94129, “streetAddress” : “37000 Graham Street”)

您可以从大多数主流编程语言访问此数据集,因为存在这些驱动程序。

示例二:存储汽车品牌和型号数据

此示例中使用了分布式列族数据库 Apache Cassandra。

Apache Cassandra 是一个分布式数据库,因此您在使用此产品时通常会设置数据库集群。对于此示例,通过将 Cassandra 作为单个节点运行来避免设置集群的复杂性。在生产环境中,您不需要这样的配置,但您现在只是在试水并熟悉基础知识,因此单个节点就足够了。

Cassandra数据库可以通过简单的命令行客户端或通过Thrift接口进行接口。Thrift接口帮助各种编程语言连接到Cassandra。从功能上讲,您可以将 Thrift 接口视为通用的多语言数据库驱动程序。

继续使用汽车品牌和模型数据库,首先启动Cassandra并连接到它。

启动 Cassandra 并连接到它

您可以通过从提取 Cassandra 压缩(tarred 和 gzipped)发行版的文件夹中调用 bin/cassandra 来启动 Cassandra 服务器。对于此示例,运行 bin/ Cassandra-f-f 选项使 Cassandra 在前台运行。这会在您的机器上本地启动一个 Cassandra 节点。作为群集运行时,将启动多个节点,并将这些节点配置为相互通信。对于此示例,一个节点足以说明在 Cassandra 中存储和访问数据的基础知识。

启动 Cassandra 节点时,您应该在控制台上看到如下输出:

image.png

特定的输出来自我的Windows 7 64位机器,当Cassandra可执行文件从Windows PowerShell运行时。如果您使用不同的操作系统和不同的 shell,您的输出可能会略有不同。

运行 APACHE CASSANDRA 节点

Apache Cassandra 存储配置在 conf/cassandra.yaml 中定义。当您下载并提取以压缩的 tar.gz 格式提供的 Cassandra 稳定版或开发发行版时,您将获得一个 Cassandra。具有某些默认配置的 yaml 文件。例如,它期望提交日志位于 /var/lib/cassandra/commitlog 目录中,数据文件位于 /var/lib/cassandra/data 目录中。此外,Apache Cassandra 使用 log4j 进行日志记录。

Cassandra log4j 可以通过 conf/log4j-server .properties 进行配置。默认情况下,Cassandra log4j 希望将日志输出写入 /var/log/ cassandra/system.log。如果要保留这些默认值,请确保这些目录存在,并且您具有访问和写入它们的适当权限。如果要修改此配置,请确保在相应的日志文件中指定您选择的新文件夹。

在我的示例中,从conf/cassandra.yaml提交日志和数据目录属性是:


# directories where Cassandra should store data on disk.

data_file_directories:

    -/var/lib/cassandra/data

# commit log

commitlog_directory: 

    /var/lib/cassandra/commitlog

cassandra.yaml 中的路径值不需要以 Windows 的格式指定。例如,您不需要将提交日志路径指定为

commitlog_directory: c:\var\lib\cassandra\commitlog.

在我的示例中,来自 conf/log4j-server.properties 的 log4j 追加器文件配置是:

log4j.appender.R.File=/var/log/cassandra/system.log

连接到计算机上正在运行的 Cassandra 节点的最简单方法是使用Cassandra 命令行界面 (CLI)。

启动命令行就像运行 bin/ Cassandra-cli 一样简单。您可以将主机和端口属性传递给 CLI,如下所示:

bin/cassandra-cli-host localhost -port 9160

运行 cassandra-cli 的输出如下:

image.png

要获取可用命令的列表,请键入 help 或 ?您将看到以下输出:

image.png

将列表结果限制为 N。现在您已经熟悉了 Cassandra 基础知识,您可以继续为汽车品牌和型号数据创建存储定义,并将一些示例数据插入并访问到这个新的 Cassandra 存储方案中。

使用 Cassandra 存储和访问数据

首先要了解密钥空间和列系列的概念。键空间和列族最接近的关系数据库并行是数据库和表。尽管这些定义并不完全准确,有时甚至具有误导性,但它们是理解密钥空间和列族使用的良好起点。随着您熟悉基本的使用模式,您将对这些概念有更深入的欣赏和理解,这些概念超出了它们的关系相似之处。

首先,列出 Cassandra 服务器中的现有密钥空间。转到 cassandra-cli,键入 show keyspaces 命令,然后按 Enter 键。因为您正在重新安装 Cassandra,所以您可能会看到类似于以下内容的输出:

image.png

顾名思义,系统密钥空间就像 RDBM 中的管理数据库。系统键空间包括一些预定义的列系列。

键空间将列族组合在一起。通常,每个应用程序定义一个密钥空间。数据复制在密钥空间级别定义。这意味着数据的冗余副本的数量以及这些副本的存储方式是在密钥空间级别指定的。

Cassandra 发行版在一个名为 schema-sample.txt 的文件中附带了一个示例键空间创建脚本,该文件在 conf 目录中可用。您可以按如下方式运行示例密钥空间创建脚本:

PS C:\applications\apache-cassandra-0.7.4>.\bin\cassandra-cli -host localhost --file.\conf\schema-sample.txt

再次通过命令行客户端连接,并在界面中重新发出 show keyspaces 命令。这次的输出应该是这样的:

image.png image.png

接下来,使用示例中的脚本在此密钥空间中创建 CarDataStore 密钥空间和 Cars 列系列。

schema-cardatastore.txt:

/*schema-cardatastore.txt*/

create keyspace CarDataStore

    with replication_factor=1

    and placement_strategy = 'org. apache. cassandra. locator. SimpleStrategy' ; 

use CarDatastore;

create column family Cars 

    with comparator=UTF8Type

    and read_repair_chance=0.1 

    and keys_cached=100

    and gc_grace=0

    and min_compaction_threshold=5

    and max_compaction_threshold=31;

您可以运行schema-cardatastore.txt脚本,如下所示:

PS C:\applications\apache-cassandra-0.7.4> bin/cassandra-cli -host localhost --file c:\workspace\nosql\examples\schema-cardatastore.txt

您已成功添加新的密钥空间!返回到脚本并简要回顾如何添加密钥空间。您添加了一个名为 carDatastore 的密钥空间。您还在此密钥库中添加了一个名为 columnFamily 的工件。ColumnFamily的名字是Cars.你稍后会看到ColumnFamily的运行,但现在把它们想象成表格,在ColumnFamily标签中,还包括一个名为 comparewith 的属性。比较的值被指定为 UTF8Type。Comparewith属性值影响行键的索引和排序方式。。密钥空间定义中的其他标记指定复制选项。CarDataStore 的复制因子为 1,这意味着 Cassandra 中只存储了一个数据副本。

接下来,将一些数据添加到 CarDataStore 密钥空间,如下所示:

 [defaulteunknown] use CarDatastore;

Authenticated to keyspace:CarDatastore

[default@CarDatastore] set Cars['Prius']['make'] = 'toyota';

Value inserted.

[defaulteCarDatastore] set Cars['Prius']['model'] = 'prius 3'; Value inserted.

[default@CarDataStore] set Cars['Corolla']['make'] ='toyota'; Value inserted.

[default@carDatastore] set Cars['Corolla']['model']='le'; Value inserted.

[defaulteCarDataStore] set Cars['fit']['make'] = 'honda'; Value inserted.

[defaulteCarDataStore] set Cars['fit']['model'] = 'fit sport'; Value inserted.

[defaulteCarDataStore] set Cars['focus']['make'] Value inserted.

[default@CarDataStore] set Cars['focus']['model']= Value inserted.

图示的命令集是将数据添加到 Cassandra 的一种方法。使用此命令,在行中添加名称-值对或列值,而行又在键空间的列系列中定义。例如,设置 Cars['Prius']['make'] = 'toyota',一个名称-值对:“make”= 'toyota' 被添加到一行中,该行由键“Prius”标识。Prius标识的行是“car”列系列的一部分。“car”列系列是在 CarDatastore 中定义的,您知道它是密钥空间。

添加数据后,可以查询和检索它。要获取Prius标识的行的名称-值对或列名和值,请使用以下命令:获取Cars['Prius']。输出应如下所示:


[default @ CarDatastore] get Cars['Prius'];

= > (column=make, value=746f796f7461, timestamp=1301824068109000)
= > (column=model, value=70726975732033, timestamp=1301824129807000)
Returned 2 results.

构造查询时要小心,因为行键、列系列标识符和列键区分大小写。因此,传入“prius”而不是“Prius”不会返回任何名称值元组。尝试通过 CLI 运行获取Cars['Prius']。您将收到一个响应,内容为“Returned 0 results”。此外,在查询之前,请记住发出使用 CarDatastore 使 CarDatastore 成为当前密钥空间。

要仅访问“Prius”行的“make”名称-值数据,您可以像这样查询:

[default@CarDataStore] get Cars['Prius']['make'];

=>(column=make,value=746f796f7461, timestamp=1301824068109000)

Cassandra 数据集可以支持比目前显示的更丰富的数据模型,查询功能也比所示的更复杂,

在浏览了两个简单的示例之后,一个涉及文档存储 MongoDB,另一个涉及列数据库 Apache Cassandra,您可能已经准备好使用您选择的编程语言开始与这些示例进行交互。


本文正在参加「技术专题19期 漫谈数据库技术」活动