使用 Azure Cosmos DB 进行社交

生活在大规模互连的社会,这意味着有时候你也会成为 社交网络中的一部分。 我们使用社交网络与朋友、同事和家人保持联系,有时还会与有共同兴趣的人分享我们的激情。

作为工程师或开发人员,我们可能想知道这些网络如何存储数据以及如何将这些数据相互联系起来,甚至有可能被要求自行为特定的间隙市场创建或构建新的社交网络。 这时就会产生一个大问题:所有这些数据是如何存储的?

假设我们正在创建一个新型时尚的社交网络,用户可以在此网络中发布与媒体相关的文章,例如图片、视频,甚至音乐。 用户可以对帖子发表评论并打分以进行评级。 主网站登录页上将提供用户可见并可进行交互的帖子源。 这听起来似乎并不复杂(最初),但为简单起见,我们就止步于此(我们可以深入了解受这些关系影响的自定义用户源,但它超出了本文的目的)。

那么,我们如何存储此数据以及存储在何处?

你们当中许多人可能有使用 SQL 数据库的经验,或者至少对 关系建模数据 有所了解,那么你们可能会忍不住想要开始绘制类似以下图形:

说明相对关系模型的关系图

非常标准且数据结构美观... 不可扩展。

请不要误会我的意思,我的一生都在与 SQL 数据库打交道,它们的确很不错,但就像每一种模式、每一次实践以及每一个软件平台一样,并非对每一种方案都适用。

为什么在此方案中 SQL 不是最佳选择? 让我们看一下单个 post 结构,如果想要在网站或应用程序中显示文章,可能不得不执行查询... 只加入 8 个表以显示单个 post。现在请想象一下:动态地上传一系列贴子并将其显示在屏幕上。你可能明白我的意思了。

当然,我们也可以使用一个功能足够强大的超大 SQL 实例来解决数以千计的查询,其中可以使用许多这些连接来为我们提供内容,但当已经有一个更简单的解决方案存在时,我们为什么还要选择这种呢?

NoSQL 加载

本文介绍如何以经济高效的方式使用 Azure 的 NoSQL 数据库 Azure Cosmos DB 对社交平台的数据进行建模。 使用 NoSQL 方法以 JSON 格式存储数据并应用非规范化,就可以将我们以前的复杂帖子转换为单个文档

{
    "id":"ew12-res2-234e-544f",
    "title":"post title",
    "date":"2016-01-01",
    "body":"this is an awesome post stored on NoSQL",
    "createdBy":User,
    "images":["http://myfirstimage.png","http://mysecondimage.png"],
    "videos":[
        {"url":"http://myfirstvideo.mp4", "title":"The first video"},
        {"url":"http://mysecondvideo.mp4", "title":"The second video"}
    ],
    "audios":[
        {"url":"http://myfirstaudio.mp3", "title":"The first audio"},
        {"url":"http://mysecondaudio.mp3", "title":"The second audio"}
    ]
}

可以使用单个查询获得,且无需联接。 这种方法更简单且更直观,且在预算方面,它所需要的资源更少,但得到的结果更好。

Azure Cosmos DB 可确保所有属性通过其自动索引功能进行索引,此功能甚至可以进行自定义。 自由架构方法可以让我们存储具有不同和动态结构的文档,也许明天我们希望帖子上显示一系列类别或与其关联的哈希标记,我们不需要执行任何额外操作,Cosmos DB 将自行使用添加的属性处理新文档。

可以将对帖子的评论视为具有父属性的其他帖子(这可以简化我们的对象映射)。

{
    "id":"1234-asd3-54ts-199a",
    "title":"Awesome post!",
    "date":"2016-01-02",
    "createdBy":User2,
    "parent":"ew12-res2-234e-544f"
}

{
    "id":"asd2-fee4-23gc-jh67",
    "title":"Ditto!",
    "date":"2016-01-03",
    "createdBy":User3,
    "parent":"ew12-res2-234e-544f"
}

并且所有社交互动都可以作为计数器存储在单个对象上:

{
    "id":"dfe3-thf5-232s-dse4",
    "post":"ew12-res2-234e-544f",
    "comments":2,
    "likes":10,
    "points":200
}

创建源只不过是创建文档的问题,文档可按给定的相关顺序保留帖子 ID 列表:

[
    {"relevance":9, "post":"ew12-res2-234e-544f"},
    {"relevance":8, "post":"fer7-mnb6-fgh9-2344"},
    {"relevance":7, "post":"w34r-qeg6-ref6-8565"}
]

我们可以有一个“最新”流(其中帖子按创建日期排序)和一个“最热门”流(其中包括在过去 24 小时内获得了更多赞的帖子),甚至还可以基于逻辑点赞粉丝和兴趣为每个用户实现客户流,且它仍然可以是一个帖子列表。 虽然如何生成这些列表还是一个问题,但读取性能仍然不受阻碍。 一旦我们获得其中一个列表之后,我们就可以使用 IN 运算符 向 Cosmos DB 发出单个查询以一次性获取帖子的所有页面。

可以使用 Azure 应用服务的后台进程 - Web 作业 - 来构建源流。 创建一个帖子后,可以通过使用 Azure 存储队列和 Web 作业(通过 Azure Webjobs SDK 触发)触发后台处理,从而根据我们自己的自定义逻辑实现流内的帖子传播。

通过使用这种相同的技术创建最终一致性环境还可以以延迟方式处理评分和点赞。

至于关注者,则需要有更多的技巧来处理。 Cosmos DB 具有文档大小上限,而且读取/写入大型文档会影响应用程序的可伸缩性。 因此,可考虑使用以下结构,以文档形式存储关注者:

{
    "id":"234d-sd23-rrf2-552d",
    "followersOf": "dse4-qwe2-ert4-aad2",
    "followers":[
        "ewr5-232d-tyrg-iuo2",
        "qejh-2345-sdf1-ytg5",
        //...
        "uie0-4tyg-3456-rwjh"
    ]
}

这对于拥有数千位关注者的用户可能有用,但如果有名人加入我们的排名,此方法会生成大型文档,并可能最终达到文档大小上限。

为了解决此问题,我们可以使用一种混合方法。 我们可以在用户统计信息文档中存储关注者人数:

{
    "id":"234d-sd23-rrf2-552d",
    "user": "dse4-qwe2-ert4-aad2",
    "followers":55230,
    "totalPosts":452,
    "totalPoints":11342
}

“阶梯”模式和数据重复

你可能已注意到,在引用帖子的 JSON 文档中,某个用户出现了多次。 而且你猜得没错,这意味着如果应用此非规范化,则表示用户的这一信息可以显示在多个地方。

为了允许更快速地查询,我们引发了数据重复。 此负面影响的问题在于,如果通过一些操作,用户的数据发生更改,那么我们需要查找该用户曾经执行过的所有活动并对这些活动全部进行更新。 听上去不太实用,对不对?

我们将通过识别用户的“密钥”属性解决该问题。对于每个活动,我们都会在应用程序中显示此属性。 如果我们在应用程序中直观显示一个帖子并仅显示创建者的姓名和照片,那么为什么还要在“createdBy”属性中存储用户的所有数据呢? 如果对于每一条评论我们都只显示用户的照片,那么我们的确不需要关于该用户的其余信息。 在这里我称之为“阶梯模式”的模式将开始发挥作用。

我们以用户信息为例:

{
    "id":"dse4-qwe2-ert4-aad2",
    "name":"John",
    "surname":"Doe",
    "address":"742 Evergreen Terrace",
    "birthday":"1983-05-07",
    "email":"john@doe.com",
    "twitterHandle":"@john",
    "username":"johndoe",
    "password":"some_encrypted_phrase",
    "totalPoints":100,
    "totalPosts":24
}

通过查看此信息,我们可以快速检测哪些信息是关键信息,哪些不是,从而形成“阶梯”:

阶梯模式关系图

最简单的一步称为 UserChunk,这是标识用户的最小信息块并可用于数据重复。 通过减少重复数据的大小直到只留下我们将要“显示”的信息,可以降低大规模更新的可能性。

中间步骤被称为用户,这是将在 Cosmos DB 上的大多数依赖性能查询上使用的完整数据,也是最常访问和最重要的数据。 它包括由 UserChunk 表示的信息。

最复杂的一步是扩展用户。 它包括所有重要的用户信息以及并不需要快速读取的其他数据,或者它的使用情况就是最终结果(就像登录过程一样)。 此数据可以存储在 Cosmos DB 外、Azure SQL 数据库或 Azure 表存储中。

为什么我们要拆分用户,甚至将此信息存储在不同的位置? 因为,从性能角度考虑,文档越大,查询成本将越高。 保持文档精简,包含用于对社交网络执行所有依赖性能的查询的适当信息,并为最终方案(例如完整的配置文件编辑、登录名,甚至使用情况分析和大数据方案的数据挖掘)存储其他额外信息。 我们实际上并不关心用于数据分析的数据收集速度是否减慢了,因为它是在 Azure SQL 数据库上运行的,然而我们确实很在意我们的用户是否具有快速和精简的用户体验。 在 Cosmos DB 中存储的用户外观如下所示:

{
    "id":"dse4-qwe2-ert4-aad2",
    "name":"John",
    "surname":"Doe",
    "username":"johndoe"
    "email":"john@doe.com",
    "twitterHandle":"@john"
}

贴子内容如下所示:

{
    "id":"1234-asd3-54ts-199a",
    "title":"Awesome post!",
    "date":"2016-01-02",
    "createdBy":{
        "id":"dse4-qwe2-ert4-aad2",
        "username":"johndoe"
    }
}

在区块的其中一个属性受到影响的情况下进行编辑时,通过使用指向已编制索引的属性 (SELECT * FROM posts p WHERE p.createdBy.id == "edited_user_id") 的查询,然后更新这些区块,可以很容易找的受影响的文档。

基础知识

存储所有此内容(每天会不断增加)后,我们可能会思考这样一个问题:我可以使用所有来自用户的此信息流做些什么?

答案非常简单:将其投入使用并从中进行学习。

但我们可以学到什么呢? 一些简单的示例包括观点分析、基于用户首选项的内容建议,甚至自动执行的内容审查方,内容审查方可确保通过社交网络发布的所有内容对该系列均安全。

由于想要深入了解,你可能会认为自己需要更多数学科学方面的知识才能提取出简单数据库和文件之外的这些模式和信息,其实不然。

Azure 机器学习Cortana Intelligence 套件的一部分)是一个全面托管的云服务,使你可以在简单的拖放界面中使用算法创建工作流、为 R 中自己的算法进行编码,或使用部分已生成的或现有的 API(如文本分析内容审查方器建议)。

为了实现上述任何一种机器学习方案,我们可以使用 Azure Data Lake 引入不同源的信息,并使用 U-SQL 来处理信息,并生成可由 Azure 机器学习处理的输出。

另一个可用的选项是使用 Microsoft 认知服务 分析用户内容:不仅可以更好地理解它们(通过分析使用 文本分析 API编写的内容),而且还可以检测不需要或不成熟的内容,然后使用计算机构想 API解决相关问题。 认知服务包括大量不需要使用任何一种机器学习知识的现成的可用解决方案。

全球范围内的社交体验

最后,还必须说明一个非常重要的主题:可伸缩性。 在设计体系结构时,因为需要处理更多的数据和/或希望拥有更大的地理覆盖范围,所以每个组件的自行可伸缩性至关重要。 幸运的是,通过使用 Cosmos DB 完成此类复杂任务是一种统包体验

Cosmos DB 根据给定的分区键(定义为文档中的一个属性)自动创建分区,从而支持现成可用的动态分区。 必须在设计时定义正确的分区键,并记住可用的最佳做法;对于社交体验,分区策略必须与查询(需要在同一分区内进行读取)和写入(通过在多个分区上分散写入来避免“热点”)方式保持一致。 一些相关选项是:基于临时键(日/月/周)的分区,按内容类别、地理区域和用户进行划分;这一切都取决于查询数据并将其显示在社交体验中的方式。

值得一提的有趣的一点是,Cosmos DB 将以透明方式在所有分区中运行查询(包括聚合),无需在数据增长过程中添加任何逻辑。

一段时间后,最终流量会增加,资源消耗(通过 RU 即“请求单位”进行度量)也会增加。 随着用户群的增长,读取和写入操作会更加频繁,用户将开始创建和读取更多内容;缩放吞吐量的能力至关重要。 增加 RU 非常容易,可以通过在 Azure 门户中单击几次或通过 API 发出命令来实现。

扩展和定义分区键

如果平台的发展越来越好,来自其他国家/地区或洲的用户注意到你的平台并开始使用它,这是一件多么让人惊喜的事!

但是你很快会意识到他们在平台的体验并不理想;他们与运营区域相距太远,延迟问题非常严重,你显然不希望他们因此退出平台。 如果有一种简单的方法可以扩展全球覆盖范围就好了······确实有!

通过 Cosmos DB,只需单击数次即可通过透明方式全局复制数据,并从客户端代码中自动选择可用区域。 这也意味着可以拥有多个故障转移区域

全局复制数据时,需确保客户端可以利用该数据。 如果要使用 Web 前端或从移动客户端访问 API,则可以部署 Azure 流量管理器并在所有所需区域克隆 Azure 应用服务(方法是通过使用性能配置支持扩展的全球覆盖范围)。 客户端访问前端或 API 时,将被路由到最近的应用服务,而该应用服务将连接到本地的 Cosmos DB 副本。

将全球覆盖范围添加到社交平台

结束语

本文尝试说明一种完全在 Azure 上创建具有低成本服务社交网络,并可通过鼓励使用多层存储解决方案和称为“阶梯”的数据分布得到良好结果的替代方法。

社交网络中各 Azure 服务之间的交互关系图

事实上,对于此类方案并没有万能方法,而需结合各种卓越的服务共同创建,才能提供绝佳的体验:Azure Cosmos DB 的速度和自由性,可用于提供绝佳的社交应用程序;一流搜索解决方案后的智能操作,如 Azure 搜索;Azure 应用服务的灵活性,不仅可以托管与语言无关的应用程序,甚至还可以托管功能强大的后台进程;Azure 存储和 Azure SQL 数据库的可扩展性,可用于存储大量数据;Azure 机器学习的分析功能,可创建能够为我们的进程提供反馈,并且有助于我们向合适的用户提供合适的内容的知识和智能。

后续步骤

若要详细了解 Cosmos DB 用例,请参阅常见 Cosmos DB 用例