MongoDB数据建模-第八章:日志记录和实时分析(三)

我们继续讲解数据库模式设计相关内容,本节包括:

单文档拮解决方案;

TTL索引;

分片(Sharding).

3.3.2 单文档解决方案

如果认为我们将有大量信息来创建分析,则每个事件有一条文档记录可能会有所帮助。 但在我们试图解决的例子中,为每个HTTP请求保存单文档是昂贵的。 利用MongoDB中的模式灵活性,这将有助于我们随着时间的推移增长文档。 以下建议的主要目标是减少持久化文档的数量,同时优化我们集合中读写操作的查询。 我们正在寻找的文档应该能够向我们提供所需的全部信息,以了解每分钟请求中的资源吞吐量; 因此我们可以拥有这种结构的文档:

具有资源的字段

包含事件日期的字段

事件发生的时间以及总点数

以下文档实现了上述列表中描述的所有要求:

通过此文档设计,我们可以每分钟检索一次特定资源中发生的事件数量。 我们还可以通过日常字段了解当天的总请求,并使用它来计算我们想要的任何内容,例如每分钟请求数或每小时请求数。 为了演示在这个集合上进行的写入和读取操作,我们将使用运行在Node.js平台上的JavaScript代码。 所以,在继续之前,我们必须确保我们的机器上安装了Node.js。

如果您需要帮助,您可以在以下网站找到更多信息http://nodejs.org.

我们应该做的第一件事是创建一个应用程序所在的目录。在终端中,执行以下命令:

接下来,我们导航到我们创建的目录并启动项目:

回答向导提出的所有问题,以创建我们新项目的初始结构。 目前,我们将根据我们提供的答案获得一个package.json文件。 下一步是为我们的项目设置MongoDB驱动程序。 我们可以通过编辑package.json文件(包括其依赖关系的驱动程序参考)或通过执行以下命令来完成此操作:

上述命令将为我们的项目安装MongoDB驱动程序并将引用保存在package.json文件中。 我们的文件应该是这样的:

最后一步是使用我们的示例代码创建app.js文件。 以下是示例代码,向我们展示了如何在Web服务器上统计事件并将其记录在我们的集合中:

前面的示例代码非常简单。 其中,我们有logDailyHit函数,它负责记录事件并在文档每日字段中增加一个单位。 第二个函数是logMinuteHit函数,它负责记录事件的发生并递增表示当天分钟的文档分钟字段。 这两个函数都有一个更新查询,如果文档不存在,则该查询具有值为true的upsert选项,在这种情况下,文档将被创建。 当我们执行以下命令时,我们将在资源"/"上记录一个事件。 要运行代码,只需导航到项目目录并执行以下命令:

如果一切正常,运行该命令后我们应该看到以下输出:

除了以前的模型可以给我们提供的所有东西之外,这个模型还有一些优势。 我们注意到的第一件事是,每当我们注册发生在Web服务器上的新事件时,我们将只操作一个文档。

下一个优点还在于,在给定特定资源的情况下,我们可以轻松找到所需的信息,因为我们在一个文档中拥有一整天的信息,这将导致我们在每个查询中操纵更少的文档。 当我们思考报告时,这种有时间的设计模式的处理方式会给我们带来很多好处。

文本和图形表示都可以从这个集合中轻松提取,用于历史或实时分析。 但是,除了以前的方法,我们也必须处理一些限制。 正如我们所看到的,当事件文档发生在Web服务器上时,我们将daily字段和minute字段都增加进去。

如果当天没有报告资源中的事件,那么将会创建一个新文档,因为我们在更新查询中使用了upsert选项。 如果资源中某个事件在给定分钟内第一次发生,则会发生同样的事情 - $inc运算符将创建新的分钟字段并将值设置为"1"。 这意味着我们的文档会随着时间的推移而增长,并且会超过最初为它分配的MongoDB的大小。每当分配给文档的空间已满时,MongoDB自动执行重新分配操作。

这一整天的重新分配操作直接影响了数据库的性能。 我们应该做什么? 和它一起存在吗? 不可以。我们可以通过添加一个为我们的文档预先分配空间的过程来减少重新分配操作的影响。

总而言之,我们将为应用程序负责创建一份包含一天所有会议记录的文档,并使用值0初始化每个字段。 通过这样做,我们将避免MongoDB在白天进行太多重新分配操作。

要了解更多关于记录分配策略的信息,请访问MongoDB参考用户手册: http://docs.mongodb.org/manual/core/storage/#record-allocation-strategies.

为了举例说明我们如何预先分配文档空间,我们可以在我们的app.js文件中创建一个新函数:

下载示例代码:

您可以从http//www.packtpub.com上的帐户下载所购买的所有Packt Publishing书籍的示例代码文件。 如果您在其他地方购买了此书,可以访问http//www.packtpub.com/support并注册,将这些文件直接发送给您。

要将当前日期中的空间预先分配给"/"资源,只需运行以下命令:

执行输出如下:

Connected to server Pre-allocated successfully! Disconnected from server

我们可以在mongod shell上运行findOne命令来检查新文档。创建的文档非常长,所以我们只会展示它的一部分:

建议我们在午夜前预先分配文档以确保应用程序的顺利运行。 如果我们安排了具有适当安全余量的创建,将不会有任何午夜后文档创建事件的风险。

那么,随着重新分配问题的解决,我们可以回到初始化文档重新设计的问题:数据的增长。 即使将我们集合中的文档数量减少到每个事件每天一个文档,我们仍然可能遇到存储空间问题。

当我们的Web服务器上有太多的资源接收事件时,就会发生这种情况,我们无法预测应用程序生命周期中会有多少新资源。

为了解决这个问题,我们将使用两种不同的技术:TTL索引和分片。

3.3.3 TTL索引

并不总是这样,我们需要将所有日志信息永久存储在我们的服务器上。 操作人员限制存储在磁盘上的文件数量已经成为标准做法。

通过同样的原因,我们可以限制我们需要集合中存活的文件数量。 为了做到这一点,我们可以在date字段中创建一个TTL索引,指定集合中一个文档存在多久。 请记住,一旦我们创建了TTL索引,MongoDB就会自动从集合中删除过期的文档。

假设事件命中信息仅在一年内有用。 我们将使用属性expireAfterSeconds在日期字段上创建一个索引,其值为31556926,对应于一年的秒数。

在mongod shell上执行以下命令会在我们的events集合上创建索引:

如果索引不存在,则输出应该如下所示:

完成此操作后,我们的文档将根据日期字段在我们的集合中保存一年,此后MongoDB将自动删除它们。

3.3.4 分片(Sharding)

如果您是拥有无限资源且希望在磁盘上存储大量信息的人群中的一员,那么缓解存储空间问题的一个解决方案是通过分片集合来分布数据存储。

正如我们之前所说的,当我们选择分片键时,应该增加我们的努力,因为通过分片键我们将保证我们的读写操作将被由分片平均分布,也就是说,一个查询将会 在集群上定位一个或几个分片。

一旦我们完全控制了我们的Web服务器上有多少资源(或页面),以及这个数字将如何增长或减少,资源名称就成为分片键的不错选择。 但是,如果我们拥有比其他请求(或事件)更多的资源,那么我们将有一个将被重载的分片。 为了避免这种情况,我们将包含date字段以组成分片键,这也将使我们在包含此字段的查询执行中获得更好的性能。

请记住:我们的目标不是解释分片群集的设置。 考虑到您之前创建了分片群集,我们将向您展示分片集合的命令。

要使用我们选择的分片键对事件集合进行分片,我们将在mongos shell上执行以下命令:

预期输入如下:

如果我们的事件集合中包含任何文档记录,那么在对集合分片之前,我们需要创建一个索引,其中分片键是前缀。为创建索引,请执行以下命令: 随着启用分片的集合,我们将有更多的能力将数据存储到事件集合中,并随着数据的增长而获得潜在的性能收益。 既然我们已经设计了文档并准备好了我们的集合以接收大量的数据,我们来执行一些查询吧!

稍作思考,下一节将继续我们的内容,开始进行查询设计操作。

3.3.5 查询报告(Quering for Report)

到现在为止,我们已经集中精力将数据存储在数据库中。 这并不意味着我们不关心读取操作。 我们所做的一切都是通过描述应用程序的轮廓来实现的,并试图覆盖所有要求,以便为我们的数据库做好准备。

因此,我们现在将说明必须查询集合的一些可能性,以便基于存储的数据构建报表。

如果我们需要的是关于资源总点击量的实时信息,我们可以使用我们的日常字段来查询数据。 通过此字段,我们可以确定一天中某个特定时间的资源总点击数,甚至可以确定一天中某分钟的平均每分钟资源请求数。

要根据当天的当前时间查询总点击数,我们将创建一个名为getCurrentDayhits的新函数,并且要查询一天中每分钟的平均请求数,我们将在app.js文件中创建getCurrentMinuteStats函数:

为了看到发生的奇迹,我们应该在终端中运行以下命令:

如果一切正常,输出应该看起来像这样:

Connected to server Document found. { _id: 551fdacdeb6efdc4e71260a2, daily: 27450 } Document found. { _id: 551fdacdeb6efdc4e71260a2, minute: { '183': 142 } } Disconnected from server

另一种可能性是检索每日信息以计算资源的每分钟平均请求数,或者获取两个日期之间的数据集以构建图或表格。 下面的代码有两个新函数:getAverageRequestPerMinuteStats和getBetweenDatesDailyStats,getAverageRequestPerMinuteStats计算资源每分钟的平均请求数,getBetweenDatesDailyStats显示如何检索两个日期之间的数据集。 让我们看看app.js文件:

正如您所看到的,查询事件集合中的数据的方法有很多。这些是如何提取数据的一些非常简单的示例,但它们是功能可靠的。

本章总结

本章向您展示了一个从头开始的模式设计过程示例,以解决现实生活中的问题。 我们从一个详细的问题及其要求开始,并演变出模式设计,以更好地利用可用资源。 基于这个问题的示例代码非常简单,但将作为您终生学习提供基础服务。 很棒的! 在最后一章中,我们有机会在几页内做出回到本书第一章的旅程,并应用沿途介绍的概念。 但是,正如你现在一定已经意识到的那样,MongoDB是一个年轻、饱满的数据库,充满各种可能性。 它被社区拥抱着 ——包括你自己,在每个新版本发行中都变得更大。 因此,如果您发现自己面临着一个新的挑战,即您意识到不止一个单一的解决方案,请执行任何必要或有用的测试认证。 同事也可以帮忙,所以跟他们谈谈。

永远记住,一个好的设计是适合实际需求的设计。

附记

到此,本书的全部内容就结束了,希望能带给你些许价值或参考。虽然从事IT行业已经十多年了,但还第一次独立翻译一本技术书籍,不足在所难免。还望理解和指正,后续还会再接再厉,带来更好的价值内容。再次感谢您的阅读。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180604A1CDE100?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券