一、为什么是 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 所示。
图 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
范围索引
对于具有操作(例如, l t , lt, lt,gt,$ lte, g t e , gte, 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 章中,我们将讨论规模调整以及分区对规模调整计算的影响。