Azure Cosmos DB 是 Azure 提供的一个分布式 NoSQL 数据库,Cosmos DB 提供一定的关系型数据库的能力,并且可以无缝地扩容。

Azure 提供了 30 天免费的试用时长,可以注册一个 Azure 帐号体验一下。该帐户中获得前 1000 RU/s 的免费吞吐量和 25 GB 的免费存储。

Cosmos DB 和其他非关系型数据库区别

Cosmos DB 是微软提供的多区域分布式的数据库,可以根据需要自动缩放吞吐量,自动扩容。

Cosmos DB 的优势

  • 多地区,可以在全球范围能有不错的响应时间,Cosmos DB 可以自行复制副本,用户可以与最近的副本进行交互
  • 高吞吐和弹性存储,透明水平分区和多主数据库复制,任何区域内,只需发出一次 API 调用,即可将每秒数千个请求弹性扩容到数百个请求,用户只需为实际使用的吞吐量和存储付费
  • 一致性,用户无需关心多个地区的数据一致性问题
  • 用户无需关心索引,数据库架构,Cosmos DB 会自动为所有数据构建索引
  • SLA 可用性保证,Azure 托管 Cosmos DB,用户不需要管理和操作复杂的多数据中心和部署软件的升级
  • 支持多种数据库模型
    • SQL
    • MongoDB
    • Cassandra

Azure Cosmos DB 账户结构

  • 一个数据库账户下面有零个或多个数据库
  • 一个数据库下面有零个或多个容器
  • 一个容器包括零个或多个项
    • 容器用于存储数据
    • 创建容器时需要提供分区键,文档中选择的要存储的属性,用于路由到要写入,更新或删除的分区
    • 容器会实现集合,表,图等

下面就重点对 Cosmos DB 中的每个概念介绍一下

Azure Cosmos DB databases

在 Azure Cosmos DB 中数据库类似于 namespace。数据库是一组 container。

Azure Cosmos DB containers

Azure Cosmos DB container (容器)就是数据真正存放的地方。和关系型数据库不同的是,当发生扩容的时候,不是扩展 VM 的存储空间, Cosmos DB 会横向扩展。数据会存储在一台或多肽服务器中,称为分区(partitions)。为了增加吞吐量或存储,需要添加更多的分区。这为容器提供了无限的吞吐量和存储。当创建容器的时候,需要添加分区键(partition key)。这个分区键需要从文档的属性中选择。该属性将用于将数据路由到要写入、更新或删除的分区。也可以被用于 WHERE 查询子句,以实现高效的数据检索。

Cosmos DB 中数据的基础存储机制称为物理分区,可以拥有高达 10000 RU/s 和多大 50 GB 的数据。Azure Cosmos DB 使用可存储多达 20 GB 数据的逻辑分区对此进行抽象。随着添加更多分区,逻辑分区允许服务为基础物理分区上的数据提供更大的弹性和更好的管理。 若要详细了解分区和分区键,请参阅将数据分区

创建容器的时候,会需要配置吞吐量:

  • 专用吞吐量:专门留给该容器使用
    • 有两种类型的专用吞吐量:标准和自动缩放
  • 共享吞吐量:
    • 在数据库级别指定,与数据库中最多 25 个容器共享。

容器与 Schema 无关,容器中的项可以具有任意 Schema 或不同的实体,只要它们共享相同的分区键。默认情况下,容器中的所有数据都会自动编制索引,无需显式索引。

容器中的数据必需具有每个逻辑分区键值唯一的 id

Azure Cosmos DB 容器的属性

Cosmos DB 容器具备一组系统定义的属性。这些属性值会根据不同的 API 附加在数据上,比如常见的:

  • _rid,容器唯一标识符
  • _etag,用于乐观并发控制的实体标记
  • _ts,容器上次更新的时间戳
  • _self,容器的可寻址 URI
  • id,用户配置,容器的名字
  • indexingPolicy,用户可配置,提供更改索引的功能
  • TimeToLive,用户配置,从容器自动删除
  • changeFeedPolicy,用户配置,用于读取对容器中的项所做的更改
  • uniqueKeyPolicy,用户配置,确保逻辑分区中一个或多个值的唯一性
  • AnalyticalTimeToLive,用户配置,在设置的时间段后从容器中自动删除功能

Azure Cosmos DB items

数据在 Cosmos DB 中表现为一个项,集合中的一个文件,表格的一行,或者图形中的一个节点或边缘。

item 的属性

  • _rid,项的唯一标识符
  • _etag,乐观并发控制的实体标记
  • _ts,上次更新的事件戳
  • _self,项的可寻址 URI
  • id,逻辑分区中用户定义的唯一名称

建模和分区

嵌入数据

在关系型数据库中,通常是将数据规范化,将数据规范化通常就是将数据拆分成不同的不同的实体。比如一个人的信息可以拆分成联系人,多条地址记录,多条联系人记录等等。通常可以通过类型来进一步区分,比如地址可以是家庭地址,或工作地址等。

规范数据的前提是为了避免冗余记录。而在 Cosmos DB 中可以使用 嵌入的方式,将此人的相关信息嵌入到一个文档中。

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "addresses": [
        {
            "line1": "100 Some Street",
            "line2": "Unit 1",
            "city": "Seattle",
            "state": "WA",
            "zip": 98012
        }
    ],
    "contactDetails": [
        {"email": "[email protected]"},
        {"phone": "+1 555 555-5555", "extension": 5555}
    ]
}

这样在创建或更新此人的信息时都是单个写入操作。

什么时候使用嵌入

通常是如下的场景:

  • 实体之间存在包含关系
  • 实体之间存在一对多关系
  • 嵌入的数据不经常更改
  • 嵌入 ude 数据在未绑定的情况下不会增长
  • 嵌入的数据会频繁地统一查询

什么时候不嵌入

如果文档中有一个无限制增长的数组,那么最好不要嵌入此数组。

可以考虑将数组拆分开,并在数组的元素中加入原始引用的 ID。

引用数据

通过在文档中包含 ID 的方式来完成引用。

比如下面的文档设计为一个人的股票持仓。

Person document:
{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        { "numberHeld":  100, "stockId": 1},
        { "numberHeld":  50, "stockId": 2}
    ]
}

Stock documents:
{
    "id": "1",
    "symbol": "zbzb",
    "open": 1,
    "high": 2,
    "low": 0.5,
    "vol": 11970000,
    "mkt-cap": 42000000,
    "pe": 5.89
},
{
    "id": "2",
    "symbol": "xcxc",
    "open": 89,
    "high": 93.24,
    "low": 88.87,
    "vol": 2970200,
    "mkt-cap": 1005000,
    "pe": 75.82
}

这种方法的缺点就是当要显示一个人的投资组合的时候,应用程序需要多次访问数据库来加载每个股票的信息。

什么时候使用引用

  • 表示一对多关系。
  • 表示多对多关系。
  • 相关数据频繁更改
  • 引用的数据可能没有限制

尽量避免使用可能较大的可变即集合。而通过将 ID 分散在单个文档中的方式来保存关系。

多对多关系

在关系型数据库中如果要处理多对多的关系,通常是引入一张关系表,保存 ID 对 ID 的关系。

在文档数据库中,可以通过分别在实体中冗余关系的方式来记录多对多的关系。

比如作者,作者图书关系,图书,三个实体之间的联系。一本书可能有多个作者,而一个作者也可能写很多本书。

这个时候就可以考虑在作者的文档中加入 books 数组,在图书的文档中加入 authors 数组来表示。这样就可以不用关系表来保存多对多的关系了。同时也可以减少应用程序需要访问服务器的次数。

创建样例数据

可以使用 New item 菜单添加新的 item

{
    "id": "1",
    "category": "personal",
    "name": "groceries",
    "description": "Pick up apples and strawberries.",
    "isComplete": false
}

其中的 id 为数据的唯一 ID。

Cosmos DB 常用查询语法

最基本的查询语法,比如查询全部文档

SELECT * FROM c

指定顺序

SELECT * FROM c ORDER BY c._ts desc

查询条件

TTL

TTL,生存时间,Cosmos DB 能够在一段时间之后自动将项从容器中删除。可以在容器级别设置 TTL,系统会基于 TTL 值自动删除过期的项,不需要客户端应用程序显式的发出删除请求。TTL 的最大值是 2147483647 秒,大约 24855 天或 68 年。

删除过期项是一个后台任务,使用剩余的请求单元,即用户请求没有使用的请求单元。TTL 过期后,如果容器出现请求过载的情况,并且没有足够的 RU 使用,也会延迟数据删除操作。但是任何查询都不会通过接口返回 TTL 过期后的数据。

  • 容器的生存时间 (DefaultTimeToLive
    • 缺失,项不回自动过期
    • 如果设置为 -1,默认情况下,项不回过期
    • 如果设置为非零数字,项将在上次修改后 n 秒后过期
  • 项的生存时间 (ttl
    • 仅当父容器的 DefaultTimeToLive 存在且不是设置为 null 时,此属性适用
    • 如果存在,将替代父容器的 DefaultTimeToLive

reference