面向 MongoDB 开发者的 CosmosDB 教程(全)
一、为什么是 NoSQL?
自从上学以来,我们大多数人都被教导要组织信息,这样它就可以用表格的形式来表示。但并不是所有的信息都能遵循这种结构,因此存在NULL
值。NULL
值表示没有信息的单元格。为了避免出现NULL
,我们必须将一个表拆分成多个表,从而引入规范化的概念。在规范化中,我们根据我们选择的规范化级别来拆分表。这些级别是 1NF(第一范式)、2NF、3NF、BCNF(Boyce–Codd 范式,或 3.5NF)、4NF 和 5NF,仅举几例。每一层都规定了分割,最常见的是,人们使用 3NF,它基本上没有插入、更新和删除异常。
为了实现规范化,必须将信息拆分到多个表中,然后在检索时,连接所有的表以理解拆分的信息。这个概念没有什么问题,对于在线事务处理(OLTP)来说仍然是完美的。
在一个处理来自多个数据流的数据并遵循一个定义的结构的系统上工作,是非常难以实现和维护的。数据量通常非常庞大,而且大多不可预测。在这种情况下,在数据检索期间插入和连接表时将数据分割成多个部分会增加额外的延迟。
我们可以通过插入自然形式的数据来解决这个问题。由于不需要或只需要很少的转换,插入、更新、删除和检索过程中的等待时间将大大减少。这样,纵向扩展和横向扩展将变得快速且易于管理。考虑到这种解决方案的灵活性,它是最适合所定义问题的解决方案。解决方案是 NoSQL,也称为不仅,或非关系,SQL。
人们可以进一步将性能置于一致性之上,这在 NoSQL 解决方案中是可能的,并由 CAP(一致性、可用性和分区容差)定理定义。在这一章中,我将讨论 NoSQL,它的不同类型,它与关系数据库管理系统(RDBMS)的比较,以及它未来的应用。
NoSQL 的类型
在 NoSQL,数据可以用多种形式表示。NoSQL 有多种形式,最常用的是键值、柱形图、文档图和图表。在这一节中,我将总结最常用的表单。
键值对
这是最简单的数据结构形式,但提供了出色的性能。所有的数据只通过键来引用,使得检索非常简单。这一类中最流行的数据库是 Redis Cache。表 1-1 给出了一个例子。
表 1-1
Key-Value Representation
| 钥匙 | 价值 | | :-- | :-- | | C1 | XXX XXXX XXXX | | C2 | One hundred and twenty-three million four hundred and fifty-six thousand seven hundred and eighty-nine | | C3 | 10/01/2005 | | 补体第四成份缺乏 | Z ZZZZ ZZZZ |键在有序列表中,HashMap 用于有效地定位键。
圆柱的
这种类型的数据库将数据存储为列,而不是行(像 RDBMS 一样),并针对查询大型数据集进行了优化。这种类型的数据库通常被称为宽列存储。这一类别中一些最受欢迎的数据库包括 Cassandra、Apache Hadoop 的 HBase 等。
与键-值对数据库不同,列数据库可以存储与构成表的键相关联的数百万个属性,但存储为列。但是,作为一个 NoSQL 数据库,它没有任何固定的名称或列数,这使它成为一个真正的无模式数据库。
文件
这种类型的 NoSQL 数据库以文档的形式管理数据。这种数据库有许多实现,它们有不同类型的文档表示。一些最流行的存储数据如 JSON、XML、BSON 等。以文档形式存储数据的基本思想是通过匹配其元信息来更快地检索数据(见图 1-1 和 1-2 )。
图 1-2
Sample document structure (XML) code
图 1-1
Sample document structure (JSON) code
文档可以包含许多不同形式的数据键值对、键数组对,甚至是嵌套文档。MongoDB 是这一类别中最受欢迎的数据库之一。
图表
这种类型的数据库以网络的形式存储数据,例如社会关系、家谱等。(参见图 1-3 )。它的美妙之处在于它存储数据的方式:使用图形结构进行语义查询,并以边和节点的形式表示。
节点是表示实体的叶信息,两个节点之间的关系(或多个关系)使用边来定义。在现实世界中,我们与其他每个人的关系是不同的,这可以通过各种属性来区分,在边缘层次上。
图 1-3
Graph form of data representation
数据的图表形式通常遵循 Apache TinkerPop 定义的标准,这一类别中最流行的数据库是 Neo4J(见图 1-4b ,它描述了图 1-4a 中执行的查询的结果)。
图 1-4b
Result in TinkerPop console
图 1-4a
Gremlin Query on TinkerPop Console to Fetch All the Records
对 NoSQL 有什么期待
为了更好地理解使用 NoSQL 的必要性,让我们从事务的角度将其与 RDBMS 进行比较。对于 RDBMS,任何事务都将具有某些特征,这些特征被称为 ACID——原子性、一致性、隔离性和持久性。
原子数
该属性确保事务应该完成或者根本不存在。如果由于任何原因,事务失败,在事务过程中发生的所有更改都将被删除。这称为回滚。
一致性
此属性确保系统在事务完成(失败或成功)后处于一致状态。
隔离
该属性确保每个事务对资源(例如表、行等)具有排他性。事务的读取和写入对于任何其他事务的读取和写入都是不可见的。
持久性
该属性确保数据应该是持久的,并且在硬件、电源、软件或任何其他故障期间不会丢失。为此,系统将记录事务中执行的所有步骤,并在需要时重新创建状态。
相比之下,NoSQL 依赖于 CAP 定理的概念,如下所示。
一致性
这确保了由任何事务执行的读取具有所有节点的最新信息/数据。它与 ACID 中定义的一致性略有不同,因为 ACID 的一致性声明所有数据更改都应为数据库连接提供一致的数据视图。
有效
每次请求数据时,都给出一个没有最新数据保证的响应。这对于需要高性能和容忍数据不确定性的系统来说至关重要。
分区容差
该属性将确保节点之间的网络故障不会影响系统故障或性能。这将有助于确保系统的可用性和一致的性能。
大多数时候,在持久的分布式系统中,网络持久性是内置的,这有助于使所有节点(分区)始终可用。这意味着我们只剩下两个选择,一致性或可用性。当我们选择可用性时,系统总是会处理查询并返回最新的数据,即使不能保证数据的并发性。
另一个定理 PACELC 是 CAP 的扩展,它指出如果一个系统在没有分区的情况下正常运行,那么必须在延迟和一致性之间做出选择。如果系统是为高可用性而设计的,则必须复制它,然后在一致性和延迟之间进行权衡。
因此,在定义分区容差时,架构师必须在可用性、一致性和延迟之间选择适当的平衡。以下是几个例子。
示例 1:可用性
例如,考虑安装在电梯上用于监控该电梯的设备。该设备向主服务器发送消息以提供状态报告。如果出现问题,它会提醒相关人员执行紧急响应。丢失这样的消息将危及整个应急响应系统,因此在这种情况下选择可用性而不是一致性将是最有意义的。
示例 2:一致性
考虑一个记录奖励积分分配和兑换的奖励目录系统。在兑换过程中,系统必须处理在时间点累积的奖励,并且事务应该是一致的。否则,您可以多次兑换奖励。在这种情况下,选择一致性是最关键的。
NoSQL 和云
NoSQL 旨在横向扩展,可以跨越数千个计算机节点。它已经使用了相当一段时间,并因其无与伦比的性能而越来越受欢迎。然而,没有一个通用的数据库。因此,我们应该为给定的用例选择最好的技术。与其他传统系统不同,NoSQL 在设计上没有严格的界限,但它可以在内部部署的情况下轻松实现突破。
如今,行业需求不断增长,关注点正从资本支出(Capex)转向运营支出(Opex),这意味着没有人真的想预先支付。这使得云成为架构师显而易见的选择,但即使在云中,服务也分为三个主要类别:基础设施即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS)。让我们更仔细地看看这些术语。
基础设施即服务
这是最简单、最直接的云计算入门方式,在提升和转移场景中备受青睐。在这种情况下,云服务提供商负责虚拟化的所有方面,例如电力、房地产、冷却、硬件、虚拟化等。其他所有事情的责任都在用户身上。他们必须负责操作系统、应用服务器、应用等。此类服务的示例包括用于 Windows/Linux 的通用虚拟机、用于 SQL Server、SharePoint 等的专用虚拟机。
聚丙烯酸钠
这最适合希望只关注应用而将其他一切都交给云服务提供商的应用开发者。PaaS 将有助于获得最大的可扩展性和性能,而无需担心端点的可用性。在这种情况下,云服务提供商保护开发者直到平台级别,意味着基础平台,例如应用服务器、数据库服务器等。这些服务的示例包括数据库即服务和缓存即服务等。
软件服务
在这种情况下,甚至软件的责任也在于云服务提供商。一切都将被卸载到云服务提供商,但开发者仍然可以上传他或她的定制或通过 API 集成它们。这些服务的示例包括 Office 365、Dynamics 365 等。
前面提到的所有服务都有各自的优点和缺点。然而,完全没有必要坚持使用一种类型的服务。相反,人们可以为不同的目的选择它们的组合。例如,主应用部署在与 Office 365 集成的 SaaS 上。应用的遗留组件可以部署到虚拟机(IaaS)上,而数据库可以作为服务 PaaS 部署到数据库上。
结论
PaaS 是开发者友好的选择,是应用开发者的最佳选择,因为它将使他们摆脱基础架构管理难题,包括数据库服务的可用性、数据库服务支持、存储管理、监控工具等。
我将讨论业界最广泛和最快速采用的 NoSQL 数据库,在后续章节中作为 PaaS 讨论。
二、Azure Cosmos DB 概述
NoSQL 旨在解决与可伸缩性、耐用性和性能相关的问题。然而,即使是高性能系统也会受到本地机器或云中虚拟机的计算能力的限制。在云中,拥有大规模计算能力的 PaaS 是最理想的选择,因为在这种情况下,人们不必担心可伸缩性、性能和可用性。所有这些都将由云服务提供商提供。
Cosmos DB 是 Azure 中众多 PaaS 服务之一,Azure 是微软公共云产品的名称。它旨在考虑六个关键方面:全球分布、弹性规模、吞吐量、定义良好的一致性模型、可用性、有保证的低延迟和性能,以及轻松的迁移。
让我们详细地看一下每个方面。
数据模型概述
Azure Cosmos DB 的数据模型和 MongoDB 没什么区别。有一个例外,在 MongoDB 中,父级是 MongoDB 实例,而在 Azure Cosmos DB 中,它被称为 Azure Cosmos DB account,是数据库的父实体。每个帐户可以有一个或多个数据库;每个数据库可以有一个或多个集合;并且每个集合都可以存储 JSON 文档。图 2-1 展示了 Azure Cosmos DB 数据模型。
图 2-1
Overview of the Azure Cosmos DB data model
设置 Azure Cosmos DB
要提供 Azure Cosmos DB 帐户,请导航到 https://portal.azure.com
,然后单击创建资源,如图 2-2 所示。
图 2-2
Click the Create a resource link (circled in red)
点击数据库➤宇宙数据库(图 2-3 )。
图 2-3
Select Azure Cosmos DB from the list of popular services, or search for Cosmos DB
现在,将出现一个包含以下字段的表单:
- ID:该字段要求您为您的 Cosmos DB 帐户指定唯一的标识符。该 ID 将作为您的 Cosmos DB 帐户的 URI 的前缀,即
<ID>.documents.azure.com
。一些约束条件适用于此输入字段,包括:- 允许最少三个字符,最多三十个字符。
- 不允许使用特殊字符,除了连字符(-)。
- 只允许小写输入。这种约束有助于确保 URI 的有效性。
- API:该字段要求您指定要创建的帐户类型。它总共提供了五个 API 选项,如下所示(对于本书,请选择 MongoDB,但是您当然可以使用其他 API):
- 结构化查询语言
- MongoDB
- 卡桑德拉
- 表格存储
- Gremlin(图形)
- 订阅:此字段要求您指定将在其下创建帐户的 Azure 订阅 ID。
- 资源组:该字段要求您指定现有的或新的资源组名称。资源组帮助您对 Azure 服务实例进行逻辑分组,例如,一个暂存资源组可以包含暂存所需的所有资源,其中可能包括虚拟机、虚拟网络、Azure Cosmos DB 帐户、Azure Redis 缓存等。
- 位置:在此字段中,选择离您的用户最近的 Azure 区域。作为 Ring 0 服务,这使得所有公开可用的 Azure 区域可用。你会发现很多选择。
- 启用地理冗余:如果选中该复选框,将在成对区域内创建一个复本。不用担心;您也可以在以后添加更多的副本区域。您可能想知道什么是成对区域。我来总结一下。每个 Azure 区域与同一地理区域内的另一个区域配对以形成区域对。Azure 确保更新补丁不会同时应用于一对中的所有 Azure 区域。一旦第一个区域升级,第二个区域也将升级。在全局中断的情况下,Azure systems 确保优先处理一对区域中的一个区域,这样至少有一个区域可以正常运行。
- Pin to dashboard:正如 Windows dashboard 上有快捷方式一样,Azure Portal dashboard 上也有快捷方式,用于快速访问。请选中此框。
现在点击 Create 按钮,提交请求以提供您的 Azure Cosmos DB 帐户(图 2-4 )。
图 2-4
Input form for provisioning an Azure Cosmos DB account
一旦 Azure Cosmos DB 帐户被提供,只需通过点击仪表板上的服务图标打开概述页面(假设表单上的 Pin to dashboard 选项被选中)。概览页面将具有关于服务端点的各种细节,包括 URI、读取位置、写入位置等。(图 2-5 )。
图 2-5
Overview of an Azure Cosmos DB account
现在,让我们创建一个数据库和集合来存放文档。这可以通过使用数据浏览器来实现。您将在屏幕左侧的可用选项列表中看到一个名为 Data Explorer 的选项。点击它打开数据浏览器,然后点击新建集合(参见图 2-6 )。
图 2-6
Data Explorer view
将出现一个用于添加集合的表单,包含以下字段:
图 2-7
Form to create a collection and database (with the Unlimited option)
- 数据库 ID:指定数据库的名称或选择一个现有的名称。
- 集合 ID:为集合指定一个唯一的名称(唯一性的范围是数据库)。
- 存储容量:有两种选择:固定和无限。对于固定的存储容量,集合的大小不能超过 10GB。如果您有精简的集合,并希望支付更少的费用,建议您选择此选项。通常,这意味着一个分区(参考 MongoDB shard),并且最大吞吐量(根据请求单元指定(参见第 7 章了解关于请求单元[ru]的更多信息))在这种情况下也将受到限制(参考下面的字段)。要详细了解分区,请参见第 5 章。第二个存储容量选项是无限的,因此存储可以根据需要扩展,并具有更大范围的请求单元。这是因为,在后台创建了多个分区,以满足您的扩展需求。
- 碎片键:如果选择了无限存储选项,该字段将变得可见(见图 2-7 )。对于无限制的存储,Azure Cosmos DB 执行水平扩展,这意味着它将在后台拥有多个分区(MongoDB 中的碎片)。这里,Azure Cosmos DB 期望一个分区键,它应该在所有记录中,并且不应该将
\ & *
作为键的一部分。碎片键应该是字段名,例如城市或客户地址(对于嵌套文档)等。 - 吞吐量:该字段用于指定 ru 的初始分配,ru 是计算+内存+ IOPS 的组合。如果您选择了固定存储选项,范围是从 400 到 10,000 个 RUs,不能扩展。使用无限存储选项,范围从 1000 RUs 到 100,000 RUs,可以通过拨打 Azure 支持电话进一步扩展。
- 惟一键:这个特性相当于 MongoDB 的惟一索引,在惟一索引中,您可以使用一个分片键将一个或多个字段定义为惟一的。例如,如果要求雇员的姓名是唯一的,请指定 employeeName。如果需要唯一的员工姓名和电子邮件地址,请指定员工姓名、电子邮件等。(有关步进的详细信息,请参见第 4 章)。请注意,它可以像 MongoDB 一样在创建集合后创建。
现在是查看文档的时候了。单击数据库名称旁边的箭头展开➤单击集合名称旁边的箭头展开➤单击文档,查看文档列表。由于目前还没有文档(见图 2-8 ),我们通过点击新建文档创建一个文档,提交清单 2-1 中给出的样本 JSON,(可以随意修改)。
图 2-8
Data Explorer, shown with Document View and New Document button (circled in red)
{
"_id" : "test",
"chapters" : {
"tags" : [
"mongodb",
"CosmosDB"
]
}
}
Listing 2-1Sample JSON Document
现在,单击 Save 按钮,这会将请求发送到 Azure Cosmos DB 以创建文档。一旦创建了文档,就可以在数据浏览器中对其进行管理(参见图 2-9 ,该图提供了特定文档的视图)。
图 2-9
Data Explorer with Document View (the Documents option and a “test” document are circled in red)
可以使用一个选项来构建 MongoDB shell。通过单击 New Shell 按钮,它将出现在一个窗口中,从该窗口中您可以执行大多数 MongoDB 查询(参见图 2-10 )。
图 2-10
Data Explorer with Shell View
也可以使用自己喜欢的 MongoDB 控制台。在屏幕左侧的选项列表中导航到 Quick start,然后单击 MongoDB Shell,这将显示自动生成的 connect 命令。点击字符串旁边的复制按钮复制该命令(参见图 2-11 ,然后打开 Linux/Windows 命令提示符并执行该命令。(对于 Linux,在命令提示符下将mongo.exe
改为mongo
;见图 2-12 。)
现在,我们可以尝试运行相同的命令,并比较结果(应该是相同的),参见图 2-13 和 2-14 。
图 2-14
Running a command against Azure Cosmos DB in MongoDB shell
图 2-13
Connection to Azure Cosmos DB from MongoDB console
图 2-12
Command pasted onto Linux console
图 2-11
Quick start for the MongoDB Shell’s auto-generated connect command Note
在写这本书的时候,shell 特性还在开发中。因此,尝试使用 MongoDB shell 来执行您的查询。
现在,让我们看看 Azure Cosmos DB 的关键特性。
交钥匙全球分销
地理复制是任何多租户应用的一个重要方面。考虑到业界对扩展云足迹的关注,现在在离用户地理位置更近的地方部署应用是可行的,但这并不是一件容易的事情。在实施之前,必须考虑各方面的问题。即使在 NoSQL 世界,这也可能是一场噩梦。
假设一个应用部署在澳大利亚,用户从美国访问它。用户将在每个请求中遇到巨大的延迟—每个请求大约 300 毫秒到 400 毫秒。您可能想知道延迟,简单的回答是,延迟是光速的函数,它必须通过多跳路由,包括路由/交换机,然后,在我们的情况下,必须通过海底电缆传输很长的距离,以满足一个请求。在我们的示例中,从澳大利亚东部到美国西海岸的单程大约为 150 毫秒,当您访问数据时,您会有两次大约 150 毫秒延迟内的请求和响应,这导致了大约 300 毫秒的延迟。这意味着,如果应用页面在加载时必须向服务器发送 5 个请求,那么 5 个请求(大约 400 毫秒/请求× 5 个请求/页面)将被计算为 2000 毫秒= 2 秒的延迟,这显然太多了。
现在,在澳大利亚和美国部署应用的单个实例怎么样?用户在访问应用时将获得最小的延迟,但是在远程区域部署数据库将导致巨大的延迟。对于每个应用请求,可能必须执行多次数据库往返,并且每次往返都将累积延迟,这意味着来自应用的响应将是所有数据库往返的累积。为了减少这种延迟,数据库还必须部署在靠近应用的区域,在这种情况下,需要两个实例:一个用于澳大利亚,另一个用于美国。(参见图 2-15 和 2-16 。)
图 2-16
Multi-geo deployment of application and database
图 2-15
Multi-geo deployment of only application (with a single roundtrip to the database)
现在噩梦开始了。在每个区域中,我们必须有两个数据库的副本实例(假设两端都具有高可用性),这意味着每个区域至少有两个副本。同步多个副本将是一项艰巨的工作,需要大量的管理和监控工作。
Azure Cosmos DB 已经预先解决了这种情况(嵌入到其设计中),其中通过单个实例,您可以实现高可用性,并且只需单击一下就可以创建地理副本。(参见图 2-17 。)所有复制方面的担忧都会被 Azure Cosmos DB 搞定。
图 2-17
Geo-replication with Azure Cosmos DB
但是,对于地理复制,必须考虑多个方面。
图 2-18
Impact of adding new region
- Azure 发展迅速,并尽可能快地扩大其覆盖范围。Azure Cosmos DB 作为最优先的服务之一,被指定为 Ring 0 服务,这意味着一旦新添加的 Azure 区域准备好业务,Azure Cosmos DB 应该可以在该区域使用。这有助于确保任何地理复制场景的最大地理分布。
- 在 Azure Cosmos DB 中,添加的区域数量没有限制。它将只受到 Azure 在给定时间点的区域数量的限制。
- 用户可以在运行时以编程方式添加或移除地理复制区域。Azure Cosmos DB 确保无论何时选择一个新区域,数据都将被复制(在 60 分钟内,如服务级别协议[SLA]中所定义的)参见图 2-18 。
- 当您添加至少一个副本时,您将自动从常规的 99.99%可用性 SLA 获得 99.999%可用性 SLA。此外,您还可以进行故障转移。Azure Cosmos DB 具有手动和自动故障转移功能。在自动故障切换的情况下,您可以设置故障切换区域的优先级。在手动故障转移的情况下,Azure Cosmos DB 保证零数据丢失。
- 即使在地理分布的情况下,Azure Cosmos DB 也有关于自动或手动故障转移时数据丢失的保证,这包含在 SLA 中。
潜伏
任何数据库最重要的方面是延迟。Azure Cosmos DB 确保了尽可能低的延迟,这种延迟受到光速和网络可靠性的制约。更强的一致性级别具有更高的延迟和 99.99%的可用性。宽松的一致性将为多区域实例提供更低的延迟和 99.999%的可用性。与其他数据库不同,Azure Cosmos DB 不会要求你选择延迟而不是可用性。它符合这两个标准,并根据所提供的吞吐量进行交付。
一致性
这是数据库的一个非常重要的方面,会影响数据库的质量。比方说,如果一个人已经选择了某种程度的一致性并启用了地理复制,那么可能会有人担心 Azure Cosmos DB 将如何保证这一点。为了解决这个问题,让我们仔细看看实现。CAP 定理证明,系统不可能在出现故障的情况下保持一致性和可用性。因此,系统可以是 CP(一致性和分区容错)或 AP(可用性和分区容错)。Azure Cosmos DB 坚持一致性,这使得它成为 CP。
生产能力
Azure Cosmos DB 可以无限扩展,并确保可预测的吞吐量。要扩展它,需要一个分区键,将数据隔离到一个逻辑/物理分区,这完全由 Azure Cosmos DB 管理。基于一致性级别分区集,它将使用不同的拓扑(例如,开始、菊花链、树等)进行动态配置。).在地理复制的情况下,分区键起着主要作用,因为每个分区集将分布在多个区域。
有效
Azure Cosmos DB 为单个区域提供 99.99%的可用性(一年可能不可用 52 分钟 35.7 秒),为多个区域提供 99.999%的可用性(一年可能不可用 5 分钟 15.6 秒)。它通过考虑每个操作的延迟上限来确保可用性,当您添加新副本或拥有许多副本时,延迟上限不会改变。无论是应用手动故障转移还是调用自动故障转移,都无关紧要。术语多宿主 API(应用编程接口)描述了对应用透明的故障转移,在故障转移发生后,不需要重新部署或配置应用。
可靠性
Azure Cosmos DB 确保每个分区都被复制,并且副本分布在至少 10 到 20 个容错域中。在回复到成功响应之前,每次写入都将由多数复制副本同步且持久地提交。那么异步复制将跨多个区域发生。这确保了在手动故障切换的情况下没有数据丢失,而在自动故障切换的情况下,有限陈旧性的上限将是数据丢失的最大窗口,这也包括在 SLA 中。您可以从门户网站监控 SLA 中涵盖的每个指标,请参考图 2-19 。
图 2-19
Viewing key monitoring metrics for Azure Cosmos DB
协议支持和多模式 API
Azure Cosmos DB 提供多模态 API,帮助开发者从各种 NoSQL 数据库迁移到 Azure Cosmos DB,而无需更改他们的应用代码。目前,Cosmos DB 支持 SQL API、MongoDB、Cassandra、Gremlin 和 Azure 表存储 API。
除了 API 支持,Azure Cosmos DB 还提供多模型实现。这意味着您可以用各种结构存储数据,即文档、键值、柱形图和图表。
表存储 API
Azure 表存储基于最简单的数据模型,即键-值对。表将数据存储为实体的集合。实体就像一行,每个实体都有一个主键和一组属性。属性是名称和类型值对,如列。首先,单击创建资源➤数据库➤宇宙数据库,然后填写表单并点击创建。对于表存储,您必须创建一个数据库和表,这将产生多个基于键值对的实体。(样表存放结构见图 2-20 。)
图 2-20
Table storage structure
要添加一个实体,点击 tablesdb 前面的箭头,点击所需表格➤前面的箭头,点击实体,然后点击添加实体(见图 2-21 )。
图 2-21
Data Explorer for table storage (selected operations are circled)
有两个强制属性将始终是实体的一部分:RowKey
& PartitionKey
(见图 2-22 )。PartitionKey
要求将数据平衡到多个分区中。RowKey
有助于唯一地标识行,如果在查询中作为标准的一部分使用,这将非常有效。TimeStamp
,不可编辑,总是最后修改服务器的日期时间。
图 2-22
Adding an entity in table storage
也可以使用。NET、JAVA、NodeJs、Python、F#、C++、Ruby 或 REST API 与 TableStorage API 交互。
SQL (DocumentDB) API
Azure Cosmos DB 从基于文档的数据模型开始,使用文档 SQL 进行查询交互。文档模型将定义存储的数据以 JSON 文档的形式交付(根据请求)。(图 2-23 展示了一个面向文档的结构示例。)这有助于缩短学习曲线,如果你对 SQL 有所了解的话。
图 2-23
Document-oriented structure
查询的结构应该是
SELECT <select_list/comma separated list of fields>
[FROM <from_specification>]
[WHERE <filter_condition>]
[ORDER BY <sort_specification]
FROM 子句
此子句的目的是指定来源,它可以是整个集合或一个集合的子集。一些典型的例子是“从书中选择名称”、“从书中选择名称、isbn”等。可以在FROM
子句中使用“AS”作为别名,这是一个可选的关键字。您也可以选择没有别名的别名,例如,“从 book 中选择 b.name 作为 b”,“从 book b 中选择 b.name”。一旦使用了别名,则所有投影/引用列都应该通过别名引用来指定它,以避免不明确的引用。因此,示例“从书 b 中选择姓名”是不正确的。相反,应该是“从图书 b 中选择 b.name”
如果您不想在FROM
子句中指定集合的名称,您可以使用一个名为ROOT
的特殊标识符来引用集合,例如,“从根 b 中选择 b.name”
WHERE 子句
使用这个子句,可以在源上指定过滤标准,该标准将根据来自源的 JSON 文档进行评估。它必须被评估为 true,才能成为结果集的一部分。通常,索引层使用它来捕获匹配的结果集,以获得最佳性能。例如,“从 ISBN='XXX-XX-XXX-XXX-X '的图书中选择名称”,使用别名“从 ISBN = ' XXX-XX-XXX-X '的图书中选择 b.name”
选择子句
这是一个强制子句,定义了从源中筛选出的 JSON 值的投影,例如,“从图书中选择 isbn”、“从图书 b 中选择 b.isbn”,或者您可以选择嵌套值:“从图书 b 中选择 b.chapter.title”。您还可以将投影自定义为“从图书 b 中选择{"BookIdentifier" : b.isbn},”或者,对于多个值,“从图书 b 中选择{"BookIdentifier" : b.isbn," BookTitle" : b.Title}。”
ORDER BY 子句
这是一个可选子句,在您想要对结果进行排序时使用。您可以指定 ASC/DESC 关键字,默认情况下使用 ASC(升序)。例如,“从图书 b 中选择 b.isbn,b . Title order by b . Title”或“从图书 b 中选择 b.isbn,b . Title order by b . Title ASC”将得到相同的结果,而“从图书 b 中选择 b.isbn,b . Title order by b . Title desc”将按降序对结果进行排序。
查询示例
让我们考虑一个例子来详细理解前面的内容。假设我们有一个图书库存,并希望将图书信息存储在 Cosmos DB–document DB 中。
示例记录可能如下所示:
{
"id": "test",
"isbn": "0312577XXX",
"title": "Cosmos DB",
"price": "200.22",
"author": "David C",
"chapters": {
"chapterno": "1",
"chaptertitle": "Overview",
"tags": [
"CosmosDB",
"Azure Cosmos DB",
"DocumentDB"
]
}
}
使用id
获取文档的查询如下:
SELECT * FROM ROOT c where c.id="test"
答案会是
[
{
"id": "test",
"isbn": "0312577XXX",
"title": "Cosmos DB",
"price": "200.22",
"author": "David C",
"chapters": {
"chapterno": "1",
"chaptertitle": "Overview",
"tags": [
"CosmosDB",
"Azure Cosmos DB",
"DocumentDB"
]
},
"_rid": "aXQ1ANuRMAABAAAAAAAAAA==",
"_self": "dbs/aXQ1AA==/colls/aXQ1ANuRMAA=/docs/aXQ1ANuRMAABAAAAAAAAAA==/",
"_etag": "\"0100191a-0000-0000-0000-5a7d3fbf0000\"",
"_attachments": "attachments/",
"_ts": 1518157759
}
]
蒙戈布蜜蜂
Azure Cosmos DB 通过协议支持来支持 MongoDB,这简化了从 MongoDB 到 Azure Cosmos DB 的迁移,因为不需要代码更改迁移。让我们看一下我们已经考虑过的演示 DocumentDB 的例子。
让我们打开 MongoDB shell,连接 Azure Cosmos DB。执行以下命令:
mongo <instancename>.documents.azure.com:10255/<databasename> -u <instancename> -p <accesskey> --ssl
use <collectionname>
请注意,use
命令的默认行为是创建一个集合(如果不存在的话),但是它最终会创建一个固定的集合。因此,建议您使用现有的集合。
以下是一个示例记录:
{
"id": "test",
"isbn": "0312577XXX",
"title": "Cosmos DB",
"price": "200.22",
"author": "David C",
"chapters": {
"chapterno": "1",
"chaptertitle": "Overview",
"tags": [
"CosmosDB",
"Azure Cosmos DB",
"DocumentDB"
]
}
}
查询如下:
db.book.find({});
回应:
{
"_id" : ObjectId("5a7d59b6d59b290864058b16"),
"id" : "test",
"isbn" : "0312577XXX",
"title" : "Cosmos DB",
"price" : "200.22",
"author" : "David C",
"chapters" : {
"chapterno" : "1",
"chaptertitle" : "Overview",
"tags" : [
"CosmosDB",
"Azure Cosmos DB",
"DocumentDB"
]
}
}
请注意_id
是系统生成的字段,不可更改,可用于记录的快速检索。
使用chapterno
获取数据。
db.book.find({"chapters":{"chapterno":"1"}})
答复如下:
{
"_id": "ObjectId(\"5a7d59b6d59b290864058b16\")",
"id": "test",
"isbn": "0312577XXX",
"title": "Cosmos DB",
"price": "200.22",
"author": "David C",
"chapters": {
"chapterno": "1",
"chaptertitle": "Overview",
"tags": [ "CosmosDB", "Azure Cosmos DB", "DocumentDB" ]
}
}
使用嵌套字段tag
获取数据。
Query: db.book.find({"chapters.tags": { $in: [ "CosmosDB" ] }},{"chapters.tags":1, "_id": 0})
答复如下:
{
"chapters": {
"tags": [ "CosmosDB", "Azure Cosmos DB", "DocumentDB" ]
}
}
使用嵌套字段tag
汇总数据。
db.book.aggregate({$project: { count: {$size:"$chapters.tags" }}})
答复如下:
{
"_t": "AggregationPipelineResponse",
"ok": 1,
"waitedMS": "NumberLong(0)",
"result": [
{
"_id": "ObjectId(\"5a7d59b6d59b290864058b16\")",
"count": 3
}
]
}
另一个查询如下:
db.book.find({},{"price":1,"_id":0}).limit(1).sort({price: -1});
答复如下:
{
"price" : "200.22"
}
图形应用编程接口
Azure Cosmos DB 的 Graph API 是基于 Apache TinkerPop 规范开发的,任何使用 Gremlin 的人都可以快速迁移到 Azure Cosmos DB,无需更改代码。对于那些不熟悉图形数据库结构的人来说,它是由节点和边组成的。节点是称为顶点的实体,边代表顶点之间的关系。两者都可以有任意数量的表示元信息的属性,称为属性图。许多社交网站使用这种类型的数据结构来定义两个实体(顶点)之间的关系。例如,如果人 A 认识人 B,其中人 A 和人 B 是顶点,关系“认识”将是边。人物 A 可以有姓名、年龄、地址作为属性,边可以有commonInterest
等属性。
Azure Cosmos DB Graph API 使用 GraphSON 格式返回结果。它是标准的 Gremlin 格式,使用 JSON 来表示顶点、边和属性。
要为 Graph API 提供 Azure Cosmos DB 帐户,请单击“创建资源”按钮➤数据库➤ Cosmos DB,然后填写表单并将 Graph 指定为 API。接下来,打开数据浏览器并单击新建图表。指定数据库 ID、图形 ID、存储容量和吞吐量,然后单击“确定”进行创建。(如果选择无限存储容量,则必须指定分区键。)现在,你必须展开数据库,通过点击数据库名称➤旁边的箭头展开图形,通过点击图形名称旁边的箭头,然后点击图形(见图 2-24 )。现在,您将获得一个成熟的用户界面来执行您的 Gremlin 查询。
图 2-24
Data Explorer view for Graph (expansion is indicated by items circled in red)
现在,让我们执行一些查询。替换执行 Gremlin 查询文本框中的g.V()
,并指定以下内容:
g.addV('John').property('id','person-a').property('name','John Shamuel')
前面将添加一个名为 John Shamuel 的人。接下来,点击执行 Gremlin 查询(见图 2-25 )。
图 2-25
Adding a vertex with some new properties
执行相同的操作,添加人员 Chris Shamuel(先生)、Laura Shamuel(夫人)和 Cartel Shamuel(桅杆。).为了搜索这些,您可以简单地包括g.V()
,这意味着“获取所有记录”,或者您可以执行g.V(<id>)
,搜索顶点的 ID,或者搜索任何属性,如g.V().has('label', 'John')
。(参见图 2-26 。)
现在,让我们添加顶点之间的边缘(约翰➤克里斯)。
g.V().has('label','John').addE('knows').property('relation','brother').to(g.V('Chris'))
这将界定从约翰到克里斯作为兄弟的优势。你也可以定义相反的反向遍历。
图 2-26
Adding edge and its result
要详细了解该查询,请参见图 2-27 。
图 2-27
Query breakdown
对整个顶点执行上述查询,并定义族。数据浏览器可以用图形可视化表示数据(见图 2-28 )。要在 Graph Visual 中查看它,请删除查询并执行查询g.V()
。
图 2-28
Data visualization in Graph Visual
现在,你可以定义所有顶点之间的边来形成一个家谱,看起来如图 2-29 。
图 2-29
Family tree visualization using Graph Visual in Data Explorer
您也可以使用 Apache TinkerPop Gremlin 控制台。从 http://tinkerpop.apache.org/
下载就行了。现在导航到apache-tinkerpop-gremlin-console-3.2.5/conf
并打开remote-secure.yaml
,然后按照清单 2-2 替换全部内容,如下所示:
hosts: [<Cosmos DB account name>.gremlin.cosmosdb.azure.com]
port: 443
username: /dbs/<database name>/colls/<collection name>
password: <access key>
connectionPool: {
enableSsl: true
}
serializer: { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0, config: { serializeResultToString: true }}
Listing 2-2Configuration for remote-secure.yaml
您必须用 Azure Cosmos DB 帐户名替换<Cosmos DB account name>
。将<databaseID>
替换为 Azure Cosmos DB 的数据库 ID,将<GraphID>
替换为 Azure Cosmos DB 的图形 ID,如图 2-30 中用红色圈出的内容。
图 2-30
Database ID and graph ID are circled in red
此外,您必须用 Azure Cosmos DB 帐户主键替换<primaryKey>
,该主键位于屏幕左侧菜单中的密钥选项下(参见图 2-31 )。
图 2-31
Primary key location circled in red
最后,保存并关闭文件,执行bin/gremlin.bat
或bin/gremlin.sh
,然后执行以下命令(输出见图 2-32 ):
图 2-32
Gremlin console connected to Azure Cosmos DB–GraphDB API account
:remote connect tinkerpop.server conf/remote-secure.yaml
在这个阶段,您已经为执行 Gremlin 查询做好了准备,您可以在这里期待相同的输出(图 2-33 )。
图 2-33
Execution of the Gremlin queries against the Azure Cosmos DB–GraphDB API account
为了给你更多的实践经验,下面是 Gremlin 控制台中的一些示例查询(如果在 Azure Cosmos DB 的数据浏览器中执行,请删除:>
):
-
使用姓名搜索特定的人。
Query: :> g.V().haslabel('name', 'Chris Shamuel')
-
遍历第一层,找出所有与该顶点相关的人。
Query: :> g.V().has('name', 'Chris Shamuel').outE('child')
-
在关系的基础上遍历多个层次。
Query: :> g.V().has('name', 'Chris Shamuel').as('x').both('husband').dedup()
卡珊德拉蜜蜂
这是 Azure Cosmos DB 中的最新介绍,它还支持使用 Cassandra wire 协议的 Cassandra API。这意味着,如果应用使用符合 CQL v4(Cassandra Query Language(CQL)第 4 版)的驱动程序,应用只需很少或不需要代码更改就可以迁移到 Azure Cosmos DB。
对于那些不熟悉 Cassandra 的人来说,这是另一种类型的 NoSQL,其目标是在没有单点故障转移的情况下使数据库高度可用。它没有主/辅助服务器角色。相反,每台服务器都是等效的,能够在运行时添加或删除节点。在写这本书的时候,这个 API 刚刚发布,还没有公开。
弹性标度
Azure Cosmos DB 是无限可扩展的,不会丢失延迟。扩展有两个变量:吞吐量和存储。Cosmos DB 可以使用这两者进行扩展,最好的一点是不需要将它们组合在一起,因此可以独立于其他参数进行扩展。
生产能力
增加计算吞吐量很容易。人们可以导航到 Azure 门户并增加请求单元(RUs ),或者使用 CLI 在不停机的情况下做到这一点。如果需要更高的计算吞吐量,用户可以在不停机的情况下扩大或缩小规模,如果需要更低的吞吐量。
以下是可用于扩展吞吐量的 Azure CLI 命令:
az cosmosdb collection update --collection-name $collectionName --name $instanceName --db-name $databaseName --resource-group $resourceGroupName --throughput $newThroughput
仓库
Azure Cosmos DB 提供了两个选项来配置集合。一个是有限的存储空间(高达 10GB)。另一个是无限存储。在无限存储的情况下,数据的分布取决于所提供的分片密钥。我将在第 3 章中详细讨论分区。
以下是可用于创建无限存储集合的 Azure CLI 命令:
az cosmosdb collection create --collection-name 'mycollection --name 'mycosmosdb' --db-name 'mydb' --resource-group 'samplerg' --throughput 11000 --partition-key-path '/pkey'
一致性
Azure Cosmos DB 提供了五个级别的一致性:强的、有界的陈旧性、会话、一致的前缀和最终的。
强烈的
这种级别的一致性保证了写操作只有在被多数复制副本仲裁持久提交后才可见。请注意,由于强一致性的性质,它比其他一致性级别需要更多的请求单元。要在门户中进行配置,请参见图 2-34 。
图 2-34
Setting strong consistency as the default consistency in Azure Portal
有限的陈旧
这是比会话、一致前缀和最终一致性更强的一致性。这种级别的一致性保证了读取可能滞后于写入,滞后的时间是项目或时间间隔的配置版本或前缀。因此,您可以用两种方式配置陈旧性:读取滞后于写入的项目版本数,或者时间间隔。
配置了有限陈旧一致性的 Azure Cosmos DB 帐户可以将任意数量的 Azure 区域与其 Azure Cosmos DB 帐户相关联。这种一致性也使用相似的 RUs 作为强一致性,它大于其他宽松的一致性级别。要在门户中进行配置,请参见图 2-35 。
图 2-35
Setting bounded staleness as the default consistency in the portal
会议
会话一致性仅限于客户端会话,最适合需要设备/用户会话的应用。它保证单调读取、写入和读取您自己的写入,并提供最大的读取吞吐量,同时提供最低延迟的写入和读取。例如,当你在社交媒体上发帖时,你使用最终一致性而不是会话一致性,你可以共享你的帖子,但在新闻订阅页面刷新后,不能保证你可以看到你的帖子,这导致你再次发帖,也许再次发帖,并引入重复的可能性。应用的开发者必须构建一个解决方案来处理这一问题,这并不容易。当你使用会话一致性时,你会立即看到你自己的帖子,开发者不需要做任何事情。Cosmos DB 会为您处理这些。要在门户中进行配置,请参见图 2-36 。
图 2-36
Setting session as the default consistency in the portal
一致前缀
这提供了组级别的一致性。让我们假设在某个时间段正在执行多个写入,然后,它不是立即复制并聚合它们,而是等待,直到有更多的写入,然后一次性聚合数据。这保证了读操作不会看到乱序的写操作。例如,一个人正在写 A、B 和 C,因此客户将得到 A;甲、乙;或者 A,B,C;等等。但绝不是 C,A;甲、丙、乙;或者 B,A;等等。
配置了一致前缀一致性的 Azure Cosmos DB 帐户可以将任意数量的 Azure 区域与其 Azure Cosmos DB 实例相关联。与更强的一致性级别相比,这会消耗更少的 ru。要在门户中进行配置,请参见图 2-37 。
图 2-37
Setting consistent prefix as the default consistency in the portal
可能的
这种最弱的一致性形式有助于实现最低的读写延迟。它确保在没有任何进一步写入的情况下,组内的副本最终会收敛。
配置了最终一致性的 Azure Cosmos DB 帐户可以将任意数量的 Azure 区域与其 Azure Cosmos DB 相关联。要在门户中进行配置,请参见图 2-38 。
图 2-38
Setting eventual prefix as the default consistency in the portal
在 MongoDB 3.4 之前,只支持强一致性和最终一致性。因此,Azure Cosmos DB 也是如此。MongoDB API 目前支持这两者。MongoDB 3.6 中现在提供了会话一致性。
表演
预定义的性能是任何 NoSQL 数据库的最高要求,Azure Cosmos DB 可以确保这一点。在 Azure Cosmos DB 中,操作延迟被认为是性能的主要因素。Azure Cosmos DB 的 SLA 保证在第 99 百分位的相同 Azure 区域中,对文档大小的 1KB 进行 10ms 读取和 15ms 写入。实际上,根据我的经验,在第 99 百分位的 Azure 区域中,1KB 大小的文档不会超过 2-5 毫秒。承诺的延迟级别可以通过 Azure Monitor 指标进行验证。
有一个专门衡量延迟的指标。要访问它,导航到 Metrics(从屏幕左侧的菜单中)并单击 Latency 选项卡(参见图 2-39 )。指标中显示的数据是针对图数据库执行的查询(在前面的“Graph API”一节中有详细描述),在 SLA 和实际数据之间存在巨大的差距(尽管可能是正面的)。SLA 中的值要高得多,而实际值要低三倍。我强烈建议您亲自进行测试,并比较结果。
图 2-39
Outcome of 99th percentile latency test
如果这样做,您会注意到 P99 级别的示例,我们会收到承诺级别下的延迟。
服务水平协议
Azure Cosmos DB 是一个企业级 NoSQL 数据库。在金融支持的 SLA 中,它涵盖了我到目前为止解释的所有方面。服务水平协议分类如下。
可用性 SLA
如果没有配置地理复制,Azure Cosmos DB 提供 99.99%的可用性,如果配置了至少一个额外的 Azure 区域,则提供 99.999%。如果读取区域出现问题,将不会影响其他区域,也不会丢失任何其他区域中的可用数据。但是,如果写入区域出现问题,将有两种故障转移选项:手动故障转移和自动故障转移。在手动故障转移的情况下,数据丢失的保证是 100%,这意味着没有数据丢失。对于自动故障切换,数据丢失是有限陈旧性的上限,这意味着数据写入组并且在灾难发生时不会复制。您可以通过一个称为可用性的指标来监控可用性(参见图 2-40 )。
为了确保 Azure Cosmos DB 的每个实例的持久性,每个分区将跨至少 10-20 个容错域进行复制。我将在第 3 章中讨论如何确保应用中的影响最小或没有影响。
图 2-40
Azure Cosmos DB monitoring metrics for Availability
吞吐量 SLA
当计算单元消耗到配置的最大值时,Azure Cosmos DB 生成错误“吞吐量失败的请求”。如果在任何情况下,它在没有达到上限的情况下生成此错误,它将被视为错误率,并根据每小时间隔内发出的请求数进行计算。这种情况不发生的保证是 99.99%。要监控 Azure Portal 中的吞吐量,请导航到度量➤吞吐量选项卡,请参考图 2-41 。
我将在第 7 章中讨论规模和计算单元策略,这将有助于确保不发生此类错误,如果发生,如何避免它们。
图 2-41
Azure Cosmos DB monitoring metrics to monitor the throughput
一致性 SLA
这是 SLA 中最容易理解的一类。假设您选择了强一致性,并且收到了幻影行(未提交的行),这将违反此类别。Azure Cosmos DB 通过一致性违反率来考虑这种情况,根据一致性违反率,成功的请求不符合配置的一致性,这将除以发出的请求总数。此类案件不发生的保证率为 99.99%。要在 Azure Portal 中监控它,请导航到度量➤一致性,请参考图 2-42 。
图 2-42
Azure Cosmos DB monitoring metrics to monitor the consistency on the portal
延迟 SLA
这就是延迟如何应用于应用,使用 Azure Cosmos DB SDK 和 TCP 连接。如果 Azure Cosmos DB 不满足指定的延迟,它会认为这样的响应实例包含在“过度延迟时间”中 SLA 承诺 99.99%的额外延迟小时-免费响应。获得读取的保证是<10ms and <15ms for writes. To monitor Latency metrics on Azure Portal, navigate to Metrics ➤ Latency, refer Figure 2-43 。
我将在第 7 章讨论性能最佳实践。
图 2-43
Azure Cosmos DB monitoring metrics to monitor the latency on the portal
结论
Azure Cosmos DB 是全球分布式多模型数据库。它可以在 Azure 的任意地理区域内(独立地)弹性扩展吞吐量和存储。它还以最低的总拥有成本(TCO)通过全面的 SLA 提供吞吐量、延迟、可用性和一致性保证。我将在随后的章节中详细介绍每个功能。
三、Azure Cosmos DB 地理复制
数据库的可用性对于任何应用的体验都是至关重要的。在用户参与至关重要的情况下,数据驱动应用中数据库的可用性是最重要的,必须确保数据库的可用性和可伸缩性。数据驱动应用的例子如下:一个电子商务应用有太多易于使用和引人注目的功能,每次用户试图购买时都会关闭,因为数据库不可用;一家医院的计费解决方案,由于数据库实例不可用,导致患者排队付款;或者是一家遍布全球的运输公司试图访问该系统,但是除了在主要位置之外,由于延迟问题,该系统的性能很差。那么,如何确保数据库可用呢?您如何确保数据库总是部署在离相关应用最近的地方?您如何实现尽可能低的延迟?
在本章中,我将尝试回答与数据库可用性相关的问题。此外,我将介绍 Azure Cosmos DB 的全球分发能力,并讨论它如何帮助解决可用性挑战。
数据库可用性
为了确保数据库的可用性,我们必须确保运行数据库的实例的可用性。我们可以通过设置高可用性(HA)来实现这一点。这仅仅意味着应该有两个以上的实例运行给定的工作负载。运行同一个数据库的两个或更多实例将是一项艰巨的工作,因为所有实例都应该同步,这样,如果一个实例离线,第二个实例将启动并运行,并提供所有所需的数据。这可以通过数据复制来实现,数据复制有两种类型:主/从和主/主。在主/从数据复制的情况下,有一个主数据库实例,它可以执行读取和写入事务,第二个或后续实例将拥有与主实例相同的数据的副本,但只执行读取事务。在主/主复制中,没有主实例。所有实例都具有同等权限,可以执行读写事务。
MongoDB 复制
在 MongoDB 中,高可用性(基于主/从的架构)可以通过副本集来配置。在副本集中,主实例中的数据将被复制到辅助实例中。主实例处理所有的写入和读取事务,辅助节点处理读取事务。次节点进一步分为两种类型:数据承载节点和仲裁节点。我们来看看他们的一些底层细节。
数据承载节点
数据承载节点是那些承载数据集的节点。所有健康的数据承载节点将不断地相互发送 pings 以检查它们的健康状态。从主节点复制数据是通过复制主节点的操作日志并将其应用于次节点的数据集来进行的。如果主节点不可用,合格的辅助节点将进行选举,选择自己作为新的主节点。举行选举并被大多数次节点选举的第一个次节点将被提升为新的主节点。选举进行时,任何节点都不能接受写和读查询。可能有优先级为 0 (P-0)的节点不能成为主节点。这些节点作为冷备份或灾难恢复的辅助手段,也称为备用模式,参见图 3-1 。
图 3-1
Replication between primary and secondary nodes
选举中的投票不是提供给每个次级节点的权利。最多只有七个辅助节点可以投票。一个非投票节点将具有投票= 0 和优先级= 0 的配置,完整流程参见图 3-2 。
图 3-2
Failover and election of the primary node (Arbiter is detailed in the following “Arbiter Nodes” section.)
仲裁节点
仲裁器节点的存在是为了提供奇数个投票成员,而没有额外的复制数据集节点的开销。与承载数据的节点不同,仲裁节点不包含数据的副本;因此,它们不能成为主节点。仲裁节点只能在选举中投票和执行健康检查,参见图 3-3 。它们的配置总是投票数= 1,优先级= 0。
图 3-3
Arbiter in overall replication
让我们看看必须为复制环境修改的连接字符串(清单 3-1 )。
MongoClient.connect("mongodb://xxx:30000,abcd:30001/db_prod?replicaSet=ProdReplication", function(err, db) {
.....
.....
}
Listing 3-1Connection String for MongoDB with replicaSet and Multiple Host Names
如果您查看连接字符串,就会发现它与平常不同。我在这里指定了多个端点,展示了指定命名节点的可能性,这将有助于连接到replicaSet
中的特定节点。但是,不建议这样做,因为这会增加验证主机的开销。您应该指定replicaSet
的名称,而不是指定主机,这足以自动导航到一个健康的节点(主节点或辅助节点)。
到目前为止,MongoDB 不支持多主复制,其中每个节点都可以处理读写事务。
HA 对一个地理区域有效,但是如果该地理区域经历自然灾害或数据中心范围的停机,并且所有实例变得不可用,该怎么办?因此,考虑为其他地理区域创建复制副本实例也很重要。这称为灾难恢复(DR)。为了设置灾难恢复,MongoDB 提供了异步复制,这有助于复制数据,即使在延迟较高的情况下也是如此,但只是在最终的一致性模式下。这意味着每个实例至少需要 4 个实例(2 个用于高可用性,2 个用于灾难恢复),这些实例必须跨数据中心复制(以确保两端的高可用性)。
除了高可用性和灾难恢复之外,如果某个应用希望跨地理区域分布,并且需要对数据库进行本地访问以减少延迟并提高应用的性能,那么我们在每个区域都需要 2 个实例。如果我们必须管理一个大型数据集,我们必须将实例分割成多个子实例,称为碎片/分区(有关更多详细信息,请参见第 5 章),然后每个碎片/分区将需要单独的 HA/DR/多地理部署考虑。这需要付出巨大的努力来部署—复制数据并在每个数据中心维护其可用性。即使是最轻微的配置错误也会造成巨大的破坏。因此,为了正确地实现多个实例,您必须雇用顾问或专门的资源。这也意味着一群 DevOps 专业人员必须全天候监控所有实例,即使这样,如果出现问题,也没有基于 SLA(服务级别协议)的承诺。
到目前为止,我已经解释了如何在 MongoDB 中执行复制,这是非常麻烦的,并且需要额外的工作来部署/管理。但不用担心,Azure Cosmos DB 是救星。它自动维护单个区域中的所有副本,并在配置后维护多个其他区域。
Azure Cosmos 数据库复制
Azure Cosmos DB 通过 SLA 提供现成的 HA、DR 和地理复制。它还涵盖了可用性、吞吐量、一致性和延迟。每个实例都有一个预配置的 HA 结构。因此,不需要显式配置。对于灾难恢复和地理复制,用户可以通过导航到 Azure 门户➤ Azure Cosmos DB 帐户➤全局复制数据来添加读写区域,然后单击地图上突出显示的圆圈+图标,然后单击保存(参见图 3-4 )。或者,您可以点按区域列表正下方的“添加区域”按钮。您可以选择任意数量的地区(最多不超过 Azure 的地区可用性限制)。
图 3-4
Configuring geo-distribution via the Azure portal
Azure Cosmos DB 支持多个地理复制的主节点,这将使应用全球分布。全球分布有助于设计低延迟访问的应用,因为它允许在更靠近应用的地方处理写和读请求。它还提高了应用的整体用户体验。地理分布前后的延迟影响示例见图 3-5 和 3-6 。
图 3-6
Optimal latency scenario after configuring global distribution
图 3-5
Latency before global distribution
添加具有多个区域的 Azure Cosmos DB 帐户的另一种方法是使用 Azure 的命令行界面(CLI)。(参见清单 3-2 )
az group create --name mytestresourcegroup --location southindia
az cosmosdb create --name mycosmosdbacct --resource-group mytestresourcegroup --default-consistency-level Session --enable-automatic-failover true --kind MongoDB --locations "South India"=0 "Central India"=1 "West India"=2 --tags kiki
Listing 3-2Configuring Multiple Regions While Creating an Azure Cosmos DB Instance Using a CLI
除了故障转移优先级之外,我们还必须提供一个位置列表。优先级在序列中应该是唯一的,如前面的参考列表 3-3 所示。优先级 0 < =表示写区域,优先级> 0 表示读区域(不像在 MongoDB 中,优先级-0 意味着实例永远不会成为主实例)。
az cosmosdb failover-priority-change --failover-policies "South India"=1 "Central India"=0 "West India"=2 --name mycosmosdbacct --resource-group mytestresourcegroup
Listing 3-3CLI Command to Change the Failover Sequence in Azure Cosmos DB
请注意,在前面的命令中,我们还将写入区域从"South India"
更改为"Central India"
。图 3-7 说明了这种变化。
图 3-7
Updated map, reflecting the change
自动转换地理 API
在 MongoDB 中,建议您提供一个replicaSet
引用,而不是指定主机,以便 MongoDB 可以隐式地管理故障转移。同样适用于 Azure Cosmos DB。编程时完全没有必要指定主机。相反,您可以简单地复制并粘贴门户中可用的连接字符串,它确实有一个引用replicaSet
。要获取连接字符串,请导航到 Azure Cosmos DB 帐户➤连接字符串,并复制主要或次要连接字符串(请参见图 3-8 ,参考清单 3-4 获取连接字符串)。
图 3-8
Gettingthe connection string from the Azure portal (Primary and secondary connection strings are indicated by a red line.)
mongodb://<CosmosDBAccountName>:<primary or secondary key>@<CosmosDBAccountName>.documents.azure.com:10255/?ssl=true&replicaSet=globaldb
Listing 3-4Connection String Depicting the replicaSet
在手动或自动故障转移的情况下,Azure Cosmos DB 将在后台处理,并且完全透明,不需要更改代码中的任何内容。
Azure Cosmos DB 的美妙之处在于,它的数据可以复制到几乎所有的 Azure 地区,因为它是一个 Ring 0 服务。一旦正式上市,所有 Azure 地区都可以使用 Ring 0 服务。到目前为止,Azure Cosmos DB 支持多个读写区域,以抑制应用的地理分布使用的延迟。
让我们在中创建一个普通的 Hello World 示例。NET 使用 MongoDB。要创建它,请执行以下步骤:
-
打开 Visual Studio ➤新项目➤ Visual C# ➤控制台应用,然后点击确定。
-
转到软件包管理器控制台并指定“Install-Package MongoDB”。司机,”然后回车。这将为. NET 添加必要的 MongoDB 客户端库。
-
添加一个类,将其命名为
EventModel
,并用以下代码替换EventModel
的类代码(列表 3-5 ):/// <summary> /// Model defined for Event Message generated from sensors /// </summary> public class EventModel { /// <summary> /// Default ID /// </summary> public MongoDB.Bson.BsonObjectId _id { get; set; } /// <summary> /// Site information /// </summary> public int SiteId { get; set; } /// <summary> /// Device information installed for a site /// </summary> public int DeviceId { get; set; } /// <summary> /// Sensor information installed in Device /// </summary> public int SensorId { get; set; } /// <summary> /// Temperature Reading /// </summary> public decimal Temperature { get; set; } /// <summary> /// Overall Health of the Device /// </summary> public string TestStatus { get; set; } /// <summary> /// Message TimeStamp /// </summary> public DateTime TimeStamp { get; set; } } Listing 3-5Code for EventModel Class
-
打开
Program.cs
并添加以下 using:using MongoDB.Driver; using System.Configuration;
-
现在用下面的代码替换函数 static main(清单 3-6 ):
static void Main(string[] args) { //ConnectionString, name of database & collection to connect //All those values will be acquired from App.config's setting section string connectionString = ConfigurationManager.AppSettings["ConnectionString"]; string databaseName = ConfigurationManager.AppSettings["DatabaseName"]; string collectionName = ConfigurationManager.AppSettings["CollectionName"]; //Mongo client object MongoClient client = new MongoClient(connectionString); //Switch to specific database IMongoDatabase database = client.GetDatabase(databaseName); //While selecting the collection, we can specify the read preference MongoCollectionSettings collSettings = new MongoCollectionSettings() { ReadPreference = new ReadPreference(ReadPreferenceMode.Secondary) }; //Adding a record into primary instances var messageId = new MongoDB.Bson.BsonObjectId(new MongoDB.Bson.ObjectId()); var deviceId = new Random(1).Next(); IMongoCollection<EventModel> productCollection = database.GetCollection<EventModel>(collectionName, collSettings); productCollection.InsertOne(new EventModel { _id = messageId, SiteId = 1, DeviceId = deviceId, SensorId = 1, Temperature = 20.05M, TestStatus = "Dormant", TimeStamp = DateTime.Now }); EventModel result = null; //Loop through till the record gets replicated to secondary instance while (result == null) { //Reading the newly inserted record from secondary instance result = productCollection.Find<EventModel>(x => x.DeviceId == deviceId).FirstOrDefault<EventModel>(); } Console.WriteLine("Message Time:" + result.TimeStamp.ToString("dd/mm/yyyy hh:mm:ss")); Console.Read(); } Listing 3-6Code to Specify the Nearest Region to Connect With
-
打开
App.config
并在</startup>
下,添加以下代码(列表 3-7 ):<appSettings> <add key="ConnectionString" Value="<Replace with ConnectionString"/> <add key="DatabaseName" value="<Replcace with name of Database>"/> <add key="CollectionName" value ="<Replace with name of collection>"/> </appSettings> Listing 3-7Configuration Changes in App.config
一旦运行代码,它将在一个区域插入记录,并从另一个区域获取记录,然后给你最后插入记录的时间戳,参见图 3-9 。您几乎不会看到一个NULL
引用或循环结构被调用超过一次,这将证明全局分布中的最低延迟点。
图 3-9
Output of the MongoDB application connecting Azure Cosmos DB reference or looping construct being called more than once, which will prove the point of lowest latency in global distribution.
一致性和全球分布
一致性是 Azure Cosmos DB 最关键的因素之一,全球发行也不例外。写入区域只有在能够写入足够的仲裁时才会确认写入,这有助于 Azure Cosmos DB 在出现故障时减少数据丢失。将为每个分区复制数据,并在粒度级别实现复制保证,参见图 3-10 。
图 3-10
Replication of data at partition level
Azure Cosmos DB 对一致性级别的尊重被指定为默认一致性或通过代码,同时连接到 Cosmos DB。针对每个一致性级别,Azure Cosmos DB 的行为如下:
- 强一致性:一旦写入区域能够写入所有区域,它就会确认这一点。就消耗的 ru 数量而言,这是成本最高的操作之一。在这种情况下,同步复制会增加整体延迟。
- 有限陈旧性:如果您需要地理复制的高度一致性,这是首选。与强一致性写入相比,它的成本影响更低。它将异步复制数据,滞后时间相当于指定的时间间隔。在自动故障转移的情况下,数据丢失的保证是指这个间隔。
- 会话一致性:在这种情况下,一致性的范围仅限于用户的会话,复制将异步执行。
- 一致前缀:这是另一种形式的最终一致性,除了它在复制期间保持写入顺序。
- 最终:这种形式的一致性总是最快和最便宜的,因为成本更低,延迟也更低。
结论
在这一章中,我已经讨论了 Azure Cosmos DB 中地理复制的各个方面,并触及了特定于 Azure Cosmos DB 的各个方面。正如我们在众多例子中看到的,Azure Cosmos DB–MongoDB API 没有引入新的术语或语法,这减少了从 MongoDB 迁移的学习曲线。在随后的章节中,我将介绍索引、大小调整、分区和其他可能涉及本章的关键场景。
四、索引
索引是任何数据库固有的一部分,MongoDB 也是如此。索引数据是必要的,有助于减少查找值时的扫描开销,这有一句谚语,“大海捞针”在这一章中,我将讨论索引是如何工作的,索引策略,定制的可能性,以及索引优化。
MongoDB 中的索引
在 MongoDB 中,用户必须定义哪个路径要被索引,以及如何被索引。这个决定定义了查询的性能。然而,索引有自己的开销。这会创建一个单独的并行树结构,在创建/更新/删除文档时,它会消耗 RAM、存储和 CPU。因此,确保索引的最大使用是很重要的,但是在实践中,可能会有一些查询不使用索引。在这种情况下,MongoDB 执行集合扫描,这将导致非最佳性能。这种场景可以通过使用 MongoDB 中的explain()
方法来识别。
默认情况下,MongoDB 有一个带有唯一索引的_id
字段,用于专门标识文档。不能删除此唯一索引。MongoDB 中存在不同类型的索引,用于不同的目的,包括单字段、复合、多键、地理空间、文本和散列索引。让我们逐一探究。
单字段索引
这是最简单的索引类型,应用于具有排序顺序的一个字段。无论何时执行查询,MongoDB 都会使用这个索引,或者在某些情况下,如果指定了多个索引字段,它也可以使用交集。详见清单 4-1 。
{
"_id" : ObjectId("5ae714472a90b83cfcf650fc"),
"SiteId" : 0,
"DeviceId" : 16,
"SensorId" : 9,
"Temperature" : "20.9",
"TestStatus" : "Pass",
"TimeStamp" : {
"$date" : 1522501431032
},
"deviceidday" : "163/31/2018"
}
Listing 4-1Sample Document
现在,连接到 MongoDB shell 并创建一个键索引(清单 4-2 ),排序顺序为 1,表示升序。若要创建按降序排序的单个键索引,请使用值-1。
>db.events.createIndex({DeviceId: 1});
Output:
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
Listing 4-2Command and Output to Create a Single Key Index
Note
在这种情况下,排序顺序无关紧要,因为 MongoDB 引擎也可以执行反向查找。
使用索引进行查询
我们将使用explain()
方法提取查询计划和关于查询计划的执行统计信息(参见清单 4-3 )。
图 4-1
Output of explain()
(the usage of the index is highlighted)
>db.events.find({DeviceId:16}).explain();
Listing 4-3Command to Be Executed (explain() is added to investigate the usage of the index; refer to Figure 4-1 for output)
在explain()
方法中,在赢计划➤输入阶段➤阶段下,有以下五种可能的操作:
COLLSCAN
:表示要执行集合扫描的查询。IXSCAN
:表示索引的使用(扫描索引键)。FETCH
:表示检索单据的操作。SHARD_MERGE
:这表示来自碎片的结果的合并。- 这将从碎片中过滤出孤立的文档。
如图 4-1 所示,使用IXSCAN
的 winningPlan ➤阶段显示查询正在使用索引。
不使用索引的查询
现在,让我们考虑一个例子,其中我们选择的字段没有使用索引。详见清单 4-4 。
> db.events.find( { "SensorId": 9 }).explain();
Output:
{
"queryPlanner": {
"plannerVersion": 1,
"namespace": "db.events",
"indexFilterSet": false,
"parsedQuery": {
"SensorId": {
"$eq": 9
}
},
"winningPlan": {
"stage": "COLLSCAN",
"filter": {
"SensorId": {
"$eq": 9
}
},
"direction": "forward"
},
"rejectedPlans": []
},
"serverInfo": {
"host": "xx",
"port": 27017,
"version": "3.6.4",
"gitVersion": "xx"
},
"ok": 1
}
Listing 4-4Execution of find with explain() and Its Output
现在,如果你仔细看,这一次我们已经采取了字段SensorId
,这是没有索引,winningPlan
➤阶段描述了操作COLLSCAN
。
复合索引
这些索引是由多个字段组合而成的。在我们的例子中,我们将创建一个包含字段SiteId
和DeviceId
的复合索引(参见清单 4-5 )。
> db.events.createIndex({SensorId:1, deviceidday:-1});
Output:
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4,
"ok" : 1
}
Listing 4-5Creating a Compound Index and Its Output
在前面的例子中,排序顺序非常重要。让我们考虑几个其他的例子,比如当查询指定排序顺序为db.events.find({},{sensorId:-1, deviceidday:1})
或db.events.find({},{sensorId:1, deviceidday:-1})
时。在这种情况下,索引将是有效的,但是如果您指定了db.events.find({},{sensorId:-1, deviceidday:-1})
或db.events.find({},{sensorId:1, deviceidday:1})
,索引将不会被使用,因为 MongoDB 引擎在其索引条目中不会有这样的组合。
第二个最重要的考虑因素是索引中字段的顺序,它应该尽可能接近您的用途。
多键索引
这些索引用于保存数组值的字段。MongoDB 将为字段中的每个值创建一个索引条目。它可以为标量值(数字、字符串等)构造。)或嵌套文档。如果 MongoDB 引擎发现字段中有数组或嵌套文档,它会自动创建一个多键索引。因此,语法与复合或单字段索引相同。
地理空间索引
MongoDB 能够支持 2D 地理空间数据,并有两个不同的索引:一个用于平面几何,另一个用于球面几何搜索。第一种主要用于遗留数据,这些数据存储为遗留坐标,而不是 GeoJSON。
GeoJSON 是一种用于各种地理空间数据结构的编码格式。它支持各种几何类型,例如点、线串、多边形、多点、多线串和多多边形。
让我们尝试一下(参见清单 4-6 )。
> db.geo2dcoll.createIndex( { location: "2dsphere" } )
Output:
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
Listing 4-6Creation of a 2dsphere Index
现在,让我们插入一些数据(列表 4-7 )。
> db.geo2dcoll.insertOne({location: {type: "Point", coordinates: [28.354153, 77.373746]}});
> db.geo2dcoll.insertOne({location: {type: "Point", coordinates: [28.370091, 77.315462]}});
Listing 4-7Inserting Some Coordinates in the 2dsphere Index
要找到 6.5 公里内的最近点,请参考清单 4-8 。
>db.geo2dcoll.find({ location: { $near: { $geometry: { type: "Point", coordinates: [ 28.354153, 77.373746 ] }, $maxDistance: 6500, $minDistance: 300 } } });
Output:
{ "_id" : ObjectId("5afdc37f83ae6a55a8f185ba"), "location" : { "type" : "Point", "coordinates" : [ 28.370091, 77.315462 ] } }
Listing 4-8Search for Nearest Point
现在让我们尝试使用 2D 索引。参考清单 4-9 创建索引。
> db.geo2dcoll1.createIndex( { location: "2d" } )
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
Listing 4-9Creation of a 2D Index
插入一些位置(列表 4-10 )。
> db.geo2dcoll1.insertOne({location:[28.370091, 77.315462 ]})
> db.geo2dcoll1.insertOne({location:[28.354153, 77.373746]})
Listing 4-10Inserting Some Sample Locations
要执行命令搜索距离一个点大约 6500 米的另一个位置,请遵循清单 4-11 中的代码。
> db.runCommand( { geoNear: "geo2dcoll1", near: [28.354153, 77.373746 ], $maxDistance: 6500 } )
Output:
{
"results" : [
{
"dis" : 0,
"obj" : {
"_id" : ObjectId("5afdc6ee83ae6a55a8f185bc"),
"location" : [
28.354153,
77.373746
]
}
},
{
"dis": 0.060423873593142,
"obj": {
"_id": "ObjectId(\"5afdc6d383ae6a55a8f185bb\")",
"location": [
28.370091,
77.315462
]
}
}
],
"stats" : {
"nscanned" : 31,
"objectsLoaded" : 2,
"avgDistance" : 0.030211936796571,
"maxDistance" : 0.060423873593142,
"time" : 1858
},
"ok" : 1
}
Listing 4-11Finding All Locations Within 6500 Meters
在前一种情况下,有一个最小距离要素会导致获得不必要的结果。当然,这样的结果会耗费不必要的时间和资源。
另一个主要差异是准确性。如果两点相距很远,你很容易就能看出差别。
文本索引
这是一种特殊类型的索引,有助于执行全文搜索。它支持基本的搜索功能,如词干,停用词,排名,短语搜索,关键字搜索等。这种类型的索引支持大约 21 种语言。但是,如果您希望支持同义词、小写分析器、特定于语言的规则、停止标记过滤器、HTML 剥离或更高级的评分集,请使用搜索技术,例如 ElasticSearch、Solr 等。
让我们创建一个文本索引。清单 4-12 中的代码可用于创建收集文章的文本索引。
>db.articles.createIndex({body: "text", abstract: "text"})
Output:
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
Listing 4-12Creating a Text Index for Articles Collection
既然比赛场地已经准备好了,那就来推数据吧。清单 4-13 中的代码引入了两个记录,用于收集集合中的文章。
> db.articles.insertOne({body: "Quick brown fox jumps over the little lazy dog.", abstract: "this is the abstract for testing purpose"});
> db.articles.insertOne({body: "This is quickly created text for testing", abstract: "article on my cat"});
Listing 4-13Inserting Two Records for Articles Collection
数据现在被推送,让我们搜索一下(见清单 4-14 )。
> db.articles.find({$text: {$search: "fox"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
Output:
{ "_id" : ObjectId("5afd9ad797a3819f3ba91ba2"), "body" : "Quick brown fox jumps over the little lazy dog.", "abstract" : "this is the abstract for testing purpose", "score" : 0.5714285714285714 }
Listing 4-14Searching for “fox” in the Entire Collection. (A textscore [relevance score] will be displayed in the score field output.)
散列索引
这种类型的索引用于对使用哈希函数对字段值进行哈希处理的数据库进行分片,以便在分片之间分发数据。在散列索引中要记住的最重要的事情是,它们不支持多键索引,并且应用不需要知道散列函数,因为 MongoDB 数据库引擎会自动进行必要的转换。
使用清单 4-15 中的代码创建一个散列索引。
> db.articles.createIndex( { _id: "hashed" } )
Output:
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
Listing 4-15Creating a Hashed Index
Azure Cosmos DB 中的索引
拥有如此多类型的索引,然后选择和管理它们是一件令人头疼的事情。如果我们能卸下所有的烦恼呢?有一个答案。不是别人,正是 Azure Cosmos DB。它将通过自动索引所有内容(默认情况下,对所有字段启用索引)来解决您所有的索引担忧,无论您推送什么,因此它将有助于降低每次读取的成本。它有特殊的空间数据索引、可以选择性定义的独特索引功能、数组、嵌套文档,最重要的是,它可以自动分片。(详见第 5 章。)这使得 Azure Cosmos DB 成为一个完全无模式的 NoSQL 数据库引擎。
默认情况下,它还有一个_id
字段,有一个惟一的索引,并可以选择指定其他字段为惟一的。在写这本书的时候,不支持文本index
和explain
方法。替换文本需要一个更复杂的搜索,称为 Azure Search,它很容易提供更好的搜索体验,只需最少的管理工作。
现在,让我们看看索引的魔力是如何配置的。导航到数据浏览器➤ <
图 4-2
比例&设置页面
现在,单击 Scale 旁边的向下箭头,这允许您特别关注设置(参见图 4-3 )。
图 4-3
Scale & Settings page, with the focus only on Settings
现在,让我们先来看看生存时间(TTL)索引的设置。
TTL 索引
在需要删除历史数据的情况下,需要 TTL 索引。一个常见的用例是比最新数据更重要的时间序列数据。虽然在 MongoDB 中有一个专门用来删除旧数据的计算(在 TTL 的情况下),但在 Azure Cosmos DB 中,它不会消耗丝毫 RUs。TTL 既可以应用于文档,也可以应用于集合级别,但在撰写本书时,使用 Azure Cosmos DB–MongoDB API,只能在集合级别应用 TTL。要使用此功能,您必须将indexingMode
设置为 none 以外的值。还要注意,TTL 支持更新和删除操作。
现在是时候为 Cosmos DB 使用相同的 MongoDB shell 了。打开 Shell 并执行清单 4-16 和 4-17 中的代码。
>sudo mongo <CosmosDBAccount>.documents.azure.com:10255/<dbname> -u <CosmosDBAccount> -p <primary/secondary key> --ssl --sslAllowInvalidCertificates
Listing 4-16Connecting Azure Cosmos DB Account from MongoDB Shell
globaldb:PRIMARY> db.tscollection.createIndex( { "_ts": 1 }, { expireAfterSeconds: 3600 } )
Output:
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
Listing 4-17Create TTL Index Using the Shell Command
Azure Cosmos DB 支持其他索引,这有助于处理特定的用例。我们将在本章的后续章节中更详细地探讨其中的一些。
数组索引
这些类型的索引解决了由数组值组成的字段的查询优化问题。每个数组值都被单独索引。在 MongoDB 中,不需要为每个数组编写单独的索引。如果字段由数组组成,则它包含在数组索引中。对于路径字符串,必须指定数组索引的路径,它会像数组索引一样对其进行索引。
"path" : /field/[]/?
{"field.tag" : "value"}
{"field.tag" : {$lt : 100 } }
稀疏索引
这些索引仅由包含指定字段的文档条目组成。由于 MongoDB 的每个文档可以有不同的字段,所以一些字段可能只出现在文档的一个子集内是很常见的。当字段不存在于所有文档中时,稀疏索引允许创建更小、更有效的索引。
我们来看一个例子(清单 4-18 )。
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
Listing 4-18Defining a Sparse Index
接下来,让我们探索 Azure Cosmos DB 中对唯一索引的支持。
唯一索引
这些类型的索引有助于避免字段或字段组合中的重复值。插入重复值时会产生错误。在无限制收集的情况下,在逻辑分区的范围内检查重复,并且一旦创建了唯一键索引,就不可能修改索引,除非重新创建容器。在 1 个约束中最多可以指定 16 个字段或字段路径,在每个唯一键中最多可以指定 10 个约束。在撰写本文时,不支持稀疏唯一键约束,缺失值被视为NULL
并根据unique
约束进行检查。唯一键约束的示例如下(列表 4-19 ):
globaldb:PRIMARY> db.collection.createIndex( { "Address-1": 1, "City": 1, "State": 1 }, { "unique": true } )
Listing 4-19Example of Unique Key Constraint
在下一节中,我将深入研究 Cosmos DB 的索引配置。
自定义索引
在其门户网站上,Azure Cosmos DB 以 JSON 的形式展示了索引的配置,这一点我们已经在其他设置中看到过(见图 4-2 和 4-3 )。让我们在这里详细复制 JSON(列表 4-20 )。
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*",
"indexes": [
{
"kind": "Range",
"dataType": "Number",
"precision": -1
},
{
"kind": "Range",
"dataType": "String",
"precision": -1
},
{
"kind": "Spatial",
"dataType": "Point"
},
{
"kind": "Spatial",
"dataType": "LineString"
},
{
"kind": "Spatial",
"dataType": "Polygon"
}
]
}
],
"excludedPaths": []
}
Listing 4-20Default Indexing Configuration
这里的主要负责人是indexingMode
、automatic
、includedPaths
和excludedPaths
。下一节将详细讨论每种方法。
索引模式
让我们从indexingMode
开始。此功能有多种模式可用于索引。
-
一致:这是默认的索引模式。它使得数据一被写入就被索引,并且在文档被索引后提供写入确认。在这种情况下,将遵循选定的一致性(无论是选择默认一致性还是在连接中指定)。下面是索引配置为 JSON,其中
indexingMode
=consistent
(列表 4-21 ):{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/*", "indexes": [ { "kind": "Range", "dataType": "Number", "precision": -1 }, { "kind": "Range", "dataType": "String", "precision": -1 }, { "kind": "Spatial", "dataType": "Point" }, { "kind": "Spatial", "dataType": "LineString" }, { "kind": "Spatial", "dataType": "Polygon" } ] } ], "excludedPaths": [] } Listing 4-21Configuration with indexingMode As consistent
-
Lazy:这种索引模式会延迟索引,Azure Cosmos DB 会在写入磁盘时立即确认写入。一旦 RUs 变得未被充分利用,就会进行索引。在这种情况下,预定义的一致性模式将不起作用,一致性将始终是最终的。它将在写入期间提供最低的成本,但在读取时可能会引入不一致,因为写入磁盘的数据将需要一些时间才能被完全索引。因此,在这种情况下,包括聚合的查询,例如
COUNT
,在峰值负载期间可能会产生不一致的结果。下面是索引配置为 JSON,其中indexingMode
=lazy
(列表 4-22 ):{ "indexingMode": "lazy", "automatic": true, "includedPaths": [ { "path": "/*", "indexes": [ { "kind": "Range", "dataType": "Number", "precision": -1 }, { "kind": "Range", "dataType": "String", "precision": -1 }, { "kind": "Spatial", "dataType": "Point" }, { "kind": "Spatial", "dataType": "LineString" }, { "kind": "Spatial", "dataType": "Polygon" } ] } ], "excludedPaths": [] } Listing 4-22Configuration with indexingMode As lazy
-
None:这种索引模式根本没有与数据相关联的索引,这意味着没有索引开销,从而在写入期间为您提供最大的产出。如果您使用 Azure Cosmos DB 作为键值对数据库,并且只能通过 ID 字段或自链接进行访问,那么通常会使用这种方法。在这种情况下,必须指定
EnableScanInQuery
选项(x-ms-documentdb-enable-scan for REST API
)。它将通过门户或代码遵守指定的一致性。请注意,在撰写本文时,这种模式只能通过 Azure Cosmos DB–SQL API 使用。下面是索引配置为 JSON,其中indexingMode
=none
(列表 4-23 ):{ "indexingMode": "none", "automatic": false, "includedPaths": [], "excludedPaths": [] } Listing 4-23Configuration with indexingMode as none
默认情况下,indexingMode
被设置为consistent
,但是如果您有一个非常繁重的写需求用例,在记录检索中有延迟是可以的,那么您可以使用lazy
。如果您不必使用查询获取数据,请使用none
。由于 Azure Cosmos DB 是高性能的,我建议您在更改 i ndexingMode
之前进行一次负载测试。如有必要,对其进行更改和验证。现在,让我们看看索引路径。
索引路径
索引路径决定了您想要索引的路径,我建议主要用于查询。有两个配置选项:第一个是includedPaths
,第二个是excludedPaths
。顾名思义,“包含”意味着它符合数据索引,“排除”意味着它不在索引范围内。下面是几个例子,将有助于定义这一点。
首先是应用于文档树的默认路径(递归)。
{ "path" : "/" }
接下来是为具有Hash
或Range
类型的查询提供服务所需的索引路径/子路径。
{"path" : "/field/?" }
这些查询的一些示例包括:
{"field" : "value"}
{"field" : {$lt : 100}}
db.book.find()._addSpecial( "$orderby", { "field" : -1 } )
db.book.find({$query:{}, $orderby : { "field" : -1}})
db.book.find().sort({"field" : -1})
最后,这里是指定标签下所有路径的索引路径:
/field/*
这适用于以下查询:
{"field" : "value"}
{"field.subfield" : "value"}
{"field.subfield.subsubfield" : {$lt : 30 }}
索引种类
与 MongoDB 一样,Azure Cosmos DB 包括以下索引:
哈希索引
Azure Cosmos DB 执行基于哈希的索引,高效地支持等式查询和连接查询。内置的Hash
函数使用索引键执行哈希值的映射。默认情况下,对于所有字符串数据类型,都使用哈希索引。
下面是一个例子(列表 4-24 ):
globaldb:PRIMARY> db.book.find({TestStatus : "Pass"});
Listing 4-24Sample Query That Uses a Hash Index
范围索引
对于具有操作(例如,\(lt,\)gt,$ lte,\(gte,\)ne)或排序顺序或等式的范围查询,将使用范围索引。它是所有非字符串和空间数据类型的默认索引类型。
以下是使用范围键索引的示例查询(清单 4-25 ):
globaldb:PRIMARY> db.book.find({DeviceId : {"$gt":1}});
globaldb:PRIMARY> db.book.find({DeviceId : {"$gt":1}},{},{_SiteId:-1});
Listing 4-25Sample Queries Using a Range Index
地理空间索引
这些类型的索引有助于优化与二维空间内的位置相关的查询,例如地球投影系统。这种查询的例子是那些包含最接近给定点或线的多边形或点的查询;圆形、矩形或多边形内的那些;或者与圆形、矩形或多边形相交的那些。Azure Cosmos DB 支持 GeoJSON,并使用坐标参考系统(CRS)世界大地测量系统(WGS-84),这是使用最广泛的坐标。以下是索引规范:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*",
"indexes":[
{
"kind":"Range",
"dataType":"String",
"precision":-1
},
{
"kind":"Range",
"dataType":"Number",
"precision":-1
},
{
"kind":"Spatial",
"dataType":"Point"
},
{
"kind": "Spatial",
"dataType": "LineString"
},
{
"kind":"Spatial",
"dataType":"Polygon"
}
]
}
],
"excludedPaths":[
]
}
目前,可用于空间索引的数据类型支持包括点、面或线串。以下是一些你可以使用的例子(列表 4-26 ):
> db.geo2dcoll.insertOne({location: {type: "Point", coordinates: [28.354153, 77.373746]}});
> db.geo2dcoll.insertOne({location: {type: "Point", coordinates: [28.370091, 77.315462]}});
Listing 4-26Inserting Some Coordinates
现在,使用清单 4-27 中的代码,在 6.5 公里内找到最近的点。
>db.geo2dcoll.find({ location: { $near: { $geometry: { type: "Point" , coordinates: [ 28.354153, 77.373746 ] }, $maxDistance: 6500, $minDistance: 300 } } });
Output:
{ "_id" : ObjectId("5afdc37f83ae6a55a8f185ba"), "location" : { "type" : "Point", "coordinates" : [ 28.370091, 77.315462 ] } }
Listing 4-27Searching for the Nearest Point
索引精度
这对于平衡索引存储和查询性能非常重要。更高的精度意味着更高的存储量。但是,这仅适用于字符串数据类型。至于数字,JSON 最少由 8 个字节组成,使用 1–7 会降低精度并抑制存储开销的减少。不允许空间变化精度。在 string 中,这很有用,因为字符串的长度可以是任意的,并且默认情况下,范围是满的。但是,如果有任何用例不需要索引整个文本,您可以将其配置为 1–100 和-1(最大值)。在 MongoDB 中没有对等的东西。
数据类型
索引路径中的数据类型支持字符串、数字、点、多边形或线串。(可以在一个路径中指定其中一个。)
结论
默认情况下,在 Azure Cosmos DB 中,所有数据都将使用最佳配置自动索引,这提供了进行图表更改的灵活性,并有助于实现最高性能。此外,它不限制用户只能自动索引。相反,它提供了更大的定制灵活性。
五、分区
规模、规模、规模……在数据库架构师的大部分职业生涯中,每当开发新的应用时,这些词语都会回响。最困难的挑战是以弹性模式设计数据库。在关系数据库管理系统(RDBMSs)的世界中,这有时会是一场噩梦,在 NoSQL 的领域中也是一项艰巨的任务。在这一章,你将学习如何使用分区,Azure Cosmos DB 扩展数据库。
碎片
在 MongoDB 中,缩放是通过一个称为分片的过程来处理的。这是一个手动配置过程,通过添加更多的计算和存储来帮助扩展 MongoDB 实例。MongoDB 在集合级别执行数据分片;因此,每个集合被分散到多个碎片,参见图 5-1 。
图 5-1
Sharding in MongoDB
存在三类组件来构成分片集群。
- Mongos:它们的行为类似于查询路由(用于读取和写入),帮助将查询路由到 MongoDB Shard 实例。Mongos 将试图通过保存碎片的元数据来抽象碎片,因为它们知道所需数据的位置。
- 配置服务器:这些服务器存储每个分片的配置设置和元数据。它们必须部署为
ReplicaSet
。 - 碎片:这些是保存数据子集的实际数据节点。
MongoDB 将数据分片,然后使用分片集群负载平衡器在物理分片中进行负载平衡。
使用shardKey
选项进行数据分割,选择shardKey
对于在运行时提供最佳查询性能非常重要。shardKey
有三种类型:
-
Range key: A range-based shard key is the default sharding methodology if neither zones nor hashing is specified. In such a case, data will be divided into sets of key ranges. This works best when there is large cardinality, low frequency, and changes occur non-monotonically. Let us consider an example in which we have a field named age, have 10, 35, and 60 as values, and are using a range key methodology. A value will be stored in the shard having that range (see Figure 5-2).
图 5-2
Sharding based on a range key methodology
-
Hashed key: According to this method, the shard key is hashed using a
hash
function and distributed to a data node in the form of ranges. This type of distribution is even and best suited for changing keys monotonically. Make sure to use a field that has a maximum number of unique values. This will result in better distribution. Also, do not use a floating-point data type for hashing. This will create issues with decimal points. E.g., in terms of the hashing function, 5.1 and 5.6 are the same; therefore, it won’t be possible to distinguish them uniquely. Let’s consider an example in which age is the key field, with the values 32, 35, and 34. These values will be hashed and stored in the chunk according to the hashed value (see Figure 5-3).图 5-3
Sharding based on hashed key methodology
-
Zones: This is a subgrouping of shards that can be based on geography, specific hardware cluster, or data isolation, owing to data residency issues. Let’s say we have Shard-1, Shard-2, and Shard-3. We can store Shard-1 and Shard-2 in Zone-A, whose physical location is in Germany, whereas Shard-3 can be stored in France, for issues related to data residency. This could be due to variations in the hardware cluster, for which you would like better hardware for premium customers, etc. Please note that the chunks will be load-balanced within their zone, and shards can be overlapped in multiple zones.
图 5-4
Sharding based on zone
选择一个最佳的分片密钥非常重要,它将是整个集群实现的读写性能的基础。一旦选择了一个键,就不可能修改它,除非重新创建集合。请注意:为了从分片环境中获得最佳性能,请在查询的过滤标准中使用 shard 键,这将有助于到达特定的分区。否则,它将被迫执行成本高昂且会导致大量延迟的广播操作。
分片的优势包括扩展存储和增加计算以获得更多吞吐量的可能性。为了实现高可用性(HA),您必须为整个分片集群创建一个ReplicaSet
。
Azure Cosmos DB 中的分区
首先,注意术语的变化。分片在这里被称为分区。(在用户界面[UI]中,术语被改为 shard,专门用于 Mongo API)。在 Azure Cosmos DB 中,分区要简单得多。所有的分区管理都是由 Azure Cosmos DB 引擎处理的,像 MongoDB 一样,人们只需要关心分区键。错误的分区键会增加成本并降低性能,这可能会导致糟糕的整体用户体验。
像 MongoDB 一样,分区是可选的,如果创建了固定集合,它不需要分区键,参见图 5-5 。它不会跨越一个物理分区,因为它不会将数据分散到多个单元中。它提供有限的吞吐量(10k RU)和存储(10GB),不会超过指定的限制。你可能想知道 RU 是什么?计算、内存和每秒输入/输出操作(IOPS)的结合有助于为最终用户创造可预测的性能体验。与此相关的更多细节在第 7 章提供。
图 5-5
Provisioning of a fixed collection
对于无限制的集合,分区实例的数量没有硬性限制。但是,一个分区的限制(10k RUs(请求单元)和 10GB 存储)也适用于此,参见图 5-6 。因此,请确保尝试在分区键范围内分配负载,并避免热路径。一旦你创建了一个无限的集合,默认情况下,Azure Cosmos DB 将创建物理分区,并根据指定的 ru 将 ru 平均分配给每个分区。例如,如果为无限集合指定 50k RUs,则将创建五个分区,并且每个分区将具有 10k RUs。如果物理分区发生变化,Azure Cosmos DB 将保持逻辑分区到物理分区的平衡以及 ru 的分布。
Note
在 Azure 中,默认情况下,通过软限制(配额)来保护支出,这可以通过提高 Azure 支持票来撤销。
图 5-6
Screenshot of the provisioning of an unlimited collection
一旦选择了无限存储容量选项,表单将自动要求您输入一个分片密钥,该密钥可以是主文档字段或子文档字段中的任何密钥,参见图 5-7 。除了_id
字段之外,每个文档中必须有指定的键。像 chunks 一样,Azure Cosmos DB 也将有基于指定分区键的逻辑分区,并且它将基于它们对物理分区的适用性来平衡这些分区。物理分区的存储大小限制为 10GB,计算容量限制为 10k RUs,因此请确保您的任何分区键都不会预期超过 10GB 的数据或超过 10k RUs 的处理要求。如果他们这样做了,你的请求将相应地受到限制。为了详细理解这一点,让我们以清单 5-1 中的以下数据为例:
{
"_id" : ObjectId("5aae21802a90b85160a6c1f1"),
"SiteId" : 0,
"DeviceId" : 0,
"SensorId" : 0,
"Temperature" : "20.9",
"TestStatus" : "Pass",
"TimeStamp" : {
"$date" : 1520929246056
}
}
Listing 5-1JSON Structure of Sensor Data
假设每个设备都有传感器,传感器可以以每秒一条消息的频率发射前面结构中定义的消息,这意味着 60 秒× 60 分钟× 24 小时=每天 86,400 条消息。如果我们的消息大小为每条消息 300B,那么我们最终得到的数据大小等于每个传感器/天 24.72MB。一个拥有 10 个传感器的设备将拥有高达 247 MB/天的容量。因此,一个物理分区可以存储 41 个设备生成的消息(< 10GB),一旦第 42 个设备开始生成消息,并试图获取大于 10GB 的额外空间,Azure Cosmos DB 分区引擎将被触发,以将该逻辑分区移动到另一个物理分区。现在,添加另一个分区将触发重新平衡 RUs 的尝试。
图 5-7
DeviceID
is defined as a partition key
你认为这是一个正确的策略吗?如果答案是否定的,我们同意,可以随意跳过下面几行。如果你仍然想知道为什么答案是否定的,让我们仔细看看。我们讨论的是传感器生成的数据,这些数据是使用设备分发的,这意味着如果我们必须将每个设备的数据(给定前面的场景)存储 42 天以上(累积超过 10GB),那么我们就遇到了瓶颈,因为一个设备 42 天的数据将累积到 10GB,这是物理分区限制,并且数据库引擎无法进一步分割数据,请参见图 5-8 。
图 5-8
DeviceID
is a bad partition key choice
那么,哪个是正确的分区键呢?让我们再试一次。D eviceID
和Day
作为分区键怎么样(见图 5-9 )。在这种情况下,数据将有更多的逻辑变化,Azure Cosmos DB 将能够将它们分布到多个物理分区。
图 5-9
DeviceID
and Day
as partition key
在这种情况下,如果通过同时应用DeviceID
和Day
作为标准来执行查询,性能将是最佳的;否则,它将扇出到所有分区(MongoDB 中的Broadcast
)。然而,在写这本书的时候,Azure Cosmos DB 不支持复合分区键。因此,必须创建一个新字段并将两个必填字段的数据合并,以便将两个字段用作一个分区键。相关代码包含在清单 5-2 中。
{
"_id" : ObjectId("5ab14e342a90b844e07fc060"),
"SiteId" : 0,
"DeviceId" : 998,
"SensorId" : 0,
"Temperature" : "20.9",
"TestStatus" : "Pass",
"TimeStamp" : 1518977329628
}
Listing 5-2Document with New Field DeviceID and Day
让我们在 MongoDB shell 中执行一个简单的find
语句(见图 5-10 )。
图 5-10
Query using clubbed field as a partition key
如果数据不需要存储超过 30 天,超过 30 天后就会过期,使用 TTL(生存时间)限制,DeviceID
最适合作为分区,那些对之前的查询回答“是”的读者现在有了正确的答案,请参考图 5-11 。
图 5-11
Setting up TTL
如果指定了分区的地理复制(见图 5-12 ),物理分区将被并行复制,并且在分区之间是独立的。
图 5-12
Geo-replication of partitions
适用于 MongoDB 分片的大多数限制也适用于 Azure Cosmos DB。例如,一旦指定了分区键,就不可能更改它。为此,您必须重新创建集合。分区发生在集合级,文档需要有一个分区键。
最佳化
以下是一些简单且易于采用的优化技巧。
-
Strictly use partition keys in query criteria: The compute cost is also a major factor in selecting a partition key. If you specify a partition key that is rarely used in query criteria, the query will fan out across partitions to serve the result. Therefore, the cost of the query will become higher and cause a great amount of latency as well. Assuming
deviceidday
as the partition key, refer to Figure 5-13 to compare the costs associated with a query, with and without the use of a partition key.图 5-13
Query cost: on the left is the query without a partition key (RU consumed = 18.43), and on the right is the query with a partition key (RU consumed = 7.66). The partition key used is
deviceidday
. -
Variable number of documents across partition key: Spread of a partition key should not be variable to the extent that the metrics of a partition graph indicate storage of logical partition with too much zigzag (see Figure 5-14). The line-of-distribution graph should be as close to straight as possible. Eventually, storage will be load-balanced upon physical partition, which achieves the ripple effect of un-optimized consumption of RUs. In such cases, RUs allocated to other partitions will be wasted.
图 5-14
Zigzag pattern in storage of logical partition (un-optimized)
-
避免分区键中的唯一值:例如,如果我们假设一个唯一的分区键值等于 U,并且记录的数量是 N,那么在基于非键值对的结构中,我们不应该有 U = N。在基于键值对的数据结构中,这是存储数据的最佳方式。
-
Keep tabs on storage per partition: Under its Metrics blade (see Figure 5-15), Azure Cosmos DB has an option to monitor storage as a separate tab, and alerts can be set up at the highest possible threshold so that preventive action can be taken before insufficient storage is generated.
图 5-15
Storage metric
-
存储相关时间段的文档:如果某个文档在某个时间间隔后不需要被查询,最好通过指定 TTL 限制来使其过期。这可以在集合级别指定,并且在文档过期时不会消耗 RUs。文档的计量表到期时间戳将被硬删除,且无法回滚。因此,如果需要时间戳来归档数据,请将其存储在更便宜的持久存储中,如 Azure Blob 存储。以下代码在文档的集合级别指定 TTL,请参考清单 5-3a 和 5-3b 。
globaldb:PRIMARY> db.sensor.createIndex( { "_ts": 1 }, { expireAfterSeconds: 3600 } ) Listing 5-3aSpecifying TTL at collection level
下面是前面代码的输出(清单 5-3 ):
{
"_t" : "CreateIndexesResponse",
"ok" : 1,
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4
}
Listing 5-3bSpecifying TTL at the Collection Level via Code
选择分区键
到目前为止,我们已经讨论了 Azure Cosmos DB 对分区键的基本原理和处理。现在,让我们看一个例子。
用例
一家消防安全公司希望从其尖端设备中分析实时数据。每个设备都像一个集线器,接收来自多个传感器的信息,这些信息将发送传感器的状态、温度等信息。该解决方案主要适用于高层公寓建筑,因为消防安全设备至关重要。将会有一个称为站点的字段来表示塔的编号。每个站点将有设备安装在塔内的公寓上,每个设备将有传感器安装在公寓的每个房间里。
现在,客户的要求是将消息推送到云,以便进行分析和实时处理。大多数时候,客户对在设备级别执行分析感兴趣。参见清单 5-4 获取消息的示例结构。
{
"_id" : ObjectId("5ab14e342a90b844e07fc060"),
"SiteId" : 0,
"DeviceId" : 0,
"SensorId" : 0,
"Temperature" : "20.9",
"TestStatus" : "Pass",
"TimeStamp" : 1518977329628
}
Listing 5-4Sample Message Structure
将每个字段评估为潜在的分区键
假设消息大小为 1KB,每个传感器每秒生成数据,将有 10 个站点,每个站点有 15 个公寓,每个公寓有 4 个房间。因此,总的硬件要求如下:站点= 10(唯一键= 10),每个站点需要的设备= 15(唯一键= 15 × 10 = 150),每个设备需要的传感器= 4(唯一键= 150 × 4 = 600)。这意味着每秒将产生总共 600 条消息(msg ),相当于每天 600 条 msg× 60 秒×60 分钟× 24 小时× 30 天= 15.552 亿条消息。传感器级别的存储大小将为 1483/GB/MO(大约。).如前所述,物理分区大小将有 10GB 的限制;因此,至少需要 149 个物理分区,这需要至少 149 个物理分区键。因此,只有设备和传感器字段可以成为分区键。
分区键的选择
有两个重要的注意事项要记住。一个是查询模式。如果不将分区键指定为标准之一,数据库引擎将最终执行扫描,这将显著增加 RUs 的消耗。您可能还会受到分配给 Azure Cosmos DB 实例的大量 ru 的限制。在我们的示例中,分析是在设备级别执行的,因此将其视为分区键会有所帮助。
第二个要考虑的是扩展的可能性。如您所见,传感器可能有 600 个键,这意味着我们可以扩展到 600 个分区(最多),而设备也有 150 个键,这也符合我们的要求。就像前面一个一样,如果我们确定我们的需求,并且我们不期望在我们的用例中有可变性,那么 device 字段将适合成为一个分区键,它将在查询数据时有效地使用 ru,并为分区的数量提供足够的键。
让我们把手弄脏吧。参考第 3 章介绍的样本,创建一个分区键为DeviceId
的新集合(见图 5-16 )。从第 3 章中引用的示例代码中打开program.cs
文件,并更改 main 方法,以添加更多的复杂性并遵守所提到的用例(参见清单 5-5 )。
图 5-16
Creating a collection with DeviceId
as partition key
static void Main(string[] args)
{
///Get the connectionstring, name of database & collection name from App.config
string connectionString = ConfigurationManager.AppSettings["ConnectionString"];
string databaseName = ConfigurationManager.AppSettings["DatabaseName"];
string collectionName = ConfigurationManager.AppSettings["CollectionName"];
//Connect to the Azure Cosmos DB using MongoClient
MongoClient client = new MongoClient(connectionString);
IMongoDatabase database = client.GetDatabase(databaseName);
IMongoCollection<EventModel> sampleCollection
= database.GetCollection<EventModel>(collectionName);
//This will hold list of object needs to insert together
List<EventModel> objList = new List<EventModel>();
//Loop through Days, right now I am considering only 1 day but feel free to change
for (int day = 1; day >= 1; day--)
{
//loop through the hour
for (int hour = 1; hour <= 24; hour++)
{
//loop through the minute
for (int minute = 1; minute <= 60; minute++)
{
//loop through the seconds
for (int second = 1; second <= 60; second++)
{
//Loop through the sites
for (int site = 1; site <= 10; site++)
{
//Loop through the Devices
for (int device = 1; device <= 15; device++)
{
//Loop through the sensors
for (int sensor = 1; sensor <= 4; sensor++)
{
//initialize the message object
var obj = new EventModel()
{
_id = new BsonObjectId(new ObjectId()),
SiteId = site,
//It will help uniquely generating DeviceId basis the site
DeviceId = device + site * 1000,
//This will help uniquely generating SensorId basis the Device
SensorId = sensor + ((device + site * 1000) * 1000),
TimeStamp = DateTime.Now,
Temperature = 20.9M,
TestStatus = "Pass",
deviceidday = device.ToString() + DateTime.Now.ToShortDateString()
};
//add into the list
objList.Add(obj);
}
}
//indicate Site's messages are added
Console.WriteLine("site:" + site);
}
//indicate the second roll over completed
Console.WriteLine("second" + second);
//inserting the messages collected in one minute interval
sampleCollection.InsertMany(objList);
//clear the list to get ready for next minute sequence
objList.Clear();
}
//indicate the minute roll over completed
Console.WriteLine("minute" + minute);
}
//indicate the hour roll over completed
Console.WriteLine("hour" + hour);
}
//indicate the Day roll over completed
Console.WriteLine("day" + day);
}
}
Listing 5-5Replacing This Code with the program.cs Code Mentioned in the Sample in Chapter 3
图 5-17 显示每个分区键都可以处理大量数据,并在需要时给 Azure Cosmos DB 的引擎提供负载平衡的机会。
图 5-17
Storage metric depicting partition keys for the DeviceId
field
现在,让我们考虑将SensorId
字段作为分区键并进行评估,参考图 5-18 和 5-19 。
图 5-19
Storage metric depicting a greater number of keys when the SensorId
field is selected as the partition key
图 5-18
Creating a collection with the SensorId
field as partition key
您可以看到SensorId
提供了更多的键,但是每个键的数据量更少。此外,在我们的目的中,我们必须使用DeviceId
,而不是SensorId
,作为大多数查询的标准。因此,我们选择的DeviceId
对于我们的用例来说是最佳的。
结论
在本章中,我们讨论了 Azure Cosmos DB 的存储如何扩展,以及如何通过文档中的候选字段获得最佳分区。与 MongoDB 相比,通过从第一天开始的分区,在 Azure Cosmos DB 中实现规模和管理它要容易得多。在第 7 章中,我们将讨论规模调整以及分区对规模调整计算的影响。
六、一致性
一致性是数据库事务中一个非常重要的因素。它规定了数据库在读写期间的行为。在分布式数据库中,这是一个更加复杂和关键的因素。在这一章中,你将学习 Azure Cosmos DB 中可用的一致性级别。
分布式数据库中的一致性
由于数据库系统对于数据驱动的应用至关重要,因此确保可用性非常重要。因此,为了确保高可用性(HA),您将最终拥有多个数据库副本(参见图 6-1 )。
图 6-1
ReplicaSet
consists of a leader (primary) and followers (secondaries)
跨区域拷贝将确保业务连续性,以防主区域出现问题。这就是所谓的灾难恢复(DR)。还可以有更多的跨区域用例。最普遍的一种情况是拥有遍布全球的用户群,并希望在离用户更近的地方部署应用,以避免网络延迟(见图 6-2 )。
图 6-2
Database with ReplicaSet
within geographical as well as cross-geographical regions
在这种情况下,确保一致性可能相当麻烦。我们来看一个例子。
如果您执行写入请求以插入项目 A,并立即从主节点和辅助节点读取项目 A,则响应将取决于一致性级别。在跨 geo 中,可能有更多的变量,例如网络延迟、连接故障等。,这将导致进一步的问题(参见图 6-3 )。因此,CAP 定理指出,人们必须在一致性、可用性和分区容差这两个方面中选择任何一个。
图 6-3
Database with ReplicaSet
indicating a network failure
前面是一个失败的例子。跨地理区域的网络延迟的成功用例如何?程序是一样的。您必须插入数据,然后尝试跨地理区域执行读取命令(假设间隔为 80 毫秒)。这是否会返回正确的结果?另一个名为 PACELC 的定理在这里出现。它指出,如果系统在正常条件下工作,除了 CAP 之外,还必须考虑延迟与一致性(见图 6-4 )。
图 6-4
Database with ReplicaSet
having network latency across geographic locations
现在,让我们看看不同的一致性级别。
MongoDB 中的一致性
在 MongoDB 中,默认情况下,强一致性适用于本地实例,最终适用于读取副本。这种行为会受到定义事务行为的读写关注点的影响。
在 MongoDB 中,写请求可以指定写关注点,这决定了来自复制实例数量的写确认。这将确保写事务的持久性。对于读取请求,您可以定义四种类型的读取问题:本地、可用、多数和可线性化。在“本地”的情况下,不管写入问题如何,数据将从主实例可用,而不确保对其他复制副本的持久承诺。如果读取与因果一致的会话相关联,则默认为针对主节点的读取问题,默认为辅助节点。对于“可用”,行为保持与“本地”相同,只是当因果一致性会话不存在时,它默认为读取辅助节点的问题,并且当设置了因果一致性时,它不可用。在大多数节点确认写入后,“大多数”读取问题将恢复到更一致的数据。“可线性化”读取将等待,直到大多数副本确认写入,这确保了所有读取问题中最一致的读取。这只能为主要实例/主节点定义。
您可以通过在 MongoDB 中显式指定 read 关注点来执行该命令(参见清单 6-1 )。
db.collection.find().readConcern(<"majority"|"local"|"linearizable"|"available">)
Listing 6-1MongoDB’s Shell Command for Specifying Read Concern
如果您在分布式数据库环境中工作,确保您可以读取您的写入是一个挑战,因为复制您的写入需要一些时间。在实践中,建立可线性化的读取问题通常是不可能的,因为这会增加等待时间。最近,在 MongoDB 3.6 中,引入了一个客户端会话,其中读/写在用户会话的范围内是一致的,这被称为因果一致性。这将确保您不会有性能故障,并且仍然允许您能够读取您的写入。
Azure Cosmos DB 中的一致性
Azure Cosmos DB 有五种类型的一致性:强的、有界的陈旧性、会话、一致前缀和最终。为了完全理解这一点,让我们定义两组一致性行为:一致的读/写和高吞吐量。
一致的读取/写入
Azure Cosmos DB 提供了一致读/写的可能性,具有三个特征:强一致性、有限陈旧性和会话陈旧性。为了理解它们的行为,让我们考虑一下每种行为的几个例子。
清单 6-2 给出了一个样本文档的代码,我们将使用它来探索不同的一致性级别。
{ "_id" : "469", "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "deviceidday" : "03/10/2018" }
Listing 6-2Code for Sample Document
强一致性
为了实现强大的一致性,Azure Cosmos DB 确保只有在写入被主副本和大多数副本提交为持久写入或被中止后,写入才是可见的。客户端永远不会看到未提交或部分提交的写入,并保证会读取最新确认的写入(参见图 6-5 )。
图 6-5
Write acknowledgment (checkmarks indicate committed writes)
就读取操作消耗的延迟和 ru 而言,这是成本最高的延迟级别(请参见本节后面的示例代码)。要在 Azure 门户中设置强一致性,请参见图 6-6 。
图 6-6
Configuration for a strong level of consistency
Azure Cosmos DB 采用了一个"线性化检查器",它持续监控操作并直接以指标报告任何一致性违规。让我们用一个例子来深入研究一下细节。
首先,让我们推送数据并尝试获取它。
db.coll.insert({ "_id" : "469", "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "deviceidday" : "03/10/2018" });
为了更好地理解性能,运行以下命令(清单 6-3 )。插入花费了 13.9 RUs,延迟相当于 55 毫秒。
db.runCommand({getLastRequestStatistics: 1});
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 13.9,
"RequestDurationInMilliSeconds" : NumberLong(55)
}
Listing 6-3Checking Performance of Linearizability
请求费用是以 RUs 表示的成本。现在,我们来读一下(列表 6-4 )。读取请求的请求费用将为 6.98 RUs,延迟为 4ms。
db.coll.find({"_id" : "469"})
db.runCommand({getLastRequestStatistics: 1});
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 6.98,
"RequestDurationInMilliSeconds" : NumberLong(4)
}
Listing 6-4Calculating the Request Charge (in RUs)
如果你注意到了,阅读一份文件的成本是 7。
有限的陈旧
这是一个独特的概念,适用于非常高的吞吐量。在这种情况下,读操作可能会滞后于写操作一个配置的时间间隔或操作次数(参见图 6-7 )。
图 6-7
Write acknowledgment (checkmarks depict committed writes)
您可以根据需要创建尽可能多的地理复制实例,这对于强一致性来说是不可用的。这也是数据丢失保证的默认级别,以防您的主区域所在的 Azure 区域出现问题。就延迟和读取操作消耗的 ru 数量而言,成本与强一致性的成本相同。要在 Azure 门户中配置这个一致性级别,请参考图 6-8 。
图 6-8
Configuration for a bounded staleness consistency level
为有界陈旧性配置值有两个约束:
- 最大滞后(操作次数):10 到 1,000,000 适用于单个区域,100,000 到 1,000,000 适用于多个区域。
- 最大滞后时间:单个地区 5 秒到 1 天,多个地区 5 分钟到 1 天
Note
在写这本书的时候,Azure Cosmos DB–MongoDB API 不支持这个一致性级别。我把它包含在这里供您参考,因为它是一个重要的功能,在不久的将来可能会作为 API 的一部分包含进来。
会议
这种一致性的范围是局部的,在您必须读取写入内容的情况下非常有用。如果您必须在会话中执行立即读取操作,这也很重要,例如,为需要立即检索值的用户会话写入信息,或者任何设备写入需要立即与最新值聚合的数据,等等。详见图 6-9 。
图 6-9
Write acknowledgment (checkmarks depict committed writes)
在这种一致性级别下,允许任意数量的地理分布。与其他强一致性相比,它将以较低的成本提供最大的吞吐量。要在 Azure 门户中设置这个一致性级别,请参考图 6-10 。
图 6-10
Configuration of the session consistency level Note
在写这本书的时候,Azure Cosmos DB–MongoDB API 不支持这个一致性级别。我把它包含在这里供您参考,因为它是一个重要的功能,在不久的将来可能会作为 API 的一部分包含进来。
高流通量
有一些一致性旨在以最小的成本提供最佳的吞吐量。这些是一致前缀和最终前缀。
一致前缀
这种一致性是基于副本的最终收敛。它确保写入顺序保持不变。如果' 1 ',' 2 ',' 3 '是用相同的序列编写的,那么 Azure Cosmos DB 将确保检索到' 1 '或' 1 ',' 2 '或' 1 ',' 2 ',' 3 ',而不考虑区域(多/单)。(见图 6-11 。)
图 6-11
Configuration of the consistent prefix consistency level
这种一致性的性能也非常接近最佳。要在 Azure 门户中配置它,请参见图 6-12 。
图 6-12
Configuration of the consistent prefix consistency level in the Azure portal Note
在写这本书的时候,Azure Cosmos DB–MongoDB API 不支持这个一致性级别。我把它包含在这里供您参考,因为它是一个重要的功能,可能会在不久的将来包含在 API 中。
可能的
最终一致性是最弱的一致性形式,在这种情况下,客户端可能会获得过时的值(比写入时间更早的值)。它确保当没有进一步的写入时,数据最终将会收敛。因为它没有确保读取顺序、提交多数或法定人数等开销。与其他一致性级别一样,最终一致性以更低的成本在读取和写入方面表现最佳(参见图 6-13 )。
图 6-13
Configuration of eventual consistency level
要在 Azure 门户中配置最终的一致性级别,请参见图 6-14 。
图 6-14
Configuration of the eventual consistency level in the Azure portal
一旦一致性级别发生变化,则将文档推送到集合中,并使用db.runCommand()
评估结果,请参考清单 6-5 。
db.coll.insert({ "_id" : "469", "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "TimeStamp" : { "date" : 1520660314835 }, "deviceidday" : "03/10/2018" });
db.runCommand({getLastRequestStatistics: 1});
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 13.9,
"RequestDurationInMilliSeconds" : NumberLong(5)
}
Listing 6-5Insertion Took 13.9 RUs with Latency Equivalent to 5ms
让我们试着阅读文档(参见清单 6-6 )。
db.coll.find({"_id" : "469"})
db.runCommand({getLastRequestStatistics: 1});
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 3.49,
"RequestDurationInMilliSeconds" : NumberLong(4)
}
Listing 6-6Request Charge for Read Request
如果您将请求费用与强一致性费用进行比较,就会发现这要少得多。
结论
我已经讨论了各种类型的一致性,并解释了一些给出可比较的结果,一些是性能性的,一些保证一致的读取。没有支配选择一个而不是另一个的经验法则,但是建议您彻底分析用例并选择适当的一致性。
为了确保 Azure Cosmos DB 满足您选择的一致性级别,Azure Cosmos DB 将其包含在 SLA 保证中。它还有一个线性化检查器,可持续监控操作并报告任何违规情况。对于有界陈旧性,它验证在有界陈旧性配置中出现的复制界限,并报告指标中的违规,称为概率有界陈旧性指标。此外,其他一致性级别的违规将在 Azure 门户➤ Azure Cosmos DB 帐户➤度量➤一致性中可用的一致性度量中报告(见图 6-15 )。
图 6-15
Consistency metrics
七、调整大小
到目前为止,我已经从使用角度介绍了 Azure Cosmos DB 的各个方面。在这一章中,我将解释 Azure Cosmos DB 的规模方面。
与任何传统的实现不同,Azure Cosmos DB 并不要求开发者成为硬件工程师,也不要求数据库架构师无所不知。坦率地说,仅仅基于开发商/建筑师的经验是不可能获得准确的评估的。Azure Cosmos DB 完美地解决了这个问题,并提供了一种基于以下参数配置数据库的自然方式:
- 文档的大小
- 文件数量
- CRUD(创建、读取、更新和删除)操作的数量
请求单位(RUs)
Azure Cosmos DB 是为高吞吐量和预测性能而设计的,这意味着它必须预留资源。然而,它提供了动态增加和减少预留资源的灵活性。保留资源被定义为每秒的请求单位。它是处理每个 CRUD 操作所需的资源的组合,包括 CPU、内存和 IOPS(每秒输入/输出操作数)。Azure Cosmos DB 将 ru 平均分配给分区。因此,如果您在容器级别有 10k RUs,对于五个物理分区,每个分区将接收 2k RUs。
RUs 的分配
在前一节的最后一行,我使用了术语容器,您可能想知道这是什么。这是一个用来索引据库或集合的术语——无论您想在哪里分配 ru。如果您有多个集合,并且您不想为每个集合分配专用的 ru,那么在集合时分配 ru 将是正确的选择。否则,您可以在收集时分配 ru。你也可以两者兼得。一旦在数据库级分配了 RUs,在给定的数据库中提供一个集合时,您有两种选择。一种是不将 RU 分配给被供应的集合,而是从数据库中取出 RU(直到数据库 RU 的最大值)。另一种是将 ru 分配给一个集合,该集合将专用于该集合,并且属于同一数据库的任何其他集合都不能消费它们。请注意,明确分配给集合的 ru 将是您分配给数据库的 ru 之外的 ru。例如,如果我们向一个数据库分配了 50k ru,然后向其中添加了 5 个集合,那么您将被收取 50k ru 的费用,而不管您添加了多少个集合,并且任何集合都可能占用 50k ru。请注意,由于达到了各自使用的峰值,它们可能不得不争夺 RUs。如果我们添加第 6 个集合,并在同一个数据库中为该集合提供 10k ru,我们将总共被收取 50k ru+10k ru = 60k ru,并且我们新添加的集合将享受专用性能。
要在数据库级别添加 ru,通过导航到 Azure Cosmos DB Account ➤数据浏览器➤新数据库创建一个新数据库,然后勾选 Provision Throughput 并填写表单,如图 7-1 所示。图 7-2 到 7-5 ,说明了在数据库和收集级别的 ru 分配。
图 7-5
There are no scale options for collections using RUs from a database
图 7-4
Adding RUs to the database from the collection
图 7-3
Allocating RUs at the collection level
图 7-2
Option to scale will appear on the database (continuation of Figure 7-1)
图 7-1
Allocating RUs at the database level
现在,在数据库级别分配 ru 有助于在您有多个集合的情况下,通过降低成本和使它们共享相同数量的 ru。假设您有 80 个收藏,并且所有收藏都属于无限存储。开始时,您至少需要 80k RUs,但是在数据库级别进行分配时,您可以添加一个具有 50k RUs 的数据库,这样您就完成了排序。在这种情况下,最大限制将是 50k。
请注意,一旦在数据库级别选择了 RU 分配,就必须在预配集合时强制选择分区键。
计算 RUs
为了理解 RUs 的计算,让我们考虑一个例子。清单 7-1 中提供了相关 JSON 文档的代码。
{ "_id" : "469", "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "deviceidday" : "03/10/2018" }
Listing 7-1JSON Document
以下是其他统计数据:
- 一个文档的大小= 231 字节
- 文档数量= 300,000,000
- 写操作次数= 200
- 读取操作次数= 400
让我们执行一些查询来确定我们需要多少个 ru。
globaldb:PRIMARY> db.eventmsgss.find({SensorId: 1001001}).limit(1);
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 3.49,
"RequestDurationInMilliSeconds" : NumberLong(4)
}
前面的 read 查询使用 S ensorId
作为一个标准,其中我们将它视为一个分区键,并且只取 1 条记录,大小为 231 字节。
globaldb:PRIMARY> db.eventmsgss.insertOne({ "_id" : ObjectId(), "SiteId" : 1, "DeviceId" : 1001, "SensorId" : 1001999, "Temperature" : "20.9", "TestStatus" : "Pass", "TimeStamp" : ISODate("2018-05-21T16:23:32.256Z"), "deviceidday" : "15/21/2018" })
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 13.14,
"RequestDurationInMilliSeconds" : NumberLong(33)
}
前面的插入查询对于 231 字节的一次写入需要 13.14 RUs。有了这些结果,下面将是典型的结果:
- 总文档大小= 65GB(大约。)
- 保持以上大小所需的分区数量= 7 个物理分区(10GB/分区)
- 写操作所需的 ru 数= 5256 RUs
- 读取操作所需的 ru 数量= 1396 个 ru
- 操作所需的 ru 总数= 6700 RUs(最接近 100,精确值为 6652 RUs)
Azure Cosmos DB RU 的价格是每 100 RUs,也就是说价格表中提到的价格必须乘以(6700/100) ×每 100 RUs 的价格。对于前面的图,我通过假设负载将分布在所有分区上来考虑这一点。因此,在实践中,每个分区有 6700 个 ru,我们可以预期有 957 个 ru(大约。).(见图 7-6 。)
图 7-6
Equal distribution of RUs in partitions
每个地理复制区域将花费独立实例的成本,与您在 Azure Cosmos DB 中配置的实例的成本完全相等。也就是说,如果你在美国西部配置了一个 Azure Cosmos DB 实例,并创建了三个副本,就会产生 3 + 1 = 4 的费用。在 Azure Cosmos DB 被地理复制的情况下,计算必须考虑区域的数量,如下所示:
- 区域数量= 1 个写区域+ 3 个读区域,这意味着 4 个区域
- ru 总数= 6700×4 = 26800 ru
- 对于价格计算= (26,800/100) ×每 100 RU 的价格
让我们看另一个例子。
- 一个文档的大小= 4KB
- 文档数量= 1600 万
- 写操作次数= 400
- 读取操作次数= 200
平均而言,1 次读取大小最大为 4KB = 4.5 RUs 的文档(约),并写入大小为 4KB = 7 RUs 的文档。典型结果如下:
- 总文档大小= 61GB(大约。)
- 保持上述大小所需的分区数量= 7 个分区(10GB/分区)
- 写操作所需的 ru 数= 6028 RUs
- 读取操作所需的 ru 数量= 1800 RUs
- 操作所需的 ru 总数= 7828 RUs
Azure Cosmos DB RUs 的价格是每 100 RUs,也就是说价格表中提到的价格必须计算为(7900/100) ×每 100 RUs 的价格。
在 Azure Cosmos DB 被地理复制的情况下,计算还应该包括区域的数量。
- 区域数量= 1 个写区域+ 3 个读区域,这意味着 4 个区域
- ru 总数= 7900×4 = 31600 ru
- 对于价格计算= (31,600/100) ×每 100 个俄罗斯单位的价格
为了便于参考,Azure Cosmos DB 在 www.documentdb.com/capacityplanner
提供了一个容量规划器。
该计划程序要求您上传一个样本文档,并根据每种操作类型、文档数量等指定值。一旦完成,您必须点击计算按钮,这将在右侧反映计算结果(参见图 7-7 )。
图 7-7
Azure Cosmos DB capacity planner
请注意,本章中提到的计算和容量规划器中提到的计算是任何特定应用的标准。我建议您使用门户中的查询指标和监控指标。
优化 RU 消耗
RU 是这里的货币。所以优化越好,RUs 烧的越少。有一些技巧可以与其他技巧结合使用,以提高优化。
以下是影响 RUs 优化的一些因素。
文档大小和复杂性
这是计算 RU 消耗的一个关键因素。如果您有较小的文档,消耗的 ru 数量将远远少于较大的文档。更多的字段会增加索引的开销。文档的复杂性也起着重要的作用。如果您有一个包含多个嵌入文档的文档,则一次写入的成本将消耗更高的 ru。该因素会影响读取和写入过程中 ru 的消耗。让我们看一些例子。
插入以下文件(清单 7-2 ),将收取 31.32 RUs 的费用:
db.customer.insertOne( {
"CustomerKey": 1122,
"Title": "Mr.",
"FirstName": "Brian",
"LastName": "Moore",
"MaritalStatus": "Single",
"Gender": "Male",
"EmailAddress": "[email protected]",
"YearlyIncome": 100000,
"TotalChildren": 2,
"Education": "Graduate",
"NumberCarsOwned": 4,
"AddressLine1": "House no. 4455, First Floor,",
"AddressLine2": "Sector Zeta A, Delwara, US",
"Phone": "xxx-xxx-xxx",
"CustomerType": "New",
"CompanyName": "Tingo"
});
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 31.32,
"RequestDurationInMilliSeconds" : NumberLong(3018)
}
Listing 7-2Inserting a Large-Sized Document
如果我们缩小字段名(参见清单 7-3 ,RUs 将被优化为 21.71 RUs。
db.customer.insertOne( {
"ck": 1122,
"ttl": "Mr.",
"fn": "Brian",
"ln": "Moore",
"ms": "Single",
"gn": "Male",
"ea": "[email protected]",
"yi": 100000,
"tc": 2,
"edu": "Graduate",
"nco": 4,
"add1": "House no. 4455, First Floor,",
"add2": "Sector Zeta A, Delwara, US",
"ph": "xxx-xxx-xxx",
"ct": "New",
"cn": "Tingo"
});
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 21.71,
"RequestDurationInMilliSeconds" : NumberLong(31)
}
Listing 7-3Minification of Field Names
如果我们删除我们用例中可能不需要的两个属性,即yi
( YearlyIncome
)和tc
( TotalChildren
),那么被消耗的 ru 的数量将是 19.81(参见清单 7-4 )。
globaldb:PRIMARY> db.customer.insertOne( {
... "ck": 1122,
... "ttl": "Mr.",
... "fn": "Brian",
... "ln": "Moore",
... "ms": "Single",
... "gn": "Male",
... "ea": "[email protected]",
... "edu": "Graduate",
... "nco": 4,
... "add1": "House no. 4455, First Floor,",
... "add2": "Sector Zeta A, Delwara, US",
... "ph": "xxx-xxx-xxx",
... "ct": "New",
... "cn": "Tingo"
...
... });
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 19.81,
"RequestDurationInMilliSeconds" : NumberLong(24)
}
Listing 7-4RUs Consumed with Fewer Fields
数据一致性
该因素主要增加或减少读取期间的 RU 消耗。更强的一致性成本更高,更弱的一致性成本更低(参见第 6 章了解更多关于一致性的细节)。让我们看一些例子。
首先,将一致性设置为强,导航到 Azure Cosmos DB Account ➤默认一致性,并设置最终一致性(见图 7-8 )。
图 7-8
Changing the default consistency in the portal to Eventual
接下来,执行以下代码:
globaldb:PRIMARY> db.customer.insertOne( {
... "ck": 1122,
... "ttl": "Mr.",
... "fn": "Brian",
... "ln": "Moore",
... "ms": "Single",
... "gn": "Male",
... "ea": "[email protected]",
... "edu": "Graduate",
... "nco": 4,
... "add1": "House no. 4455, First Floor,",
... "add2": "Sector Zeta A, Delwara, US",
... "ph": "xxx-xxx-xxx",
... "ct": "New",
... "cn": "Tingo"
...
... });
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 19.81,
"RequestDurationInMilliSeconds" : NumberLong(24)
}
现在,让我们检索相关的记录(参见清单 7-5 )。将使用 2.35 RUs 检索记录,最终保持一致。
globaldb:PRIMARY> db.customer.find({}).limit(1);
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 2.35,
"RequestDurationInMilliSeconds" : NumberLong(4)
}
Listing 7-5Retrieving the Record
我们把一致性改成强(见图 7-9 )。
图 7-9
Changing the default consistency in the portal to strong
现在,如果我们执行相同的查询,消耗的 ru 数量将会增加。注意,对于强一致性,相同的查询将花费 4.7 RUs(参见清单 7-6 )。
globaldb:PRIMARY> db.customer.find({}).limit(1);
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 4.7,
"RequestDurationInMilliSeconds" : NumberLong(4)
}
Listing 7-6Cost of Query with Strong Consistency
索引
默认情况下,Azure Cosmos DB 支持文档的自动索引,这是为读取而优化的,但写入会更昂贵。如果您需要大量的写操作和少量的读操作,可以随意关闭索引。这将有助于减少写入期间的 RU 消耗。让我们看一些例子。
关闭自动索引并打开自定义索引。将索引的一致性更改为 lazy,并使用excludedPaths
来排除被索引的属性。(有关步进的更多信息,请参考第 4 章。)
让我们看一个样本文档。
{ "_id" : "469", "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "deviceidday" : "03/10/2018" }
在默认情况下,注意以下索引设置(列表 7-7 ):
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*",
"indexes": [
{
"kind": "Range",
"dataType": "Number",
"precision": -1
},
{
"kind": "Range",
"dataType": "String",
"precision": -1
},
{
"kind": "Spatial",
"dataType": "Point"
},
{
"kind": "Spatial",
"dataType": "LineString"
},
{
"kind": "Spatial",
"dataType": "Polygon"
}
]
}
],
"excludedPaths": []
}
Listing 7-7Default Index Settings
接下来(列表 7-8 )是用于插入的 RU 消耗,花费了 12.9 RUs,延迟相当于 6ms。
db.coll.insert({ "_id" : "469", "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "deviceidday" : "03/10/2018" });
db.runCommand({getLastRequestStatistics: 1});
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 12.9,
"RequestDurationInMilliSeconds" : NumberLong(6)
}
Listing 7-8RU Consumption for Insertion
以下(列表 7-9 )是读取文档时的 RU 消耗。读取请求的请求费用将为 3.48 RUs,延迟为 5ms。
db.coll.find({_id:ObjectId("5b0546a8512d8c81c1e6bf95")});
db.runCommand({getLastRequestStatistics: 1});
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 3.48,
"RequestDurationInMilliSeconds" : NumberLong(5)
}
Listing 7-9RU Consumption While Reading the Document
现在,让我们执行自定义索引。索引设置将类似于清单 7-10 。
{
"indexingMode": "lazy",
"automatic": true,
"includedPaths": [
{
"path": "/*",
"indexes": [
{
"kind": "Range",
"dataType": "Number",
"precision": -1
},
{
"kind": "Range",
"dataType": "String",
"precision": -1
},
{
"kind": "Spatial",
"dataType": "Point"
},
{
"kind": "Spatial",
"dataType": "LineString"
},
{
"kind": "Spatial",
"dataType": "Polygon"
}
]
}
],
"excludedPaths": [
{
"path": "/SiteId/?"
},
{
"path": "/DeviceId/?"
},
{
"path": "/Temperature/?"
},
{
"path": "/TestStatus/?"
},
{
"path": "/TimeStamp/?"
},
{
"path": "/deviceidday/?"
}
]
}
Listing 7-10Custom Index Settings
清单 7-11 计算了插入的 RU 消耗,花费了 4.95 RUs,延迟相当于 7ms。
db.coll.insert({ "_id" : ObjectId(), "SiteId" : 2, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "deviceidday" : "03/10/2018" });
db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "insert",
"RequestCharge" : 4.95,
"RequestDurationInMilliSeconds" : NumberLong(7)
}
Listing 7-11RU Consumption for Insertion
以下(列表 7-12 )是读取时的 RU 消耗。读请求的请求费用为 3.48 RUs,延迟为 4 ms
globaldb:PRIMARY> db.coll.find({_id:ObjectId("5b0546a8512d8c81c1e6bf95")})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 3.48,
"RequestDurationInMilliSeconds" : NumberLong(4)
}
Listing 7-12RU Consumption While Reading
请注意,在插入文档时,RU 消耗显著减少,但 read 保持不变。
查询模式
查询的复杂性在这里扮演着重要的角色。如果您使用了索引属性,RU 消耗将得到优化。但是,这在非分区集合中是有效的。在分区集合中,使用PartitionKey
值很重要,可以帮助您优化 ru。如果同时使用PartitionKey
和索引属性,这将提高 RU 消耗的效率。让我们看一些例子。
假设PartitionKey
在SensorId
上,执行只有一条记录的查询,如下所示:
globaldb:PRIMARY> db.eventmsgss.find({SensorId:8010003}).limit(1);
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 6.98,
"RequestDurationInMilliSeconds" : NumberLong(5)
}
如果您更改要检索的记录数量,则执行以下操作:
globaldb:PRIMARY> db.eventmsgss.find({SensorId:8010003}).limit(5);
globaldb:PRIMARY> db.runCommand({getLastRequestStatistics:1})
{
"_t" : "GetRequestStatisticsResponse",
"ok" : 1,
"CommandName" : "OP_QUERY",
"RequestCharge" : 9.64,
"RequestDurationInMilliSeconds" : NumberLong(6)
}
RU 费用将增加到 9.64 RUs。
结论
在 Azure Cosmos DB 中,您不需要担心硬件规模,相反,您可以使用应用的事务需求作为规模的基础,例如,多少写入、读取、一致性级别、索引等。您自然能够转换成所需的 ru 数量。一旦你指定了 RU,Azure Cosmos DB 将在后台配置你不需要担心的硬件。
在部署之前确定规模很重要,但是不要太紧张,您也可以在部署之后确定规模。您可以继续使用您预期的 ru,然后您可以通过“metrics”下的“throughput”选项卡监控 ru 的消耗,并在不停机的情况下动态增加/减少 ru。
八、迁移到 Azure Cosmos DB–MongoDB API
既然我已经介绍了 Azure Cosmos DB–MongoDB API 的大部分内容,在本章中,我们将深入研究将整个应用迁移到 Azure Cosmos DB–MongoDB API 的实际逻辑。
迁移策略
有许多策略可以将 NoSQL 数据从一种数据库迁移到另一种类型的数据库。开发者在从一种技术迁移到另一种技术时主要担心的是确保目标和源之间的兼容性。有了协议支持,Azure Cosmos DB with Mongo API 试图解决兼容性问题。理想情况下,您应该只更改连接字符串,它主要在配置文件中提供,并且您可以很容易地替换它。但是在某些情况下,您必须更改代码,因为这里不支持某些命令,例如,$text
、$pull
with condition 等。您可以在 https://aka.ms/mongodb-feature-support
访问 MongoDB API 支持页面,获取最新的命令支持列表。
下一个最重要的考虑因素是如何在最短或没有停机时间的情况下迁移数据。如果 Azure Cosmos DB 可以附加到现有的 MongoDB 集群上,并且所有的数据都可以同步,那么这就很容易了。但是不要担心,有其他方法可以简化迁移。我将在下面讨论这些。参考清单8-1–8-12。
mongoexport 和 mongoimport
MongoDB 有两个工具mongoexport
和mongoimport
来简化迁移。顾名思义,它们通常用于将现有数据导出到 JSON 中,并导入到 MongoDB 实例中。您可以通过 Azure Cosmos DB 使用这两种工具,如下所示:从 SSH 或 RDP 连接到 Mongo 服务器或能够访问服务器的客户端,执行特定于操作系统的命令,这些命令将在下面的章节中单独讨论。
对于 Linux
mongoexport --db <name of database> --collection <name of collection> --out <name of json file to export data in>
Listing 8-1mongoexport Command Template
mongoexport --db test --collection sample --out sample.json
Listing 8-2Exporting Data Using the mongoexport Command
现在,让我们使用mongoimport
在 Azure Cosmos DB 上执行导入到导入数据。
mongoimport --host <Azure Cosmos DB URI>:10255 -u <Name of Azure Cosmos DB account> -p <primary or secondary key> --db <name of the database> --collection <name of collection> --ssl --sslAllowInvalidCertificates --type json --file <path of json file>
Listing 8-3mongoimport Command Template
mongoimport --host testmongo.documents.azure.com:10255 -u testmongo -p jsF6xFsNXz6lZ3tGVjx7bErkQCzoJUzyI2lj8MAqCD --db test --collection sample --ssl --sslAllowInvalidCertificates --type json --file sample.json
Listing 8-4mongoimport Sample Command
对于 Windows mongodump/mongorestore
mongodump
是一个以二进制格式导出数据的 MongoDB 实用程序。它还可以压缩导出的数据,便于移动。mongorestore
从转储中恢复数据,并将其推回到非二进制格式。
以下是命令的详细信息:
对于 Linux
mongodump --host <hostname> --port <port> --collection <name of collection> --username <username> --password <password> --out <nameof file> --gzip
Listing 8-5mongodump Command Template
mongodump --host mongodbtest.site.net --port 37017 --collection coll --username test --password "test" --out mongodbtestdmp --gzip
Listing 8-6mongorestore Command Template
现在,让我们将转储恢复到 Azure Cosmos DB。
mongorestore --host <Azure Cosmos DB account name>.documents.azure.com:10255 -u <Azure Cosmos DB account name> -p <account's primary/secondary key> --db <name of database> --collection <name of collection>--ssl --sslAllowInvalidCertificates mongodbtestdmp --gzip
Listing 8-7mongorestore Command Sample
mongorestore --host testmongocosmos.documents.azure.com:10255 -u testmongocosmos -p jsF6xFsNXz6lZ3tGVjx7bErkQCzoJUzyI2lj8MAqC --db test --collection testcoll --ssl --sslAllowInvalidCertificates mongodbtestdmp --gzip
Listing 8-8mongodump Command Template
对于 Windows
Mongodump.exe --host <hostname> --port <port> --collection <name of collection> --username <username> --password <password> --out <nameof file> --gzip
Listing 8-9mongodump Command Template
Mongodump.exe --host mongodbtest.site.net --port 37017 --collection cooll --username test --password "test" --out mongodbtestdmp --gzip
Listing 8-10mongorestore Command Template
现在,让我们将转储恢复到 Azure Cosmos DB。
mongorestore.exe --host <Azure Cosmos DB account name>.documents.azure.com:10255 -u <Azure Cosmos DB account name> -p <account's primary/secondary key> --db <name of database> --collection <name of collection>--ssl --sslAllowInvalidCertificates mongodbtestdmp --gzip
Listing 8-11mongorestore Command Sample
mongorestore.exe --host testmongocosmos.documents.azure.com:10255 -u testmongocosmos -p jsF6xFsNXz6lZ3tGVjx7bErkQCzoJUzyI2lj8MAqC --db test --collection testcoll --ssl --sslAllowInvalidCertificates mongodbtestdmp --gzip
Listing 8-12mongodump Command Template
批量遗嘱执行人
这个工具是最近添加到 Azure Cosmos DB 中的,可以在几分钟内上传数百万个文档。这是一个基于 AIMD 式拥塞控制机制设计的客户端库。这将有助于创建基于键范围的多线程,并以并行方式访问所有分区。正如我们在第 7 章中所解释的,每个分区将具有相同的 ru,因此将所有分区放在一起会将吞吐量消耗增加到 100%。它可以消耗超过 500 K RU/s 的数据,并在一小时内推送数 TB 的数据。API 详情请参考清单 8-13 和 8-14 。
BulkImportResponse bulkImportResponse = await bulkExecutor.BulkImportAsync(
documents: documentsToImportInBatch,
enableUpsert: true,
disableAutomaticIdGeneration: true, maxConcurrencyPerPartitionKeyRange: null,
maxInMemorySortingBatchSize: null,
cancellationToken: token);
Listing 8-13Usage of BulkImport API to create the data
BulkUpdateResponse bulkUpdateResponse = await bulkExecutor.BulkUpdateAsync(
updateItems: updateItems,
maxConcurrencyPerPartitionKeyRange: null,
maxInMemorySortingBatchSize: null,
cancellationToken: token);
Listing 8-14Usage of BulkImport API to which will update the document if exists
应用开关
现在,是时候改变应用了,通过切换连接字符串并将其连接到 Azure Cosmos DB–MongoDB API(见图 8-1 和 8-2 )。
图 8-1
Copying the connection string from the portal (either primary or secondary)
从门户复制连接字符串(主要的或次要的),然后用现有的连接字符串替换它(在您的应用的app.config or web.config
中)。
图 8-2
Replacing the connection string in the application’s config
file
彻底测试应用,执行功能和负载测试,以确保应用的正确结果。
Note
测试是至关重要的,因为它将让您了解运行应用或处理峰值负载所需的 ru,您可以随时更改这些 ru。这也将利用你在整本书中学到的知识。
最佳化
以下是优化过程:
-
In Azure Cosmos DB, increase the RUs for the duration of the import/restore and keep an eye on the throttling at Azure Metrics. If an error occurs, increase the RUs further. (See Figure 8-3.)
图 8-3
Monitoring throughput metrics for throttling errors
-
确保在查询字符串级别启用 SSL,因为 Azure Cosmos DB 不允许不安全的连接。
-
尝试在配置 Azure Cosmos DB 的同一地区使用 Azure 中的虚拟机。否则,网络延迟会增加恢复/导入时间。
-
It is possible to determine network latency from the client machine. Execute
setVerboseShell(true)
in the MongoDB shell (see Figure 8-4). Next, execute the following command:图 8-4
Identifying the latency from the MongoDB shell
db.coll.find().limit(1)
-
对于
mongoimport
,按如下方式配置batchSize
和numInsertionWorkers
:batchSize
=单个单据消耗的总供应 ru/ru。如果计算出的batchSize
< = 24,则将其作为batchSize
值;否则,使用 24。numInsertionWorkers
=(调配的吞吐量延迟(秒)/(批处理大小单次写入消耗的 ru)。
下面是一个例子:
batchSize= 24
RUs provisioned=10000
Latency=0.100 s
RU charged for 1 doc write=10 RUs
numInsertionWorkers= (10000 RUs x 0.1 s) / (24 x 10 RUs) = 4.1666
最后的命令将是
mongoimport --host testmongocosmos.documents.azure.com:10255 -u testmongocosmosd -p jsF6xFsNXz6lZ3tGVjx7bErkQCzoJUzyI2lj8 --db test --collection coll --ssl --sslAllowInvalidCertificates --type json --file sample.json --numInsertionWorkers 4 --batchSize 24
Code: Finished mongoimport command
Note
除了上文描述的工具,你还可以使用其他工具,比如mongomirror
和mongochef
,将你的数据从mongodb
迁移到 Azure Cosmos DB。
结论
从 Mongo DB 迁移到 Azure Cosmos DB - Mongo DB API 是非常有意义的,因为没有管理开销、高可伸缩性、高弹性、最低延迟、最高可用性,并且所有这些都包含在 SLA 中。现在,接下来的迁移非常具有挑战性,Azure Cosmos DB 的迁移非常简单,因为其强大的 MongoDB 协议支持,只需要很少或不需要任何更改。对于数据,协议再次支持投入,并提供使用 MongoDB 现有工具的可能性。您可以使用 MongoDB 的 shell 命令导入/导出、恢复或他们的 OOTB 工具,如 mongomirror、mongochef 等。最近,推出了 BulkExecutor 工具,它将使数据推送并行化,并将推送数据的时间减少 40 - 50 倍。
下一章,我们将探讨高级功能,如 Spark、聚合管道等。
九、Azure Cosmos DB–MongoDB API 高级服务
最后,我们已经到了旅程的最后一章,我想分享一些适用于日常场景的要点。
聚合管道
这是 MongoDB 的固有特性,对于需要分析和特定聚合的工作负载至关重要。在 Azure Cosmos DB 中,支持数据聚合管道。然而,在写这本书的时候,它是公开预览的,必须通过导航到 Azure portal 中 Azure Cosmos DB blade 下的预览项目来显式启用。
现在,让我们把手弄脏。打开你最喜欢的 MongoDB 控制台,连接 Azure Cosmos DB。
sudo mongo <Azure Cosmos DB Account Name>.documents.azure.com:10255/db -u < Azure Cosmos DB Account Name > -p <primary/secondary key> --ssl --sslAllowInvalidCertificates
以下(列表 9-1 )是示例文档和命令,用于汇总每个传感器的消息数(列表 9-2 ):
{ "_id" : ObjectId("5acafc5e2a90b81dc44b3963"), "SiteId" : 0, "DeviceId" : 0, "SensorId" : 0, "Temperature" : "20.9", "TestStatus" : "Pass", "TimeStamp" : ISODate("2018-03-10T05:38:34.835Z"), "deviceidday" : "03/10/2018" }
Listing 9-1Sample Document
globaldb:PRIMARY> db.book.aggregate([{$match:{TestStatus: "Pass", DeviceId:6} },{$group:{_id: "$SensorId", total: {$sum: 1}}},{ $sort:{SensorId: -1}}]);
Listing 9-2Aggregate Command to Count Messages per Sensor, Where DeviceId Is 6 and TestStatus Is Pass
输出如下:
{ "_id" : 0, "total" : 173 }
{ "_id" : 1, "total" : 173 }
{ "_id" : 2, "total" : 173 }
{ "_id" : 3, "total" : 173 }
{ "_id" : 4, "total" : 173 }
{ "_id" : 5, "total" : 173 }
{ "_id" : 6, "total" : 173 }
{ "_id" : 7, "total" : 173 }
{ "_id" : 8, "total" : 173 }
{ "_id" : 9, "total" : 173 }
现在,让我们创建另一个示例。在这个例子中(清单 9-3 ,我们将使用db.runCommand
而不是db.collection.find().count
。原因很简单,如果孤立文档存在或者正在进行分区负载平衡,那么db.collection.find().count()
会导致不准确的计数。为了避免这种情况,建议在 MongoDB 中使用带有分片集群的db.runCommand
。默认情况下,在 Azure Cosmos DB 中,每个实例都由一个分区组成;所以计数用db.runCommand
代替db.Collection.find().
count
比较合适。
globaldb:PRIMARY> db.runCommand({ count: "book",query: {"DeviceId": {$gte:10} }} )
Listing 9-3Counting the Number of Documents in a Collection Named “book” That Has DeviceId>10
输出如下:
{ "_t" : "CountResponse", "ok" : 1, "n" : NumberLong(81828) }
让我们给count
命令列表 9-4 增加一些复杂性。
globaldb:PRIMARY> db.runCommand({ count: "book",query: {"DeviceId": {$gte:10} }, skip: 10} )
output
{ "_t" : "CountResponse", "ok" : 1, "n" : NumberLong(81818) }
Listing 9-4Counting the Number of Documents in a Collection Named “book” That Has DeviceId Greater Than Ten and Skips the First Ten Rows
清单 9-5 中的代码在集合“book”中获得不同的DeviceID
globaldb:PRIMARY> db.runCommand({distinct: "book", key:"DeviceId"})
{
"_t" : "DistinctResponse",
"ok" : 1,
"waitedMS" : NumberLong(0),
"values" : [
"20.9"
]
}
Listing 9-5Selecting a Distinct Value for a Specified Key
列表 9-6 根据基础对设备进行分组,并计算每个设备中的传感器数量。
globaldb:PRIMARY> db.runCommand({aggregate: "book", pipeline:[{$group: { _id: "$DeviceId", count: {$sum : 1 }} }] })
Listing 9-6Grouping Devices by Basis and Counting the Number of Sensors in Each
上述清单的输出如下:
{ "result" : [
"_t" : "AggregationPipelineResponse`1",
"ok" : 1, "_id" : "DeviceId",
"waitedMS" : NumberLong(0),051
"cursor" : {
] "ns" : "db.book",
} "id" : NumberLong(0),
globaldb:PRIMARY"firstBatch" : [
{
"_id" : 0,
"count" : 20
},
{
"_id" : 1,
"count" : 2
},
{
"_id" : 2,
"count" : 20
},
{
"_id" : 3,
"count" : 2
}
]}}
使用$match
,您可以聚合命令(清单 9-7 )。
db.book.aggregate( [
{ $match: { "$Temperature": { gte: 0 } } },
{
$group: {
"_id": "$DeviceId",
"avgTemperature": { "$avg": "$Temperature" }
}
}
] )
Listing 9-7Aggregating the Query to Identify the Average Temperature vs. DeviceId
输出如下:
{ "_id" : 0, "avgDevice" : 0 }
{ "_id" : 1, "avgDevice" : 1 }
{ "_id" : 2, "avgDevice" : 2 }
让我们用$project
和$match
来表示条件。
db.eventmsgsd.aggregate( [ {$match:{DeviceId:1001}}, { $project: { Temperature: 1, "DeviceId": 1, "SensorId" : 1, "SiteId": { $cond: { if: { $eq: [ 1, "$SiteId" ] }, then: "$$REMOVE", else: "$SiteId" } } } }] );
输出如下:
{ "_id" : ObjectId("5b0449132a90b84018822f96"), "Temperature" : "20.9", "DeviceId" : 1001, "SensorId" : 1001003 }
{ "_id" : ObjectId("5b0449132a90b84018822f97"), "Temperature" : "20.9", "DeviceId" : 1001, "SensorId" : 1001004 }
正如你所看到的,Azure Cosmos DB 支持大多数聚合表达式和管道阶段,在大多数情况下,允许应用开发者快速迁移到 Azure Cosmos DB,而无需更改任何代码。
火花连接器
这是收集/分析数据的最丰富、最有效的方式。MongoDB 的 Spark 连接器也可以用在这里。
让我们一步一步地经历这个过程。
第 1 步:调配 HD Insight 并为其增添活力。为此,导航至portal.azure.com
,点击创建资源,搜索 HDInsight,然后选择适当的选项(参见图 9-1 )。
图 9-1
Creating HDInsight from the Azure portal (search and select the image)
将出现一个页面,提供有关 HDInsight 的详细信息。在此页面上,点击创建(图 9-2 )。
图 9-2
HDInsight service details page
现在,会出现一个表格,你必须在上面填写必要的信息。完成后,点击下一步(见图 9-3 )。
图 9-3
Fill in the basic details
点击集群类型并选择您的首选处理框架(图 9-4 )。
图 9-4
Click Cluster type and choose Spark (version 2.2.0)
现在返回并单击下一步。在这里,您可以指定与存储相关的信息。现在,您可以保留其默认设置,并点击下一步(见图 9-5 )。
图 9-5
Specify the storage information
现在,单击 Create 提交带有 Spark 的 HDInsight 集群的部署(参见图 9-6 )。
图 9-6
Summary form
第二步:让我们使用 SSH 进入 Spark 集群。导航到 SSH ➤集群登录,然后从下拉菜单中选择主机名,并在它下面的框中复制 SSH 命令(参见图 9-7 )。现在打开 SSH 工具,复制粘贴命令连接到头节点(参见图 9-8 )。
图 9-8
Connect to Head Node using SSH
图 9-7
Locating the SSH command
步骤 3:使用以下代码下载 Spark 连接器:
wget https://scaleteststore.blob.core.windows.net/mongo/mongo-spark-connector-assembly-2.2.0.jar
第 4 步:运行 Spark shell 命令,用您的 Mongo 端点、数据库和集合细节替换它。输入和输出可以是相同的。
spark-shell --conf "spark.mongodb.input.uri=mongodb://testmongo:jsFlj8MAqCDqjaPBE2DWRhm9jRx5QfMQ3SYf9vwGxElPjZmeQKO1vbA==@testmongo.documents.azure.com:10255/?ssl=true&replicaSet=globaldb" --conf "spark.mongodb.output.uri=mongodb://testmongobook:jsF6xFsNXz6lZ3tGVjx7bErkQCzoJUzyI2lj8MAqCDqjaPBE2DWRhm9jRx5QfMQ3SYf9vwGxElPjZmeQKO1vbA==@testmongobook.documents.azure.com:10255/?ssl=true&replicaSet=globaldb" --conf "spark.mongodb.input.database=db" --conf="spark.mongodb.input.collection=eventmsgss" --conf "spark.mongodb.output.database=db" --conf="spark.mongodb.output.collection=coll" --jars mongo-spark-connector-assembly-2.2.0.jar
现在,在成功执行上述代码后,您将获得 Scala 控制台,这是 SparkSQL 的游乐场。执行以下代码:
scala> import com.mongodb.spark._
import com.mongodb.spark._
scala> import org.bson.Document
import org.bson.Document
scala> val rdd = MongoSpark.load(sc)
现在,让我们执行几个聚合查询(参见清单 9-8 和 9-9 )。
scala> val agg = rdd.withPipeline(Seq(Document.parse("{ $match: { DeviceId : { $eq : 1004 } } }")))
scala> println(agg.count)
Listing 9-8Counting the Number of Records on the Basis of the Filter
让我们看另一个例子。
val agg = rdd.withPipeline(Seq(Document.parse("{ $group: { _id : '$SensorId', total:{$sum:1} } } ")))
Listing 9-9Grouping by SensorId and Counting the Values
现在,您可以更高效地运行所有聚合查询。您可以将结果导出到 Azure Cosmos DB 的另一个集合中,这将帮助您分析所有数据,并离线聚合这些数据。然后可以使用这个集合在 UI 上快速展示结果。
结论
现在,您已经了解了 Azure Cosmos DB–Mongo DB API 的大部分特性和功能。协议支持保持了无缝的迁移路径和最小的学习曲线。在单个 Azure Cosmos DB 实例中,如果您配置了最少一个地理复制,您将获得每个分区的高可用性和内置的灾难恢复。此外,您还可以获得针对一致性、可用性、延迟和吞吐量的全面 SLA,这可能是一件非常昂贵的事情。这将帮助您使您的应用全年高效可用,尽可能减少延迟。所有这些都将提高应用的用户体验。考虑到这些事实,选择 Azure Cosmos DB 升级您的应用并添加特色架构工具没有太多麻烦是显而易见的。
Note
Azure Cosmos DB–MongoDB API 受到 MongoDB 中不存在的特性的限制,例如存储过程、函数、变更提要等。然而,我希望所有这些特性将很快成为这个 API 的一部分。
标签:--,MongoDB,DB,索引,开发者,Azure,CosmosDB,Cosmos From: https://www.cnblogs.com/apachecn/p/18448092