vlambda博客
学习文章列表

通过 Azure SQL 上的 Microsoft Advertising 平台进行学习

Microsoft Advertising 是一个功能强大的广告平台,可帮助您将您的广告精准投放给正确的客户。在 2019 财年,搜索广告收入为 Microsoft 带来了 76 亿美元的收入。自 2015 年以来,我们一直在使用 Azure SQL,回顾过去真是令人难以置信:我们的可用性、性能、可伸缩性、可靠性和效率都得到了极大的提高。Azure SQL 一直是 Microsoft Advertising 平台赖以吸引我们数百万客户并完成数十亿美元业务的基础。在这篇博客中,我们将分享我们如何在 Azure 上重新设计了我们的系统,如何利用众多令人兴奋的 Azure SQL 功能,以及随着 Azure SQL 的持续提供创新和卓越表现,Microsoft Advertising 平台如何实现蓬勃发展


我们把时间快速退回到 5 年前。那时候,我们在本地运行 Microsoft Advertising 系统。业务的高速增长导致数据呈爆炸式增长,每秒快速查询量 (QPS) 增长并且业务需求日趋复杂。作为一个多租户系统,无论帐户大小如何,我们都必须良好应对。但本地数据库系统无法进行扩展并满足不断增长的需求。最重要的是,大家都知道 SAN 存储很难升级、维护和维修。运营成本不断增加。


01
重新构建数据层架构

我们意识到我们需要彻底改变架构。我们的设计理念是将特定帐户的数据分布到一组数据库服务器上。这样,数据的分布变得更加均匀,并且可以同时从多个数据库中提取服务。我们看中了 Azure SQL 的功能、灵活性和可操作性,选择了它作为我们的平台。


使用 Azure SQL,我们可以快速轻松地添加/删除实例。它可以提供 ACID 事务处理,这对我们的应用情形很重要。我们实施了水平分区以扩展数据,这种技术称为分片;对于特定的客户,数据平均分布在 N 个分片上。在我们的实现中,这 N 个分片构成了我们所谓的分片组。随着每个分片组中数据量的增大或缩小,我们在现有分片组之间进行拆分或合并。我们的设计原则之一是,无论每个帐户大小如何,都对其进行分片,并且代码和分片逻辑之间没有耦合,服务代码假定数据库可以 N 种方式分片。

ACID
https://en.wikipedia.org/wiki/ACID
分片
https://en.wikipedia.org/wiki/Shard_(database_architecture)


通过 Azure SQL 上的 Microsoft Advertising 平台进行学习

Google F1 设计的启发,我们将数据库数据组织结构更改为类似于下图的层次结构.

Google F1
https://research.google/pubs/pub41344/

通过 Azure SQL 上的 Microsoft Advertising 平台进行学习

通过这种数据表示,属于客户的广告组级别数据很可能会在物理存储中被群集在一起。在我们的用例中,由于可以从磁盘上的一组连续页面中检索 CustomerId 的数据而无需联接其他表,因此有意在子表中引入受控的冗余并复制子表中的关键列提高了我们的性能。因此,SQL 需要从磁盘加载的页面更少,从而获得更好的性能。这种技术对我们非常有效,使我们能够实现延迟目标。


02
重新构建服务层架构

由于数据层已经发生了巨大的变化,数据现在已采用分布式形式,因此我们也必须大力改变服务层。


首要原则是在设计时要考虑横向扩展。收到请求时,无论哪个服务器接收到该请求,都将成为该请求的主服务器。当主服务器估计增加的并行度将减少查询处理延迟时,它将选择分布式执行而不是集中式执行。在分布式执行的情况下,它将请求分发给其对等方,等待响应,然后合并结果。然后将最终的合并结果发送给客户。

通过 Azure SQL 上的 Microsoft Advertising 平台进行学习

我们还决定,业务逻辑应尽可能驻留在服务中,而不是驻留在存储层中。这种模式非常适合我们的大规模数据处理用例。对于我们的多租户需要大量数据处理的方案,投掷大量便宜的机器来执行业务逻辑和处理已证明是非常有益的。


最后,由于我们的服务需要同时连接到许多数据库,因此我们与开发团队制定了新的编程范例。我们构建了负责处理 SQL 数据库连接重用和生命周期管理的库代码。我们告诉开发人员避免串行读取,使用批处理,使用 IDataReader 并行流式传输数据以仅进行正向处理(与使用 DataSet 和 DataTable 相对)。根据我们的经验,使用 DataTable 会降低性能,特别是在高吞吐量数据处理的情况下。因此,我们转向构建纯内存中的 C# 对象,而不是依靠 DataTable 进行内存中联接和数据处理。为了最大化性能,我们还避免使用任何对象关系映射 (ORM) 库,而是编写特定于场景的代码(通常使用并行运行的任务)来联接和处理所有数据。我们在整个堆栈中使用 async,以避免在等待 I/O 时出现不必要的线程池阻塞。


03
利用 Azure SQL 的新功能

Azure SQL 提供了许多很棒的功能,我们对此深表感谢。下面列举其中几个:

  • Columnstore 帮助我们针对数据不经常更改的分析和审计情形优化存储。使用 Columnstore 而不是 Rowstore 表可以帮助我们节省 30% 到 40% 的空间,并且由于采用了批处理操作模式而提高了性能。对存档数据使用存档压缩使我们能够节省另外 30% 的空间,而不会对性能产生明显影响。

  • 对于不适合使用 Columnstore 索引的表,页面压缩可帮助我们在应用场景中省高达 50% 的空间,而不会带来任何明显的性能降级。

  • 表分区,将它与常规索引维护结合使用,可帮助我们控制空间消耗。

  • AlwaysOn 可用性组活动异地复制长期备份保留 (LTR) 是非常好的保持业务连续性的功能。

  • DNS 别名提供了一个转换层,可以将客户端应用程序重定向到其他服务器。在灾难恢复应用情形中,这非常方便。

  • DMVXEvents 查询存储 (QDS) 可提供非常出色的性能故障排除功能。

Columnstore
https://docs.microsoft.com/sql/relational-databases/indexes/columnstore-indexes-overview
页面压缩
https://docs.microsoft.com/sql/relational-databases/data-compression/page-compression-implementation
表分区
https://docs.microsoft.com/sql/relational-databases/partitions/partitioned-tables-and-indexes
AlwaysOn 可用性组
https://docs.microsoft.com/azure/sql-database/sql-database-high-availability
活动异地复制
https://docs.microsoft.com/en-us/azure/sql-database/sql-database-active-geo-replication
长期备份保留 (LTR)
https://docs.microsoft.com/azure/sql-database/sql-database-long-term-retention
DNS 别名
https://docs.microsoft.com/azure/sql-database/dns-alias-overview
DMV
https://docs.microsoft.com/azure/sql-database/sql-database-monitoring-with-dmvs
XEvents
https://docs.microsoft.com/azure/sql-database/sql-database-xevent-db-diff-from-svr
查询存储 (QDS)
https://docs.microsoft.com/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store


在 Azure SQL 上运行的 Microsoft Advertising 系统在支持客户不断增长的参与方面也表现良好。尽管这些年来负载显著增加,但我们仍然达到并超过了性能 SLA。我们的客户对此十分满意。我们并没有就此止步,而是坚持不懈地致力于提高系统效率。Azure SQL 具有很好的灵活性,在它上面花钱可以购买一些性能,但是我们认为从我们的代码中提升性能比花大笔钱要有趣得多。那么,我们如何支付尽可能少的费用却又能获得最高的性能呢?最好的技术就是改进我们的代码使它变得更好,这样即使在便宜的 Azure SQL 上运行速度也能足够快。我们的系统从 P11 开始,然后降级到 P6;现在,在我们对代码进行了相当大的改进之后,我们在 P4 上运行 OLTP 系统。此外,我们使用以下方法来充分利用 Azure SQL 的所有强大功能。


04
完全利用只读副本

许多开发人员没有意识到,一旦为高级/业务关键数据库设置了单个异地副本,我们实际上就可以查询四个数据库副本 — 主数据库、主数据库的本地辅助数据库、异地辅助主数据库和异地辅助主数据库的本地辅助数据库。


Microsoft Advertising 中的一种工作负载具有两种不同的查询模式:交互式 UI 调用(QPS 低,但具有严格的延迟要求)和报告调用(对于大量数据而言 QPS 高,但是具有较低的延迟要求)。为了避免这两种类型的调用相互干扰,我们曾经还托管额外的副本,而事实证明这样做的成本非常高。本地辅助功能被证明是可以“救命”的。我们设计了一种新方案:将通过 UI 进行的调用同时转到两个副本:主副本 + 异地辅助主副本,哪个先返回便使用哪个。报表调用也会同时转到两个副本:主本地辅助副本 + 异地辅助本地辅助副本,哪个先返回便使用哪个。这样一来,我们将 Azure SQL 的这部分成本减少了一半。要注意的一件事是,有时主副本和辅助副本之间会存在复制延迟,因此请确保您的应用场景可以容忍和处理这种延迟。


05
定期清理数据以保持整洁

为了降低 Azure SQL 成本并确保出色的查询性能,对于我们来说最重要的是开发一个稳健的数据清理基础架构,用于定期清理已删除的实体和老旧审核数据。清理需要占用大量 CPU 和 I/O,因为需要检查所有表。通过大量的调整,我们现在已经能够优化清理操作,使其对系统的影响减至最小。我们利用 Azure SQL 的弹性作业来清理数据。


06
负载平衡

负载均衡器是一项基础架构功能,可帮助我们将数据均衡分布到各数据库服务器。目的是消除热点并分散客户数据,避免增长高峰。它以在线方式将客户数据从繁忙的分片组移动到更空闲的分片组,此操作对客户的影响非常小。仅当客户活动低于阈值时才锁定和切换分区,从而将对客户的影响减至最小。如果客户仍在进行大量更改,则负载均衡器将退出并重试。它会持续运行,确保将我们的数据尽可能高效地打包。


07
结论

Azure SQL 是云中全托管 SQL 的最佳选择。Microsoft Advertising 非常高兴在这个梦幻般的平台上运行并实现蓬勃发展。目前,我们正在测试最新的炫酷技术 — Hyperscale!它支持高达 100 TB 的存储,近乎即时的数据库备份,快速扩展等等。利用迁移到 Hyperscale 服务层的数据库,我们已经看到了显著的性能改进。我们将很快完成其余数据库的迁移。




通过 Azure SQL 上的 Microsoft Advertising 平台进行学习