设计可伸缩的高性能表

提示

本文中的内容适用于原始的 Azure 表存储。 不过,相同概念也适用于较新的 Azure Cosmos DB for Table,后者提供更高的性能和可用性、全局分布和自动辅助索引。 它还可用于基于使用量的无服务器模式。 Azure Cosmos DB 中的表 API 和 Azure 表存储之间存在某些功能差异。 有关详细信息,请参阅 Azure Cosmos DB for Table。 为了便于开发,我们现在提供统一的 Azure 表 SDK,它可用于同时面向 Azure 表存储和 Azure Cosmos DB for Table。

要设计可伸缩的高性能表,必须考虑性能、可伸缩性和成本等诸多因素。 如果你以前为关系数据库设计过架构,则应当很熟悉这些注意事项,尽管 Azure 表服务存储模型与关系模型之间有一些相似之处,但也存在重大差异。 这些差异通常会导致不同的设计,这些设计对于熟悉关系数据库的人来说可能看起来不直观或是错误的,但如果正在设计 Azure 表服务等 NoSQL 键/值存储,就会体会到这些设计是合理的。 许多设计差异将反映这样一个事实:表服务旨在支持云级别应用程序,这些应用程序可包含数十亿个实体(或关系数据库术语所称的行)的数据,或者用于必须支持高事务量的数据集。 因此,需要以不同方式考虑如何存储数据,并了解表服务的工作原理。 相对于使用关系数据库的解决方案而言,设计良好的 NoSQL 数据存储可以使解决方案以更低的成本更进一步扩展。 本指南中介绍这些主题。

关于 Azure 表服务

本部分重点介绍表服务的一些主要功能,这些功能尤其与设计性能和可伸缩性相关。 如果不熟悉 Azure 存储和表服务,请在阅读本文的其他部分之前,先阅读通过 .NET 实现 Azure 表存储入门。 尽管本指南的重点是介绍表服务,但它也包括对 Azure 队列和 Blob 服务的论述,并介绍了如何将它们与表服务一起使用。

什么是表服务? 从名称可以推测出,表服务将使用表格格式来存储数据。 在标准术语中,表的每一行表示一个实体,而列存储该实体的各种属性。 每个实体都有唯一地标识它的一对键,还有一个时间戳列,表服务使用该列来跟踪实体的最后更新时间。 时间戳是自动应用的,无法使用任意值手动覆盖它。 表服务使用此上次修改时间戳 (LMT) 来管理开放式并发。

注意

表服务 REST API 操作还会返回它从 LMT 推导出的 ETag 值。 本文档互换使用术语 ETag 和 LMT,因为它们指的是同一基础数据。

下面的示例演示了一个简单的表设计,该表用于存储员工和部门实体。 本指南后面所示的许多示例都基于此简单设计。

PartitionKey RowKey 时间戳
Marketing 00001 2014-08-22T00:50:32Z
FirstName LastName Age 电子邮件
Don Hall 34 donh@contoso.com
Marketing 00002 2014-08-22T00:50:34Z
FirstName LastName Age 电子邮件
Jun Cao 47 junc@contoso.com
Marketing 部门 2014-08-22T00:50:30Z
DepartmentName EmployeeCount
Marketing 153
Sales 00010 2014-08-22T00:50:44Z
FirstName LastName Age 电子邮件
Ken Kwok 23 kenk@contoso.com

到目前为止,此数据看起来非常类似于关系数据库中的表,主要区别是有必需的列,以及能够在同一个表中存储多种实体类型。 此外,FirstNameAge 等用户定义的每个属性还具有数据类型(如 integer 或 string),就像关系数据库中的列一样。 虽然与关系数据库中不同,表服务的架构灵活性质意味着每个实体的属性不需要具有相同的数据类型。 若要在单个属性中存储复杂数据类型,必须使用序列化格式(例如,JSON 或 XML)。 若要深入了解表服务(例如支持的数据类型、支持的日期范围、命名规则和大小限制),请参阅 Understanding the Table Service Data Model(了解表服务数据模型)。

PartitionKeyRowKey 的选择是实现良好的表设计的基础。 表中存储的每个实体都必须具有唯一的 PartitionKeyRowKey。 与关系数据库表中的键一样,将为 PartitionKeyRowKey 值编制索引来创建聚集索引以便快速地进行查找。 但是,表服务不创建任何辅助索引,因此,PartitionKeyRowKey 是唯一具有索引的属性。 表设计模式中介绍的一些模式展示了可以如何解决此明显的限制。

一个表包含一个或多个分区,为优化解决方案,所做的很多设计决策都将围绕选取合适的 PartitionKeyRowKey 而展开。 一个解决方案可以仅包含单个表,该表包含组织为分区的所有实体,但通常一个解决方案具有多个表。 表可帮助你在逻辑上组织实体,帮助你使用访问控制列表管理对数据的访问,并且可以使用单个存储操作删除整个表。

表分区

帐户名称、表名称和 PartitionKey 共同标识存储服务中表服务用于存储实体的分区。 作为实体寻址方案的一部分,分区定义事务的作用域(详见下方的实体组事务),并构成表服务缩放方式的基础。 有关分区的详细信息,请参阅表存储的性能与可伸缩性核对清单

在表服务中,单个节点为一个或多个完整的分区提供服务,并且该服务可通过对节点上的分区进行动态负载均衡来进行缩放。 如果某节点负载过轻,表服务将该节点针对的分区范围拆分为不同节点;流量下降时,该服务可将无操作的节点的分区范围合并为单个节点。

有关表服务的内部细节(特别是服务管理分区的方式)的详细信息,请参阅文章 Microsoft Azure 存储:具有非常一致性的高可用云存储服务

实体组事务

在表服务中,实体组事务 (EGT) 是唯一内置机制,用于对多个实体执行原子更新。 EGT 有时也被称为“批处理事务”。 EGT 只能对存储在同一分区中的实体(也就是说,在给定的表中共享同一分区键)执行操作。 因此,任何时候需要实现跨多个实体的原子事务行为时,必须确保那些实体位于同一分区中。 这通常是将多个实体类型保存在同一个表(和分区)中,而不是对不同实体类型使用多个表的原因。 单个 EGT 最多可应用于 100 个实体。 若要提交多个并发 EGT 进行处理,请务必确保不在 EGT 共用实体上操作这些 EGT,否则会造成延迟处理。

EGT 还引入了一个在设计时需要评估的潜在权衡。 那就是,使用更多分区会提高应用程序的可伸缩性,因为 Azure 可以有更多的机会在各个节点之间对请求进行负载均衡。 但是,使用更多分区可能会限制应用程序执行原子事务以及保持数据的强一致性的能力。 而且,在分区级别还有特定的可伸缩性目标,这些目标可能会限制预期单个节点可以实现的事务吞吐量。 有关 Azure 标准存储帐户的可伸缩性目标的详细信息,请参阅标准存储帐户的可伸缩性目标。 有关表服务的可伸缩性目标的详细信息,请参阅表存储的可伸缩性和性能目标

容量注意事项

下表描述了表存储的容量、可伸缩性和性能目标。

资源 目标
Azure 存储帐户中表的个数 仅受存储帐户的容量限制
表中的分区个数 仅受存储帐户的容量限制
分区中实体的个数 仅受存储帐户的容量限制
单个表的最大大小 500 TiB
单个实体的最大大小,包括所有属性值 1 MiB
表实体中属性的最大数目 255(包括 3 个系统属性:PartitionKeyRowKeyTimestamp
实体中单个属性的最大总大小 因属性类型而异。 有关详细信息,请参阅了解表服务数据模型中的属性类型
PartitionKey 的大小 大小最大为 1 KiB 的字符串
RowKey 的大小 大小最大为 1 KiB 的字符串
实体组事务的大小 一个事务最多可包含 100 个实体,并且有效负载大小必须小于 4 MiB。 实体组事务只能包含对实体的更新一次。
每个表存储的访问策略的最大数目 5
每个存储帐户的最大请求速率 20,000 事务/秒,假定实体大小为 1-KiB
单个表分区的目标吞吐量(1 KiB 实体) 每秒最多 2,000 个实体

成本注意事项

表存储的价格相对便宜,但在评估任何表服务解决方案时,应同时针对容量使用情况和事务数量进行成本估算。 但是,在许多情况下,为提高解决方案的性能或可伸缩性,存储非规范化或重复的数据是一种有效方法。 有关定价的详细信息,请参阅 Azure 存储定价

后续步骤