数据点

Microsoft Azure DocumentDB 概述

Julie Lerman

下载代码示例(VB)

Julie Lerman2011 年 11 月,我撰写过一篇专栏,标题为“文档数据库到底是什么?”(msdn.microsoft.com/magazine/hh547103),我在其中讨论了最常见的文档数据库:MongoDB、CouchDB 和 RavenDB。这三个 NoSQL 数据库仍然非常流行。那时,尽管 Microsoft 在市场上已有 Microsoft Azure 表存储(一个基于键-值对的 NoSQL 数据库),但市场还没有 Microsoft 文档数据库。但是,2014 年 8 月,Microsoft Azure DocumentDB 首次发布,顾名思义,它是在 Azure 上提供的 NoSQL 文档数据库服务。

在本专栏中,我将概括介绍 Azure DocumentDB,我希望能引发您对它进行更深入的研究。此服务在 Azure 上提供,甚至在其 4 月 8 日从预览版升级为通用版本之前就已投入使用。例如,bit.ly/1GMnBd9 上有一个很好的客户案例,一家名为 SGS 的公司将 DocumentDB 作为其部分解决方案进行实施。该项目的其中一名开发人员向我发送了关于该案例的推文,声称到目前为止客户对它感到很满意。

文档数据库是什么?

我之前的专栏曾重点回答过这个问题,不过我在这里还是会简单进行介绍,并推荐您阅读那篇专栏文章。文档数据库将数据作为文档(大多数情况下,作为单个 JSON 文档)进行存储。(MongoDB 更改较小,因为它将其多个 JSON 文档压缩为一个称为 BSON 的二进制格式)。当处理大量数据时,此存储可提供的性能更快,因为它不需要跳过所有数据库来汇集相关数据。相关数据可被合并在一个 JSON 文档中。文档数据库和其他 NoSQL 数据库另一个重要特征是它们的架构灵活。与需要对表进行预定义架构才能存储和检索数据的关系数据库不同,文档数据库允许每一个数据定义其自己的架构。所以,此数据库由各个文档的集合构成。图 1 显示单个 JSON 文档外观的简单示例。注意,它指定了属性名称和值,且包含相关数据。

图 1 一个简单的 JSON 文档

{
  "RecipeName": "Insane Ganache",
  "DerivedFrom": "Café Pasqual’s Cookbook",
  "Comments":"Insanely rich. Estimate min 20 servings",
  "Ingredients":[
    {
      "Name":"Semi-Sweet Chocolate",
      "Amount":"1.5 lbs",
      "Note":"Use a bar, not bits. Ghiradelli FTW"
    },
    {
      "Name":"Heavy cream",
      "Amount":"2 cups"
    },
    {
      "Name":"Unsalted butter",
      "Amount":"2 tbs"
    }
],
  "Directions": "Combine chocolate, cream and butter in the top ..."
}

此数据不是全部都是 JSON 格式和自描述性数据,它还包含相关数据(组成部分)。某些这类文档数据库共有的另一个特性是,他们都可通过 HTTP 调用访问。您稍后会看到更多相关信息,不过,同样,以前的专栏曾详细讨论过这些特征以及此类数据库共有的其他特性。

Azure DocumentDB 的结构

图 1 显示了文档数据库中存储的典型文档的外观。不过,Azure DocumentDB 不只是由这些数据库构成。文档被视为 Azure DocumentDB 中的资源,可能会被分组为各个集合,这些集合也是 DocumentDB 中的资源。您可以使用 HTTP 调用来创建、更新、删除和查询集合,就如同您操作文档一样。事实上,DocumentDB 完全是由不同类型的资源组成的。集合被分组到一个 DocumentDB 数据库中。您可在一个 DocumentDB 帐户中创建多个数据库,您还可以拥有多个帐户。

所有这些资源都是生态环境中的一流成员。此外,还有一组资源可伴随您的文档提供。它们按照关系数据库用户所熟悉的方式进行命名:存储过程、用户定义函数 (UDF)、索引和触发器。

最后一个与文档相关的资源是附件,它是附加在 JSON 文档上的任意一种二进制文件。二进制附件位于 Azure Blob 存储中,但是元数据存储在 DocumentDB 中,以确保您可查询不同属性的附件。

此外,DocumentDB 具备内置安全功能,在该范围内,用户和权限也是您可与之交互的资源,方法与您和文档交互方法一样。

与 DocumentDB 交互

有多种方式可使用 Azure DocumentDB 中的资源,其中包括:SQL、REST API 和各个客户端 API,包括 .NET API,它让您可使用 LINQ 来查询数据库。您可以在 bit.ly/1aLm4bC 中了解更多关于查询的详情。

您可以在 Azure 门户中创建和管理 DocumentDB 帐户。(可在 bit.ly/1Cq8zE7 上查看文档。)您还可以在门户中管理 DocumentDB,还可以使用文档资源管理器和查询资源管理器查看和查询您的文档。在查询资源管理器中,您可以使用 SQL 语法,就像我在图 2 中进行简单查询一样。

使用 SQL 语法在 Azure 门户查询资源管理器中查询文档
图 2 使用 SQL 语法在 Azure 门户查询资源管理器中查询文档

您还可以在应用中使用此 SQL。例如,此处为“使用 DocumentDB 构建 Node.js Web 应用程序”中的某些代码 (bit.ly/1E7j5Wg),其中,查询以 SQL 语法表达:

getOrCreateDatabase: function (client, databaseId, callback) {
  var querySpec = {
    query: 'SELECT * FROM root r WHERE r.id=@id',
    parameters: [{
      name: '@id',
      value: databaseId
    }]
  };

在 DocumentDB 早期阶段,您可能发现此 SQL 语法受限,不过请记住,您可以使用 UDF 补充现有 SQL。例如,您可以编写您自己的 CONTAINS 函数,以构建可评估字符串的谓词,例如 CONTAINS(r.name, “Chocolate”)。

与其他许多 Azure 资源一样,Azure DocumentDB 包含本机 REST API,且可使用 HTTP 进行查询和更新。每一个资源都包含唯一的 URI。此处为针对特殊 DocumentDB 权限的 HTTP 请求的示例:

GET https://contosomarketing.documents.azure.com/dbs/ruJjAA==/users/ruJjAFjqQAA=/permissions/ruJjAFjqQABUp3QAAAAAAA== HTTP/1.1
x-ms-date: Sun, 17 Aug 2014 03:02:32 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig%3dGfrwRDuhd18ZmKCJHW4OCeNt5Av065QYFJxLaW8qLmg%3d
x-ms-version: 2014-08-21
Accept: application/json
Host: contosomarketing.documents.azure.com

有关直接使用 REST API 的详细信息,请访问 bit.ly/1NUIUd9。但是,对任何 REST API 的使用会变得很繁琐。已经有许多客户端 API 可用于与 Azure DocumentDB 进行交互:.NET、Node.js、JavaScript、Java 和 Python。可在 bit.ly/1Cq9iVJ 上下载 SDK 并读取文档。

.NET 库允许您使用 LINQ 进行查询,这将受到 .NET 开发人员的青睐。LINQ 方法支持肯定会随时间的推移而增长,但当前受支持的 LINQ 表达式是:Queryable.Where、Queryable.Select 和 Queryable.SelectMany。

在您可以与 DocumentDB 执行任意交互之前,您需要指定要工作的帐户、数据库和集合。如以下示例所示,使用 .NET API 定义 Microsoft.Azure.Documents.ClientDocument:

string endpoint = ConfigurationManager.AppSettings["endpoint"];
string authKey = ConfigurationManager.AppSettings["authKey"];
Uri endpointUri = new Uri(endpoint);
client = new DocumentClient(endpointUri, authKey);

此示例代码来自 Azure 文档页面 (bit.ly/1HS6OEe) 中的 ASP.NET MVC 和 DocumentDB 演练。此演练相当完整,以在 Azure 门户中创建 DocumentDB 帐户的步骤为开头。我强烈推荐此演练,或者,使用其中一个以其他语言演示 DocumentDB 的演练,例如,我之前提到过的 Node.js 文章。示例应用程序包含一个类型、一个“项”类,如图 3 所示。

图 3 项类

public class Item
  {
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }
    [JsonProperty(PropertyName = "descrip")]
    public string Description { get; set; }
    [JsonProperty(PropertyName = "isComplete")]
    public bool Completed { get; set; }
  }

请注意,项类中的每一个属性都指定 JsonProperty PropertyName。这不是必需项,但它允许 .NET 客户端在已存储的 JSON 数据和我的项类型之间进行映射,并允许我随意命名类属性,无论其在数据库中的名称是怎样的。使用定义的客户端,您就可以表达可返回已知数据库 ID 的 Microsoft.Azure.Documents.Database 实例的 LINQ 查询:

var db = Client.CreateDatabaseQuery()
               .Where(d => d.Id == myDatabaseId)
               .AsEnumerable()
               .FirstOrDefault();

从此处,您可以定义数据库内的集合,最后通过如下的 LINQ 表达式查询集合,它将返回一个 JSON 文档:

return Client.CreateDocumentQuery(Collection.DocumentsLink)
             .Where(d => d.Id == id)
             .AsEnumerable()
             .FirstOrDefault();

.NET API 内的各个对象使用 CreateDocument­Async、UpdateDocumentAsync 和 DeleteDocumentAsync (CUD) 方法启用操作以插入、更新和删除文档,这会将 HTTP 调用打包到 REST API 中。与查询类似,还有一些相关 CUD 方法适合其他资源类型,例如,存储过程和附件。

CAP 新转换

将 DocumentDB 与其他文档数据库区别开来的一个更加有趣的方面是,它允许您调整一致性。我此前关于文档数据库的文档讨论过 CAP 定理,认为在分布式系统的一致性、可用性和分区 (CAP) 容错性保证中,系统只能保证实现三个中的两个。关系数据库会以损失可用性为代价确保一致性(例如,等待事务完成)。而另一方面,NoSQL 数据库更能容忍最终一致性问题,为了获得可用性,数据可以不必百分百为当前数据。

Azure DocumentDB 通过允许您调整一致性的级别,提供可应对 CAP 定理的新方式,从而提供了可同时从可用性和分区容错性中获益的机会。您可以在四个一致性级别(强、有限度的过时、会话和最终一致性)中进行选择,这四个级别可根据操作(而不只是根据数据库)定义。与全部一致性或无一致性不同,您可以调整一致性的级别以满足您的解决方案需要。有关更多信息,请参阅 bit.ly/1Cq9p3v 上的 Azure DocumentDB 文档页面。

服务器端 JavaScript

很多人可能已经熟悉存储过程和关系数据库中的 UDF,与其他文档数据不同,Azure DocumentDB 包含这些概念,尽管它们以 JavaScript 编写。JavaScript 本身可与 JSON 交互,所以与 JSON 文档和其他资源之间的交互将会极其有效。不需要转换或翻译或映射。以存储的过程或 UDF 触发器的形式拥有服务器端 JavaScript 的另一个好处是,您可在多个文档中获得原子事务(如果一个过程失败,则事务范围内的一切都会回滚)。对存储过程和 UDF 的定义与您在关系数据库(例如,SQL Server)所使用的非常不同。此门户不提供此能力。而是,您在客户端代码中定义您的服务器端代码。我推荐在 bit.ly/1FiNK4y 上了解 Azure DocumentDB .NET 代码示例的“服务器端脚本”部分。

现在,我将要介绍如何创建并存储已存储的过程,然后介绍如何执行该过程。图 4 显示的是使用 .NET API 代码将已存储过程插入 DocumentDB 的简单示例。

图 4 将已存储过程插入 DocumentDB

public static async Task<StoredProcedure> InsertStoredProcedure() {
  var sproc = new StoredProcedure
              {
                Id = "Hello",
                Body = @"
                  function() {
                    var context = getContext();
                    var response = context.getResponse();
                    response.setBody('Stored Procedure says: Hello World');
                  };"
              };
  sproc = await Client.CreateStoredProcedureAsync(setup.Collection.SelfLink, sproc);
  return sproc;
}

为了简单起见,我将所有逻辑封装在单个方法中。我的 StoredProcedure 对象由一个 ID 和正文组成。正文是服务器端 JavaScript。您可能倾向于为每一个过程创建 JavaScript 文件,并在创建 StoredProcedure 对象时读取其内容。代码假定 StoredProcedure 尚不存在于数据库中。在下载示例中,您将看到我调用自定义方法来查询数据库,以确保在插入过程之前该过程不存在。最后,我使用 SetupDocDb<T>.Client 属性(此属性提供 DocumentClient 实例)创建存储过程,这与我此前查询文档的做法相似。

现在,存储过程存在于数据库中,我可以使用它。这对于我来说有点难,因为我使用的是 SQL Server 的工作方法,这是不同的。即使我知道过程的 ID 是“Hello”,也知道当前的 API,但这在调用 ExecuteStoredProcedureAsync 时不足以识别此过程。每个资源都拥有一个 DocumentDB 创建的 SelfLink。SelfLink 是一个固定不变的键,支持 DocumentDB 的 REST 功能。它可以确保每个资源都拥有固定不变的 HTTP 地址。我需要 SelfLink 告诉数据库执行哪一个存储过程。这意味着,我必须先查询数据库以使用熟悉的 ID(“Hello”)查找存储过程,这样我便可以查找其 SelfLink 值。此工作流对于开发人员来说会引起冲突,DocumentDB 团队正在更改其工作方式以消除对任何 SelfLink 的需要。当此篇文章发布时,可能已经有所改动。但是现在,我会查询过程,与查询 DocumentDB 资源一样:我将使用 CreateStoredProcedureQuery 方法。然后,通过 SelfLink,我可以执行此过程并获得结果:

public static async Task<string> GetHello() {
  StoredProcedure sproc = Client.CreateStoredProcedureQuery(Collection.SelfLink)
    .Where(s => s.Id == "Hello")
    .AsEnumerable()
    .FirstOrDefault();
  var response =
    (await Client.ExecuteStoredProcedureAsync<dynamic>(sproc.SelfLink)).Response;
  return response.ToString();
}

创建 UDF 与其相似。您可以将 UDF 定义为 UserDefinedFunction 对象中的 JavaScript,并将其插入 DocumentDB 中。当它存在于数据库中时,您可以在您的查询中使用该函数。尽管在 2015 年 4 月初 DocumentDB 正式发布之前就已添加了 LINQ 支持,但是,最初只有将 SQL 语法作为 CreateDocumentQuery 方法的参数才可能进行该操作。以下为使用自定义 UDF 的 SQL 查询示例:

select r.name,udf.HelloUDF() AS descrip from root r where r.isComplete=false

UDF 仅是输出一些文本,所以它不包含参数。

请注意,我使用的是查询中的 JsonProperty 名称,因为它将在服务器中针对 JSON 数据进行处理。然而,通过 LINQ 查询,我会转为使用项类型的属性名称。

您会发现,在示例下载中使用的是相似的查询,尽管我的 UDF 名为 HelloUDF。

性能和可伸缩性

当谈到性能和可伸缩性时,有诸多因素在发挥作用。甚至您的数据模型和分区的设计都可影响任何数据存储的这两个关键层面。我强烈建议访问 bit.ly/1Chrjqa,阅读非常棒的 DocumentDB 数据建模指南。此篇文章阐述了图形设计和关系的优缺点,以及它们如何影响 DocumentDB 的性能和可伸缩性。作者 Ryan CrawCour 是 DocumentDB 团队的高级项目经理,他解释了哪一种模式可使读取性能受益,哪一种使编写性能受益。实际上,我发现此指南不只对 Azure DocumentDB 有用,对一般模型设计也有用。

您还应该根据您的读取和编写需要确定对数据库的分区方式。bit.ly/1y5T4FG 中有关在 DocumentDB 中对数据进行分区的文章,提供了关于使用 DocumentDB 集合定义分区以及根据您需要访问数据的方式如何定义集合方面的更多指南。

分区的另一个好处是,您可以按需要创建(或删除)更多的集合或数据库。DocumentDB 伸缩自如;即,它会自动包含资源的完整集合。

索引是另一个影响性能和 DocumentDB 的重要因素,它允许您在各集合中设置索引策略。没有索引,您只能使用资源的 SelfLink 和 ID 执行查询,就像我以前的操作那样。默认索引策略尝试在查询性能和存储效率之间寻找平衡,但是,您可以覆盖它以得到您需要的平衡。索引还是一致的,它意味着利用索引的搜索将会直接访问新的数据。在 bit.ly/1GMplDm 中阅读关于索引的更多详情。

虽不是免费,但却经济实惠

对性能和可伸缩性的管理影响的不只是数据的可访问性,它还影响提供此数据的成本。作为 Azure 产品的一部分,DocumentDB 是有代价的。有三个价位水平,由可供您选择的三个性能级别组合决定。由于 Microsoft 不断调整其服务费用,您直接访问 DocumentDB 价格详情页面 (bit.ly/1IKUUMo) 最为安全。与任何 NoSQL 数据库一样,DocumentDB 旨在为大量数据提供数据存储,因此,DocumentDB 比相关方案中的关系数据要经济实惠许多。


Julie Lerman是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她是《Programming Entity Framework》(2010) 以及“代码优先”版 (2011) 和 DbContext 版 (2012)(均出自 O’Reilly Media)的作者,博客网址为 thedatafarm.com。通过她的 Twitter(网址为 twitter.com/julielerman)关注她,并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Ryan CrawCour
Ryan CrawCour 是一名具有 20 年数据库经验的老将,他在很多年以前就开始编写首个适用于 SQL Server 4.2 的存储过程。后来经过诸多光标、连接和存储过程,他开始探索令人兴奋的 NoSQL 解决方案的自由世界。Ryan 现在与位于雷蒙德的 DocumentDB 产品团队共事。作为项目经理,Ryan 帮助塑造这款全新 NoSQL 数据库即服务的未来。