在 Azure 云服务上设计大规模服务的最佳实践

更新时间: 2014年10月

作者:Mark Simms 和 Michael Thomassy

供稿作者:Jason Roth 和 Ralph Squillace

审阅者:Brad Calder、Dennis Mulder、Mark Ozur、Nina Sarawgi、Marc Mercuri、Conor Cunningham、Peter Carlin、Stuart Ozer、Lara Rubbelke 和 Nicholas Dritsas。

云计算是一种分布式计算,而分布式计算所需要的是缜密的规划和传递,而与平台选择无关。本文档的目的是基于真实的客户案例提供一个成熟的指导方针,以方便客户在 Azure 和 SQL Database 上构建可扩展的应用程序,以及实施平台即服务 (PaaS) 方案,在这种方案中需要借助 Web 角色和辅助角色将应用程序构建成为 Azure 云服务。

Important重要提示
注意:本文中的所有最佳实践指导均源自与在 Azure 上运行生产代码的客户之间的深入合作。本文仅讨论基于 SDK 1.6 版本的 Azure 云服务 (PaaS) 平台,而不涵盖将来的功能,如 Azure 网站Azure 虚拟机 (IaaS)。

本文档介绍有关构建 Azure 应用程序的基础设计概念、关键的 Azure 平台功能、限制和特性,以及使用核心 Azure 服务的最佳实践。重点介绍那些适合松散一致的分布式数据存储区的应用程序(与严格一致或高密度的多租户数据模型相对)。

出于多种原因,将应用程序和服务的一些环节转换为 Azure 可能很有吸引力,例如:

  • 节省资本支出或将资本支出合并为运营支出(资本支出至运营支出)。

  • 通过更密切地匹配需求和容量来降低成本(并提高效率)。

  • 通过减少或消除基础结构障碍来提高灵活性和缩短上市时间。

  • 提高受众对新市场(如移动设备)的接触面。

  • 通过构建新的应用程序,使它们通过地理上分散的数据中心支持全球受众,从而因云计算的巨大规模而受益。

从技术方面而言,也有许多极好的理由,要求我们开发新的应用程序或将现有应用程序的某些部分或全部内容移植到 Azure。由于环境具有许多可选的实现方法,因此,你必须仔细地评估你具体的应用程序模式,以便选择正确的实现方法。一些应用程序非常适合 Azure 云服务(这是平台即服务或 PaaS 方法),而其他一些应用程序可能受益于部分或完整的基础结构即服务(或 IaaS)方法,如 Azure 虚拟机。最后,结合使用这两者可能能够最好地满足某些应用程序的要求。

你的应用程序应具有以下三个关键环节的一个或多个,以便最大限度地发挥 Azure 云服务的优势。(并非所有这些环节都需要出现在你的应用程序中;一个应用程序可能只需以下这些环节之一就能够非常充分地利用 Azure,从而带来巨大的投资回报。但是,如果工作负荷没有展现任何这些特性,则它可能并不是非常适合 Azure 云服务。)

评估应用程序时应考虑的重要环节包括:

  • 弹性需求。过渡到 Azure 的重要价值主张之一是弹性扩展:能够在应用程序中添加或删除容量(缩小和放大)以更密切地匹配动态用户需求的特性。如果你的工作负荷具有静态、稳定的需求(例如,静态的用户数和交易数等),则不能最大限度地发挥 Azure 云服务的这一优势。

  • 分布式用户和设备。在 Azure 上运行使你能够即时访问应用程序的全球部署。如果你的工作负荷是一个在单一位置(如单个办公室)开展工作的固定用户群,则云部署可能无法提供最佳的投资回报。

  • 可分区工作负荷。通过向外扩展,进而在更小的块区中添加更多容量,云应用程序可自如扩展。如果你的应用程序依赖于向上扩展(例如,大型数据库和数据仓库)或者是专业化、专用的工作负荷(例如,大型、统一的高速存储),则必须对其进行分解(分区),以便使在云中运行向外扩展的服务变得可行。根据工作负荷的大小,这一过程可能并不那么简单。

重申一遍:当评估你的应用程序时,如果你的工作负载仅具有在平台即服务环境(如 Azure 云服务)中起重要作用的上述三个环节之一,则过渡到 Azure 云服务或在此类服务之上构造应用程序会带来较高的投资回报。如果应用程序具有所有这三个特性,则你将有望获得非常高的投资回报。

尽管为 Azure 设计应用程序的许多环节与内部开发非常相似,但在基础平台和服务的行为方面存在若干关键差异。了解这些差异进而了解如何根据(而不是针对)平台进行设计,对于交付能够在云中实现弹性扩展的应用程序而言是至关重要的。

这一部分概括了五个关键概念,这些概念对于为平台即服务 (PaaS) 环境(如 Azure 云服务)构建大规模、广泛分布的向外扩展应用程序而言是至关重要的设计点。了解这些概念将帮助你设计和构建满足以下要求的应用程序:它们不仅在 Azure 云服务上运行,还能在此类服务上发展壮大,从而使你的投资获得尽可能高的回报。本文档后面讨论的所有设计注意事项和选择都会与这五个概念之一有密切联系。

此外,值得注意的是,尽管可以从 .NET 应用程序的角度来查看其中许多注意事项和最佳实践,但基础概念和方法大体上是不区分语言或平台的。

从内部应用程序转向 Azure 云服务的主要变迁与应用程序如何扩展密切相关。构建较大型应用程序的传统方法依赖于将向外扩展(无状态的 Web 和应用程序服务器)与向上扩展(购买较大的多核心/大内存系统、数据库服务器以及构建较大型的数据中心等)相结合。在云中,向上扩展是一个不太现实的选项;实现真正可扩展的应用程序的唯一途径是明确设计向外扩展

随着内部应用程序的许多元素已适于进行向外扩展(Web 服务器、应用程序服务器),挑战在于如何确定应用程序中依赖于向上扩展服务的环节,并将其转换(或映射)到向外扩展实现。向上扩展依赖性的主要候选元素通常是关系数据库 (SQL Server/Azure SQL Database)。

传统的关系设计主要围绕着全局连贯的数据模型(单一服务器向上扩展)以及严格的一致性和事务行为。针对此支持存储区,传统的扩展方法一直是“使所有元素都变得无状态”,并将管理状态的责任推延到向上扩展 SQL 服务器。

不过,SQL Server 的可扩展性虽然很强大,但是缺乏真正灵活的扩展。也就是说,它无法提供响应性极高的资源可用性,而是必须购买较大的服务器以及执行代价高昂的迁移阶段,使容量大大超过需求,并且每次都要扩大向上扩展的规模。此外,当扩展以前的中等规模硬件时,成本曲线呈指数上升。

而当 Azure 云服务的基础结构进行扩展时,应用程序需要设计为使用向外扩展数据存储区。这样就会引发一些设计难题,例如,显式将数据分区到较小的块区(每个块区适合一个数据分区或向外扩展单元)以及管理分布式数据元素之间的一致性。这种通过分区实现扩展的方法,避免了设计为向上扩展的许多缺点。

从众所周知的向上扩展方法转向向外扩展数据和状态管理,通常是针对云进行设计的最大障碍之一;解决这些难题并设计应用程序以充分利用 Azure 云服务和 Azure SQL Database 的可扩展、灵活的功能来管理持久数据,这是本文档中大量内容的焦点。

当你运行自己的数据中心时,你具有几乎无限的控制度,同时具有几乎无限的选择。从实际场所(空调、供电、空间)到基础结构(机架、服务器、存储设备、网络等)的任何事物,直至配置(路由拓扑、操作系统安装),一切都在你的控制之下。

这种控制度也带来了各方面的成本 – 资金、运营、人力和时间。在灵活和不断变化的环境中管理所有细节的成本是转向虚拟化的过程的核心,也是转向云的征程中的一个关键环节。作为对放弃控制度的回报,这些平台降低了部署、管理的成本并提高了灵活性。它们所产生的约束在于:可用组件和服务的规模(容量、吞吐量等)限制在一定量之内。

打个比方来说,商业散装货运主要依赖于集装箱。这些集装箱可以装在各种运输工具(轮船、火车和卡车)上,并且有各种标准尺寸(最长为 53 英尺)。如果你要装运的货物量超过了最大拖车的容量,则你需要:

  • 使用多个拖车。这就涉及到将货物拆分(或分区)以适合装入不同的集装箱并协调拖车安排。

  • 使用特殊的装运方法。对于无法拆分到多个标准集装箱的货物(太大、过重等),则需要使用高度专业化的方法(如驳船)。这些方法通常要比标准货物装运昂贵得多。

从这个比方可以看出,在 Azure(通常是云计算)中,每种资源都有一个限值。无论是单独的角色实例、存储帐户、云服务或甚至是数据中心 – Azure 中的每个可用资源都具有某种确定的限制。有些可能是非常大的限制,如数据中心内可用的存储空间量(类似于最大的驳船可装运超过 10,000 个集装箱),但它们也是有限的。

有了这个概念后,扩展的方法将是:对负载进行分区,然后拆分组合到多个扩展单元(多个 VM、数据库、存储帐户、云服务或数据中心)。

在本文中,我们使用术语“扩展单元”来指示满足以下条件的一组资源:(a) 处理一定程度的负载;(b) 组合在一起来处理附加负载。例如,一个 Azure 存储帐户的最大大小为 100 TB。如果你需要存储超过 100 TB 的数据,你将需要使用多个存储帐户(也即,至少两个存储扩展单元)。

后面的章节将讨论 Azure 的每个核心服务或组件的常规容量设计指南,以及用于组合这些服务以实现附加扩展的建议方法。

在内部构建高度灵活的应用程序已投入了大量的时间、精力以及智力资本。通常,这归根结底是将应用程序拆分为低状态组件(应用程序服务器、网络)和高状态组件(数据库、SAN),并使每个组件可灵活地应对故障模式。

在这一上下文中,故障模式指的是以下两者的组合:(a) 观察到系统处于故障状态;(b) 故障原因所导致的结果。例如,由于错误地配置了密码更新而导致数据库无法访问就是故障模式;故障状态是无法连接(拒绝连接,不接受凭据),而故障原因是无法与应用程序代码正确通信的密码更新。

低状态组件借助于集成到由外部系统管理的系统,通过松散偶合的冗余性来提供灵活性。例如,在负载平衡器之后放置其他 Web 服务器;每个 Web 服务器与其他 Web 服务器完全相同(使添加新容量变成只是克隆基本 Web 服务器映像的过程),并集成到由负载平衡器管理的整体应用程序中。

而高状态组件则借助于集成到由组件之间紧密管理的系统,通过紧密偶合的冗余性来提供灵活性。以下是一些示例:

  • SQL Server。要添加一个冗余的 SQL Server 实例作为主动/被动群集的一部分,要求谨慎地选择兼容(也即完全相同!)的硬件和共享的存储设备(如 SAN),以便在多个节点之间提供事务上一致的故障转移。

  • 供电。提供冗余的电力是一个非常复杂的示例,这要求多个系统协同发挥作用,以缓解本地(一台服务器多个电源,板载硬件在主电源和辅助电源之间切换)与中心(备用发电机以防断电)范围的问题。

基于紧密耦合方法的灵活性解决方案本身就比松散耦合的“添加更多克隆项”的方法更昂贵,因为前者要求经过良好培训的人员、专门的硬件以及仔细的配置和测试。这不仅难以实现,而且实现起来也成本高昂。

这种专注于确保硬件平台高度灵活的方法也可称为“钛金蛋壳”。为了保护蛋里的内容,我们用一层坚固(但昂贵)的钛金覆上了一层壳。

大范围运行系统的经验(有关进一步的讨论,请参阅 http://www.mvdirona.com/jrh/TalksAndPapers/JamesRH_Lisa.pdf)表明,在任何足够大的系统(如处于 Azure 范围内的数据系统)中,物理上处于运动状态的部件总数导致系统的某些部分始终处于易于破坏的状态。Azure 平台的设计已规避了这一局限性,这种设计不是消除这种局限性,而是依赖于从节点级的故障事件中自动恢复。这种设计意图贯穿所有核心的 Azure 服务,它对于设计可使用 Azure 可用性模型的应用程序而言至关重要。

转换到 Azure 后,灵活性会话从基础结构冗余性挑战变为服务冗余性挑战。对内部可用性计划起支配作用的许多核心服务在 Azure 中“仅仅维持正常工作”:

  • SQL Database 自动为你的数据维护多个事务上一致的副本。数据库的节点级故障自动故障转移到一致的辅助副本;请将这种体验的便利性与提供内部灵活性所需的时间和费用进行对比。

  • Azure 存储自动维护数据的多个一致的副本(要了解详情,请参阅 http://sigops.org/sosp/sosp11/current/2011-Cascais/11-calder-online.pdf)。存储卷的节点级故障自动故障转移到一致的辅助副本。对于 SQL Database,请将这种完全托管的体验与在内部群集或 SAN 中直接管理灵活存储进行对比。

但是,这一部分的主旨是可用性,而不是灵活性。灵活性只是在 SLA 的范围内持续向用户交付价值的总体过程的一部分。如果服务的所有基础结构组件运行状况均正常,但该服务无法处理预期的用户量,则此服务不可用,也无法交付价值。

以移动设备或社交为中心的工作负荷(如公共 Web 应用程序以及移动设备应用程序)与面向固定受众的工作负荷相比,前者的动态程度要高得多;这种工作负荷要求采用更高级的方法来处理大量突发事件和峰值负载。本文档通篇介绍为 Azure 应用程序设计可用性时要记住的关键概念,这些概念基于以下要点:

  • Azure 中的每个服务或组件都提供了某种服务级别协议 (SLA);这种 SLA 可能并不与运行你的应用程序所需的可用性指标直接关联。了解系统中的所有组件、其可用性 SLA以及它们的交互方式对于理解可提供给用户的总体可用性至关重要。

    • 避免出现将导致 SLA 降级的单点故障,如单实例角色。

    • 组合或回退到多个组件,以减轻因特定设备脱机或不可用而导致的影响。

  • Azure 中的每个服务或组件都可能遇到故障:要么是短期临时事件,要么是长期事件。你的应用程序应编写为能够从容地应对故障。

    • 对于临时故障,提供适当的重试机制以重新连接或重新提交工作。

    • 对于其他故障事件,提供丰富的检测机制来处理故障事件(向运营部门报告错误),并向用户返回适当的错误消息

    • 如果可能,可以回退到其他服务或工作流。例如,如果将数据插入 SQL Database 的请求失败(由于非临时原因,如架构无效),则以序列化格式将数据写入 Blob 存储区。这样,就可以持久捕获数据,并在解决架构问题之后将数据提交到数据库。

  • 所有服务都具有一个峰值容量,要么显式指定(通过限制策略或峰值负载渐近线),要么隐式指定(达到系统资源限制)。

    • 将你的应用程序设计为在达到资源限制时从容降级,并采取适当的措施减轻对用户的影响。

    • 实施适当的退让/重试逻辑,以避免对服务产生“护航”效应。如果没有适当的退让机制,则在经历峰值事件之后,下游服务将始终没有机会赶上正常步伐(因为应用程序将持续尝试将更多负载推进服务中,从而触发限制政策或资源枯竭)。

  • 可能经历快速突发事件的服务通常需要通过舍弃功能来处理超过其峰值设计负载的情况。

    • 就像人体在极冷情况下会限制血液流向末端一样,将你的服务设计为在极端负载事件期间舍弃不太重要的服务。

    • 此时的必然结果是:你的应用程序所提供的所有服务不能都具有同等的业务重要性,它们可以遵从不同的 SLA。

这些高级概念将在描述核心 Azure 服务的每个章节中详细说明,同时还有适用于每个服务或组件的可用性目标以及如何针对可用性进行设计的建议。请记住,数据中心仍然是大型应用程序的单一故障点;从电源错误(请在此处查看示例)到系统错误(请在此处查看示例),基础结构和应用程序问题都会导致数据中心崩溃。要求最高级别正常工作时间的应用程序应部署在多个冗余的数据中心内,但这种情况极为罕见。

在多个数据中心部署应用程序要求若干基础结构和应用程序功能:

  • (基于地理位置、用户分区或其他关联逻辑)将服务的用户路由到适当的数据中心的应用程序逻辑。

  • 在数据中心之间以适当的延迟和一致性级别同步和复制应用程序状态。

  • 自主部署应用程序,以便最大限度降低数据中心之间的相互依赖(也即,避免数据中心 A 的故障导致数据中心 B 出现故障的情况)。

关于可用性,(在数据中心丢失的情况下)提供灾难恢复解决方案需要大量的时间、精力和资金。这一部分将集中讨论在面对系统故障和数据丢失(无论是系统触发还是用户触发)时提供业务连续性的方法和注意事项,因为术语“灾难恢复”围绕着数据库连续性的实施方法具有特定的含义。

交付业务连续性的过程可分解为:

  • 在发生灾难性基础结构故障时,维护对关键业务系统的访问以及可用性(针对持久状态运行的应用程序)。

  • 在发生灾难性基础结构故障时,维护对关键业务数据的访问以及可用性(持久状态)。

  • 在发生操作员错误或意外删除、修改或损害时,维护关键业务数据的可用性(持久状态)。

前两个元素传统上已在地理灾难恢复 (geo-DR) 的上下文中得以解决,而第三个元素则属于数据备份和数据还原的领域。

Azure 大大改变了关键业务系统可用性的平等分布方式,从而能够快速将关键应用程序部署到地理上分散的全球各数据中心。事实上,发布地理上分布的应用程序与发布单个云服务的过程之间的差异并不明显。

关键的难题仍然是管理对持久状态的访问;跨数据中心访问持久状态服务(如 Azure 存储和 SQL Database)通常会因延迟较高和/或不确定而导致结果达不到最佳状态,从而在数据中心发生故障时无法满足业务连续性要求。

对于灵活性,许多 Azure 服务提供(或者在其路线图中具有)自动地理复制功能。例如,除非另行专门配置,否则所有写入 Azure 存储(blob、队列或表)中的内容都会自动复制到另一个数据中心(每个数据中心在同一个地理区域内都有一个特定的“镜像”目标)。这大大减少了在 Azure 基础之上提供传统灾难恢复解决方案所需的时间和努力。后面的章节概述管理持久状态的核心 Azure 服务的地理复制功能。

为了在面对用户错误或操作员错误时维护业务连续性,在应用程序设计中应考虑若干其他注意事项。尽管 Azure 存储通过“存储分析”功能提供了有限的审核功能(在后面的章节中介绍),但它没有提供任何时点还原功能。在面对意外删除或修改时要求灵活性的服务将需要考察以应用程序为中心的方法,如定期将 blob 复制到其他存储帐户。

SQL Database 提供了用于维护数据的历史快照的基本功能,包括 DB 复制以及通过 bacpac 导入/导出。本文档后面将详细讨论这些选项。

借助于 Azure 平台提供的灵活扩展,供给曲线可以与需求曲线密切匹配(而不是用大量额外容量来满足峰值负载)。

借助灵活扩展,商品成本由以下各因素驱动:

  • 解决方案中利用了多少个扩展单元;虚拟机、存储帐户等(组合以进行扩展)

  • 这些扩展单元执行工作的效率如何。

我们将针对给定容量可执行的工作量称为应用程序的密度。服务和框架的密度越大,针对给定的资源部署执行的工作量就越大;也就是说,提高密度后会减少所部署的容量(和成本),或者能够以相同的部署容量吸收附加负载。密度由两种主要因素驱动:

  • 在一个扩展单元内执行工作的效率。这是传统的性能优化方式 – 管理线程争用和锁、优化算法、优化 SQL 查询。

  • 跨扩展单元协调工作的效率。如果系统由大量的较小单元组成,则将它们高效地结合起来的能力对于实现高效率而言至关重要。这涉及到跨组件进行通信的框架和工具,如 SOAP 消息传递堆栈(如 WCF)、ORM(如实体框架)、TDS 调用(SQL 客户端代码)和对象序列化(如数据合同或 JSON)。

除了针对单一计算机(或数据库)使用的传统优化方法之外,优化分布式通信和操作对于交付可扩展、高效的 Azure 服务而言是至关重要的。后面的章节将详细介绍这些关键的优化:

  • 块区并不意味着琐碎。对于每个分布式操作(也即导致网络调用的操作),针对包组帧、序列化、处理等都有一定量的开销。为了最大限度地减少开销,可尝试批处理为数量较少的“块区”操作,而不是大量“琐碎”的操作。请记住,批处理琐碎的操作的确会增加延迟,并可能导致丢失数据。正确的批处理行为的示例如下所示:

    • SQL。在单一批处理中执行多个操作。

    • REST 和 SOAP 服务(如 WCF)。利用以消息为中心的操作界面而不是琐碎的 RPC 样式,如果可能,还可考虑基于 REST 的方法。

    • Azure 存储(blob、表、队列)。成批(而非单独)发布多个更新。

  • 序列化的影响。在计算机之间移动数据(以及移入和移出持久存储)通常要求将数据序列化为线路格式。此操作的效率(即所需时间和占用的空间)在很大程度上影响了较大型系统的总体应用程序性能。

    • 利用高效率的序列化框架。

    • 使用 JSON 与设备通信,或者用于可互操作(用户可读)的应用程序。

    • 当你同时控制两个端点时,使用非常高效的二进制序列化(如 protobufAvro)来进行服务间通信。

  • 使用高效框架。许多丰富的框架可用于进行开发,它们具有大量高级的功能集。其中许多框架的缺点是你经常会为不使用的功能耗费性能成本。

    • 在通用界面之后隔离服务和客户端 API,以便允许替换或并行评估(通过静态工厂或倒置控制容器)。例如,提供一个针对通用界面发挥作用的可插拔 caching 层,而不是特定实现(如 Azure 缓存)。

在前一节中,我们介绍了要构建利用由 Azure 提供的云结构的应用程序,应涉及的关键设计概念和观点。本节将讨论核心平台服务和特性,阐述其功能、扩展边界和可用性模式。

因为每个 Azure 服务或基础结构组件都通过可用性 SLA 提供有限的容量,所以,了解这些限制和行为对于为你的可扩展性目标和最终用户 SLA 做出适当的设计选择而言至关重要。每个核心 Azure 服务都体现为以下四个中心环节:功能及其意图、密度、可扩展性和可用性。

Azure 订阅是最基本的管理、结算和服务配额单元。每个 Azure 订阅都具有一组默认配额,旨在防止意外覆盖或占用资源,但你可以通过与支持部门联系来增加这些配额。

每个订阅都有一个帐户所有者和通过 Microsoft 帐户(以前称为 Live ID)授权的一组联合管理员,他们通过管理门户对订阅中的资源具有完全控制权。他们可以创建存储帐户、部署云服务、更改配置,并可以添加或删除联合管理员。

Azure 管理 API(基于 REST 的 Web 服务)提供了一个自动执行界面,用于创建、配置和部署 Azure 服务(由管理门户在后台使用)。对这些 API 的访问是使用管理证书来限制的。

 

服务 默认限制

云服务

20

存储帐户

20

Cores

20

SQL Database 逻辑服务器

5

Tip提示
有关最新的 Azure 订阅和服务限制,请参阅 Azure 订阅和服务限制、配额与约束

Azure 云服务(以前称为托管服务)是基本的部署和扩展单元。每个云服务都由两个部署(生产和临时)组成,每个部署具有一组角色。云服务对于生产部署具有公共 DNS 条目(格式为 servicename.cloudapp.net),并具有一个临时部署 DNS 条目(格式为 someguid.cloudapp.net)。

每个部署都包含一个或多个角色(要么是 Web 角色,要么是辅助角色),它们反过来又包含一个或多个实例(非持久 VM)。每个实例都包含该角色的软件包的一个完全相同、不可变、非持久的快照(也即,给定角色的所有实例都部署了完全相同的内部版本)。这些实例运行 Windows Server 针对 Azure 专门推出的版本(默认情况下为提高安全性禁用了许多服务,配置为与 Azure 网络和服务结构等很好地协作),并且默认情况下由 Azure 结构自动进行修补。实时修补是通过滚动升级方案来处理的,介绍如下。

云服务可以部署到任何 Azure 数据中心,可以直接部署(在创建服务时选择目标区域),也可以通过关联组。关联组是对部署目标的间接引用,而部署目标可用来简化将应用程序的所有组件部署到相同数据中心的过程。

Web 角色预配置了 IIS 实例,该实例承载着应用程序代码。辅助角色中承载的应用程序代码在预配置的长期运行的应用程序主机中执行。每个云服务可能最多具有 25 个角色。角色默认配置是执行 .NET 代码,但角色可配置为运行与 Windows Server 兼容的任何代码– Java、Python、Ruby、node.js 等等。本文档中提到的所有平台功能都可以从任何平台中获得,但可能要求进行额外的客户端-代理开发以指向基于 REST 的 API。

在云服务内,所有实例都分配有专用 IP 地址(在 10.x 块中);所有传出连接看起来都是通过网络地址转换来自单个虚拟 IP 地址或 VIP(这是云服务部署的 VIP)。入站连接必须经由已配置的端点;这些端点针对内部角色和端口提供负载平衡的访问。例如,默认情况下,到云服务部署的入站 HTTP/HTTPS(端口 80 和 443)连接针对主 Web 角色的所有可用实例实现了负载平衡。

请注意,跨服务延迟(也即,使 NAT 跨越一个云服务并通过负载平衡器进入另一个云服务)比内部延迟相比,前者的可变程度要高得多,它是鼓励进行批处理或大批量跨服务连接以实现可扩展性的原因之一。

Azure 结构还为云服务部署中的所有实例提供了可用的配置服务。服务定义中提供了一组预期的静态配置参数(作为开发周期的一部分),并提供了在将服务发布到 Azure 时随服务一起部署的一组初始配置值。这组配置值可用作针对服务部署中的所有实例的运行时查找,可以在运行时通过 REST 接口、Azure 门户或 PowerShell 脚本进行修改。

当更改运行时配置时,所有实例可以选择(在应用程序代码中)挂接配置更改通知并在内部处理配置更新。如果应用程序代码未配置为捕获配置更改事件,则此角色中的所有实例都将遇到滚动重新启动,以更新其配置。

每个实例的状态都是非持久的;基本 Azure 映像(专门的 Windows Server VM)之上的任何配置都要求启动时配置,以创建性能计数器、优化 IIS 设置、安装依赖软件等等。这些配置脚本通常作为由云服务配置定义的启动任务来运行。

在云服务内部,Azure 结构提供有关配置、内部 IP 地址、服务配置等的信息,而此结构是通过 RoleEnvironment 提供的。在 Azure 实例之上运行的所有进程都可以访问 RoleEnvironment 信息,以检索配置、发现网络拓扑等等。你还可以使用 Azure 管理 API 从外部访问此信息。

Azure 结构提出了两个用于管理组件故障、重新配置以及升级/修补的核心概念:升级域和故障域。

升级域是 Azure 服务内的逻辑分组;默认情况下,每个服务都具有五 (5) 个升级域(可以在云服务定义中修改此值)。任何服务更改或升级一次只影响单一升级域。这些更改的示例包括修补操作系统、更改虚拟机大小、向正在运行的服务添加角色或角色实例、或者修改端点配置。

这样,就可以在保持可用性的同时实时重新配置正在运行的云服务。对于仅包含单个实例的角色,Azure 结构在升级操作期间无法提供可用性,这就是为何运行单一实例角色无法满足 Azure SLA 的原因。

故障域是基于基础硬件的逻辑分组。尽管无法保证直接映射到任何特定的硬件配置,但应将逻辑分组视为 Azure 结构自动将实例从表示单点故障(如单一基础物理服务器、机架等)的基础资源中分离的方法。为了满足服务 SLA,Azure 需要至少将实例部署到两个故障域。这是单一实例角色部署无法满足 Azure SLA 的另一个原因。

总结如下:

  • Azure 中的基本部署和扩展单位是云服务,由一组角色组成。每个角色都包含一组完全相同的角色实例,每个实例运行 Windows Server 的一个特定的云配置版本。

  • 除了物理拓扑(角色和实例)和应用程序代码之外,云服务还定义一个服务范围的配置。此配置可以在运行时更新。

  • 每个角色实例都是非持久的(更改、文件等都不能保证在重新引导、修补、故障事件中持久存在)。

  • 每个云服务都同时针对入站和出站通信公开单个虚拟 IP。云服务公开端点,而端点向内部角色和端口提供(循环法)负载平衡的映射。

  • Azure 使用升级域在逻辑上划分实例组,并提供滚动更新或修改(同时保持可用性)。

  • Azure 使用故障域在物理上将实例分组,使之脱离单点故障(如在同一台基础物理计算机上运行所有实例)。

  • 利用多个订阅以隔离部署、测试、临时和生产环境。

每个角色都包含一组实例(由一个或多个实例组成)。其中每个实例都是虚拟机,运行 Windows Server 的一个特定版本。实例(虚拟机)当前有五种规模:特别小一直到特别大。其中,每种规模都分配了一定量的 CPU、内存、存储空间和带宽。

 

虚拟机规模 CPU 内核数 内存 Web 角色和辅助角色的本地存储资源磁盘空间 虚拟机角色的本地存储资源磁盘空间 分配的带宽 (Mbps)

特别小

共享

768 MB

19,480 MB

(6,144 MB 预留用于系统文件)

20 GB

5

小型

1

1.75 GB

229,400 MB

(6,144 MB 预留用于系统文件)

165 GB

100

中型

2

3.5 GB

500,760 MB

(6,144 MB 预留用于系统文件)

340 GB

200

大型

4

7 GB

1,023,000 MB

(6,144 MB 预留用于系统文件)

850 GB

400

特别大

8

14 GB

2,087,960 MB

(6,144 MB 预留用于系统文件)

1,890 GB

800

Tip提示
有关最新的 Azure 订阅和服务限制,请参阅 Azure 订阅和服务限制、配额与约束

当两个或更多实例部署在不同的故障域和升级域中时,Azure 为云服务提供以下 SLA:

  • 针对面向 Internet 的角色(具有外部端点的角色)提供 99.95% 的外部连接

  • 在两分钟内检测到角色实例问题的几率达到 99.9% 并启动纠正措施

角色实例大小和计数在运行的应用程序中可能动态发生变化(注意,更改角色实例大小将触发滚动重新部署)。在给定用于构建 Azure 应用程序的向外扩展方法的情况下,当需要选择实例大小时,并非实例越大就一定越好。这一点同时适用于成本(为你不使用的功能付费)和性能(取决于你的工作负荷是 CPU 绑定、I/O 绑定等等)。本文档的“最佳实践”部分将更详细地探讨实例数目和实例大小。

Azure 存储是 Azure 的基线持久数据服务,提供 blob(文件)、队列和表存储(键到值)。存储帐户是扩展和可用性的基本单元,同时提供以下功能。与存储设备之间的所有通信均基于通过 HTTP 的 REST 接口。

Tip提示
有关最新的 Azure 订阅和服务限制,请参阅 Azure 订阅和服务限制、配额与约束

Azure 存储可用性 SLA 保证,在至少 99.9% 的时间内,经过正确格式化的用于添加、更新、读取和删除数据的请求将成功并正确地得到处理,此外,存储帐户与 Internet 网关之间保持连接。

在任何情况下使用此单独存储帐户都会遵循这些限制;也即,每秒操作数和总体带宽在表、blob 和队列之间共享。如果应用程序超过每秒总操作数,则服务可能返回 HTTP 代码(503 服务器忙)。操作特定于每个存储环节(表、队列或 blob),下面各小节将介绍这些内容。

根据前面的集装箱比方,每个存储帐户都是一个可提供一定容量的集装箱。超过单个帐户(集装箱)的限制就要求在同一个应用程序中利用多个帐户。

Azure 存储默认情况下提供了可用性和灵活性;所有写入或更新 Azure 存储的操作都在三个存储节点间透明、一致地复制(这三个节点处于不同的升级域和故障域中)。对 Azure 存储的访问是以访问密钥方式以单因素身份验证来控制的。每个存储帐户都具有主密钥和辅助密钥,这样就可以在循环主密钥时实现持续可用性。Azure 存储中的数据可自动通过地理复制而复制到“镜像”数据中心(除非使用门户专门禁用此功能)。地理复制是不透明的,在主数据中心出现故障的情况下,利用 DNS 重定向将客户端故障转移到辅助位置。

请注意,尽管 Azure 存储通过自动化副本提供了数据复原功能,但这不会防止你的应用程序代码(或开发人员/用户)因为意外或无心的删除、更新等操作而造成数据损坏。在遇到应用程序或用户错误时保持数据保真需要更为先进的技术,如将数据和审核日志复制到辅助存储位置。Blob 存储提供了快照功能,这一功能可在 blob 内容的时间快照中创建只读点,这可用作 blob 的数据保真度解决方案的基础。

Azure 存储通过其存储分析功能提供了遥测功能,同时收集和公开有关针对表、队列和 blob 的单独存储调用的使用情况数据。需要使用收集策略(收集所有内容、仅搜集表等等)和保留策略(保留数据的时间长度)针对每个存储帐户启用存储分析。

Blob 存储在 Azure 中提供了文件管理服务,同时提供可用性高、经济高效的方法来存储成批的未结构化数据。此服务提供两种类型的 blob

  • 块 blob。块 blob 用于高效地管理大型数据 blob。每个块 blob 由多达 50,000 个块组成,每个块最大可达 4 MB(总的最大块 blob 大小为 200 GB)。块 blob 支持并行上载,以便通过网络高效和并发移动大型文件。可以插入、替换或删除各个块,但无法进行就地编辑。

  • 页 blob。页 blob 旨在高效提供随机的读/写操作(如访问 VHD)。每个页 blob 的最大大小为 1 TB,由 512 字节的页面组成。可以添加或更新单个页面或一组页面,并可以就地覆盖。

下表中列出了 blob 存储的设计限制。请记住,所有这些操作都针对总体存储帐户限制进行计数。

 

Blob 类别 限制

最大 blob 大小(块)

200 GB(50k 个块)

最大块大小

4 MB

最大 blob 大小(页)

1 TB

页大小

512 字节

最大带宽/blob

480 Mbps

当超出单个 blob 的大小或带宽限制后,应用程序可以写入多个并发(或序列)blob 文件。如果你的应用程序超出单个存储帐户的限制,请利用多个存储帐户以提供更多容量。

Azure 队列在发布服务器与订阅服务器之间提供中间(中转)消息传递服务。队列支持多个并发发布服务器和订阅服务器,但自身并不公开较高顺序的消息传递基元(如发布/订阅或基于主题的路由)。它们通常用于将工作项(如消息、文档、任务等)分发到一组辅助角色实例(或在多个托管服务等之间分发这些工作项)。

Windows Azure 队列

如果应用程序未检索和删除队列消息,则 7 天后将自动删除这些消息。它们在信息的发布服务器与使用方之间提供解除关联功能;只要双方都具有存储帐户密钥和队列名称,它们就可以通信。

 

队列类别 限制

队列中的最大消息数

无(最大为存储帐户限制)

消息的最大生存期

1 周(自动清除)

最大消息大小

64 kB

最大吞吐量

每秒高达 500 条消息

队列旨在传递控制消息,而非原始数据。如果你的消息过大而无法放入一个队列中,则可以重构消息以便分离数据和命令。将数据存储在 blob 存储中,添加对存储在队列消息中的数据和意图的引用 (URI)(意图也即 blob 存储中的数据的用途)。

为了提高单个队列中的吞吐量,请在单一消息内批处理多条消息,然后利用“更新消息”命令来跟踪封装消息的任务的进度。另一种方法是将多条消息放入一个 blob 中,并由一个指针指向队列消息中的 blob。

如果你的应用程序需要的吞吐量高于单个队列所能提供的吞吐量,则利用多个并发队列。在这一上下文中,你的应用程序必须实现适当的分区和路由逻辑。

Azure 表存储为列(二维)数据提供了持久性高、可扩展性高且一致的存储区。它提供了 { partition key, row key } -> { data[] } 语义以存储和访问数据,如下图中所示。每个表都按分区进行分解,而分区中包括实体。每个实体都有其自己的(平面)架构或属性列表(列)。

Windows Azure 表

每个分区每秒支持高达 500 个操作;反过来,每个表最高支持存储帐户中提供的最大操作数。因为每个实体不仅包含实际数据,还包含列元数据(因为每个实例可能具有不同架构),所以不建议采用长列名,尤其对于大型方法。

 

表类别 限制

每个分区每秒最大操作数

500

最大实体大小(列名 + 数据)

1 MB

最大列大小(byte[] 或字符串)

64 kB

最大行数

无(最大为存储帐户限制)

支持的数据类型

byte[]、Boolean、datetime、double、Guid、int32、int64、string

各实体(你可以将其视为行)的最大大小为 1 MB,各列的最大大小限制为 64 kB。上表中列出了支持的数据类型;对于不支持的类型(如 DateTimeOffset),你的应用程序代码中需要一个序列化代理(例如,以标准字符串格式存储 DateTimeOffset)。

表存储使用与分区和实体、分区扫描或实体扫描关联的键来提供对所存储数据的访问。它支持筛选器投影,因为筛选表达式可以作为查询的一部分推送到表存储,并在表存储中执行。表存储不提供辅助索引,因此,任何非基于分区键或实体键的查找都要求进行表扫描和/或分区扫描。对于包含大量实体的分区,这通常对性能有重大影响。

任何超过 5 秒的查询处理都会返回一个继续标记,而应用程序可以使用此继续标记来继续接收查询的结果。检索的实体数超过 1,000 的查询必须利用分页模型,以便将数据返回到包含 1,000 个实体的块中(表存储 API 本身就支持块)。

目前对于表存储支持的唯一查询表达式是筛选和选择(选择特定属性);表存储不提供服务器端聚合或分组语义。为了构建要求丰富的聚合或分析功能的应用程序,通常更好的选择是以聚合格式存储数据或使用关系引擎(如 Azure SQL Database)。某些应用程序采用混合方法,将表存储中的数据聚合到一个辅助 SQL Database 中,然后用于查询和报告目的。

选择适当的分区函数对于高效和有效地利用表存储而言至关重要。对于分区函数的类型,有两种主要选择:

  • 时间。常用于存储时间序列数据,如 Azure 诊断性能计数器(本文档的遥测部分中介绍了用法),基于时间的分区函数将当前时间转换为表示时间窗口的值(当前分钟、小时等)。

    这样,就可以高效地查找和定位特定分区(因为表存储的筛选子句支持 >=、<= 等等),但如果所选时间窗口太窄并且发生了峰值事件,则容易受到限制。例如,如果所选分区函数是当前分钟值并发生了峰值事件,则过多的客户端可能尝试并发写入同一个分区。这不仅影响插入时的吞吐量,而且影响查询时的吞吐量。

  • 数据。以数据为中心的分区函数基于要存储(或检索)的数据的一个或多个属性来计算分区值。选择适当的数据驱动的分区函数取决于几个因素:查询模式、分区密钥密度(分区中最终用了多少个实体)以及无法预测的增长(这对于使非常大的表重新达到平衡可能是个挑战)。常见模式包括:

    • 单一字段。分区键是源数据中的单一字段(如订单信息中的客户 ID)。

    • 多字段。分区键或行键是源数据中多个字段的组合(通常是串联)。当选择分区键时,请注意,批处理操作要求所有实体位于同一个分区中(也即具有相同的分区键)。

    • 计算字段。分区键是基于一个确定性函数从一个或多个字段计算得出的。一个常见的示例是将用户配置文件分配到多个分区中。将使用为实现相对统一的分配而设计的哈希函数来对用户 ID 进行哈希处理,然后针对所需分区的数量进行取模。

任何重要的应用程序都要求使用多个分区。甚至对于具有总实体数中少量实体的表(如 200 个实体),如果应用程序每秒将发出几千个请求,则需要用多个分区才能满足吞吐量要求。

  • 单一表/单一分区。最简单的选项(恒定的分区键值),可满足小规模的工作负荷、有限的数据量以及请求吞吐量要求(< 500 个实体/秒)。

  • 单一表/多个分区。大多数部署的典型选项;认真选择与目标查询模式匹配的分区键。

  • 多存储帐户/多分区。如果负载投影超过每秒 5,000 个操作,则要求跨多个存储帐户使用表。

一旦选择,则数据达到重新平衡(重新分区)可能会非常昂贵,这涉及读取和复制具有新分区键的所有实体,然后删除旧数据。请注意,分区没有最小大小限制;分区可以由单个实体(也即行)组成。

如果你的应用程序需要的吞吐量高于单一表所能提供的吞吐量(在仔细选择分区之后),请在不同的存储帐户中利用多个并发表。在这一上下文中,你的应用程序需要实现适当的路由逻辑,以便选择适当的存储帐户。

Azure 内容传送网络 (CDN) 提供了一种高效的方法,在全球分布的缓存网络中缓存静态内容(从 blob 或应用程序输出)。这会减轻应用程序传送静态内容的压力,并改进总体最终用户体验。

内容传送网络可用性 SLA 确保按月以 99.9% 的可用性传送缓存的对象。

使用 CDN 要求为你的订阅激活此功能。在此,可以缓存 blob 内容(来自公开提供/匿名访问容器)和匿名应用程序输出内容(例如 http://www.myapp.com/cdn/somepage.aspx)。

通常,对于任何大规模的应用程序,所有常访问的静态内容(图片、css 等等)都应通过 CDN 并借助适当的缓存到期策略来进行传送。

例如,考虑一个具有一百万册书的在线电子书商店。在 CDN 中包括所有图书的内容(图片等)将会非常昂贵(因为绝大多数内容都不会频繁访问并且会持续过期),而只加入前面的内容(例如,前 50 本图书)将使缓存与价格之间达到最佳组合。

成功传送大规模服务的核心元素之一是遥测,也即深入了解应用程序的操作、性能和最终用户体验。Azure 应用程序的遥测方法需要同时考虑平台的向外扩展/分布式性质,以及可用于收集、分析和使用遥测的平台服务。

Azure 中用于收集和理解遥测的基本计数组件是 Azure 诊断 (WAD)。WAD 由一个代理以及一组用于存储和访问数据的标准结构和约定组成,而代理负责从各个实例收集数据并将它们转发至一个中心收集点(存储帐户)。代理支持若干配置方法,包括代码 (.NET)、在部署的项目代码内嵌入的配置文件或部署到 blob 存储的集中化的配置文件。配置在最后的实例中一定程度上是动态的,以便允许将更新后的诊断文件推送到 blob 存储,然后向下拉取到目标代理。

Windows Azure 诊断

WAD 配置面向若干数据源而提供;其中每个数据源都定期进行收集、通过用于 Windows 的事件跟踪(称为 ETW)会话进行批处理,并发布到目标存储帐户。代理负责处理临时连接问题、重试等其他事项。可用数据源如下所示:

  • 性能计数器。捕获到本地 ETW 会话并定期暂存至表存储中的性能计数器值的子集(CPU、内存等)。

  • Windows 事件日志。捕获到本地 ETW 会话并定期暂存至表存储中的 Windows 事件记录(应用程序、系统等等)的子集。

  • Azure 日志。应用程序代码(通过 System.Diagnostics.Trace)发布并由 DiagnosticMonitorTraceListener 跟踪侦听程序捕获的应用程序日志(跟踪消息)。这些日志将发布到目标存储帐户的“WAD 性能计数器”表中。

  • IIS 7.0 日志。关于 IIS 记录的请求的标准 IIS 日志信息(仅限 Web 角色)。日志将收集到本地文件中,并定期暂存至 blob 存储区。

  • IIS 失败请求日志。来自 IIS 的关于失败请求的信息,这些信息将收集到本地文件中,并定期暂存至 blob 存储区。

  • 崩溃转储。系统一旦崩溃,即捕获关于操作系统状态的日志并发布至 blob 存储区。

  • 数据源。WAD 可以监视更多本地目录(如本地存储区中的日志目录),并定期将这些数据复制到 blob 存储区的自定义容器中。

以上每种数据源都配置有要收集的数据子集(例如,性能计数器列表)以及收集/发布时间间隔。还可以通过一些 PowerShell 脚本来更改运行时配置,或强制从代理向目标存储帐户立即发布数据。

Important重要提示
将遥测数据记录到单独的存储帐户中。将遥测数据和应用程序数据记录到同一存储帐户中将导致大规模的严重争用问题。

Azure SQL Database 提供数据库即服务,支持应用程序快速设置关系数据库、向其中插入数据以及查询该数据库。它提供许多熟悉的 SQL Server 功能,同时减少了硬件、配置、修补和复原方面的负担。

note备注
SQL Database 并不提供与 SQL Server 一一对应的功能,其目的在于满足专门适用于云应用程序的一套不同的要求(通过弹性扩展、数据库即服务来降低维护成本等)。有关详细信息,请参阅 http://blogs.msdn.com/b/windowsazure/archive/2012/06/26/data-series-sql-server-in-windows-azure-virtual-machine-vs-sql-database.aspx

该服务运行在多租户共享环境中,其中的数据库来自基于商用硬件构建的基础结构上运行的多个用户和订阅(横向扩展,而非纵向扩展)。

数据库设置在逻辑服务器内部;每个逻辑服务器默认包含最多 150 个数据库(包括 master 数据库)。默认情况下,每个订阅可以设置五 (5) 个逻辑服务器;但通过调用支持可以提高此限额以及每个逻辑服务器包含的最大数据库数。

每个逻辑服务器都分配有一个公共的唯一生成的 DNS 名称(格式为 [服务器名称].database.windows.net),订阅中的每个逻辑服务器都共享同一个公共 IP 地址。通过标准的 SQL 端口 (TCP/1433) 访问逻辑服务器(和数据库),同时通过基于 REST 的管理 API 访问端口 TCP/833。

默认情况下,限定只能根据针对 Azure 管理门户的基于 IP 的防火墙规则来访问逻辑服务器及其中的所有数据库(可以针对逻辑服务器或单个数据库设置规则)。若要支持访问 Azure 应用程序以及从 Azure 外部直接连接应用程序(例如,连接 SQL Server Management Studio),需要配置防火墙规则。可以通过 Azure Web 管理门户使用管理服务 API 调用来配置这些规则。

SQL Database 提供 SQL Server 中现有的多数关键功能,但也有一些重要的例外情况,包括:

  • 所有表都必须包含 CLUSTERED INDEX;在定义 CLUSTERED INDEX 之前不能将数据 INSERT(插入)SQL Database 中的表。

  • 没有内置的公共语言运行时 (CLR) 支持、数据库镜像、Service Broker、数据压缩或表分区功能。

  • 没有 XML 索引;不支持 XML 数据类型。

  • 不支持透明数据加密 (TDE) 或数据审核。

  • 不支持全文搜索。

每个数据库在创建时,都配置了大小上限。当前可用的容量上限包括 1 GB、5 GB、10 GB、20 GB、30 GB、40 GB、50 GB、100 GB 和 150 GB(当前可用的最大容量)。当数据库达到大小限制时,它会拒绝其他 INSERT 或 UPDATE 命令(仍然可以查询和删除数据)。还可以通过发出 ALTER DATABASE 命令来创建数据库大小(增大或缩小)。

由于要依据每天使用的平均大小对数据库计费,预计快速增长或增长不可预测的应用程序可以选择将数据库最大容量最初设置为 150 GB。将数据库扩展至 150 GB 以上需要利用横向扩展方法,下一节将对此进行详细介绍。

SQL Database 为节点级别的故障提供内置的故障恢复功能。所有写入数据库的内容会使用仲裁提交技术自动复制到两个或多个背景节点(主要副本和至少一个辅助副本必须先确认活动写入事务日志,然后才将事务视为成功并返回)。在节点失败时,数据库会自动故障转移到其中一个辅助副本。这将导致客户端应用程序出现暂时性连接中断,这就是为何所有 SQL Database 客户端都必须执行某种形式的暂时性连接处理的主要原因之一(请参阅下文,了解有关执行暂时性连接处理的详细信息)。

月度可用性 SLA 要求 99.9% 的正常运行时间,即要求能够在 5 分钟的时间间隔内于 30 秒内连接到 SQL Database。上一段中所述的故障转移事件通常出现的时间不超过 30 秒,这更加迫切地需要你的应用程序能够处理暂时性连接故障。

SQL Database 通过动态管理视图 (DMV) 提供关于数据库运行状况和性能的深入分析信息;这些 DMV 包含有关系统主要方面的信息,如查询性能、数据库和表大小等。应用程序负责定期自主要 DMV 收集和分析信息,并将其整合到范围更大的遥测和洞察框架中。

有若干业务连续性(备份、恢复)选项可用于 SQL Database。数据库可以通过数据库复制功能或 DAC 导入/导出服务进行复制。数据库复制提供业务一致的结果,而 bacpac(通过导入/导出服务)则不会提供业务一致的结果。这两个选项都作为数据中心内基于队列的服务来运行,并且当前都未提供完成时间 SLA。

请注意,数据库复制和导入/导出服务都会给源数据库带来相当大的负载,并且可能触发资源争用或限制事件(在下文中的“共享资源和限制”一节中介绍)。由于以上两种方式都不能提供 SQL Server 支持的同等程度的增量备份,所以当前正在预审一项用来启用时间点恢复功能的新功能。时间点恢复功能允许用户将其数据库恢复到近两周以内的任意一个时点。

当前唯一支持的身份验证方法就是 SQL 身份验证,这是一种基于数据库中已注册用户的单因素用户名/密码登录方式。Active Directory 或双因素身份验证功能尚不提供。极力推荐使用 ADO.NET、ODBC 等接口中现有的内置加密支持对 SQL Database 连接加密。数据库级权限与 SQL Server 的权限一致。有关设置 Azure SQL Database 安全性的详细介绍,请参阅在 Azure SQL Database 中管理数据库和登录名

SQL Database 提供一套功能齐全的动态管理视图,可用于观测查询性能和数据库运行状况;但并未提供用来收集和分析这些数据的自动化基础结构(同时也未提供直连式事件探查器和操作系统级别的性能计数器等用户熟悉的工具)。用于收集和分析的方法在本文档的“遥测”一节中介绍。

如上所述,SQL Database 是一种运行在共享基础结构之上的多租户服务。来自不同租户的数据库共享基于商用硬件构建的底层物理节点。其他系统用户可以使用同一底层基础结构之上的关键资源(工作线程、事务日志、I/O 等)。资源的使用要接受监管,这样便可将数据库限定在既定的资源限定范围之内。一旦超出这些限制,在租户或物理节点级别,SQL Database 就会中止使用或断开连接。这些限制列在下表中。

 

资源 每个事务/会话的最大值 每个物理节点的最大值 软中止限制 硬中止限制

工作线程

N/A

512

305

410

数据库大小

每个数据库配置的最大容量为 150 GB

N/A

100%;达到限值后不接受插入或更新

事务日志增长

每个事务 2 GB

500 GB

N/A

N/A

事务日志长度

总日志空间 (100 GB) 的 20%

500 GB

N/A

N/A

锁计数

每个事务一百万

N/A

N/A

N/A

阻止系统任务

20 秒

N/A

N/A

N/A

临时数据库空间

5 GB

N/A

N/A

5 GB

内存

N/A

N/A

N/A

16 MB/20 秒

最大并行请求数

每个数据库 400 个请求

N/A

N/A

N/A

一旦达到事务限值,系统就会取消该事务。一旦数据库达到软中止限制,事务处理和连接的速度就会减慢或中止。一旦达到硬中止限制,底层物理节点上的所有数据库(和用户)都会受到影响,导致终止现有操作并且阻止新操作或连接,直到资源使用量下降到中止阈值之下。

以上有些中止限制导致应用程序的设计和性能限值可能不直观。例如,限制事务日志增长到每个事务最大 2 GB 将阻止针对大型表构建索引(因为构建索引将生成超过 2 GB 的事务日志)。本文档的“最佳实践”一节会介绍关于执行此类操作的方法。

处理这些类型的限制条件和暂时性错误要求慎重设计和实施客户端代码;解决这些问题需要横向扩展数据库层,以便同时利用多个数据库(下一节将介绍横向扩展)。

SQL 客户端应用程序代码应该:

  • 实施可以识别与限制有关的 SQL 错误代码的重试代码,并提供相应的退让逻辑。如果应用程序中不存在某种形式的退让逻辑,数据库可能因不断地承受峰期负载而被锁定在持续限制状态。

  • 记录限制错误,用重试代码来区分暂时性连接、限制和硬故障 - 语法、缺失存储过程等。这将有助于跟踪和解决应用程序可用性问题。

  • 实施断路器模式。适当选择的重试策略一旦失效(针对应用程序的重试频率权衡滞后时间与系统响应),需调用某一代码路径来处理非暂时性错误(即断路器脱扣)。应用程序代码随后可以:

    • 回退到另一服务或方式。如果应用程序未能将新数据插入 SQL Database(并且这些数据不需要即时可用),那么这些数据可改为序列化为 DataTable 中的数据(或其他 XML/JSON 格式),并写入 blob 存储区中的文件。该应用程序随后可能向用户(或 API 调用)返回成功代码,稍后再将这些数据插入数据库。

    • 通过返回 Null 值无提示失败(如果数据或工作流是可选的,即不影响最终用户体验)。

    • 通过返回错误代码快速失败(如果未提供有用的/适当的回退机制)。

SQL Database 可以轻松交付大量的相对较小的扩展单元(数据库)。利用 SQL Database 在 Azure 上实施高度可扩展的应用程序要求采用横向扩展方式,结合使用多个数据库的资源来满足变化莫测的需求。以往应用程序都是面向“钛金蛋壳”(即单个纵向扩展的高度灵活的数据库服务器)来设计,而今需要通过周密的设计实现应用程序转型,使其能够高效利用横向扩展的数据库服务。

和其他 Azure 核心服务一样,对于 SQL Database,横向扩展和组合是进一步的扩展(数据库大小、吞吐量)和资源(工作线程等)利用的关键所在。有两种核心方法可实施 SQL Database 的分区/分片(进而实施横向扩展);这两种方法在一个应用程序内部并不互斥:

  • 水平分区。在水平分区方法中,完整的表或数据集被分隔到各个数据库中。例如,对于为不同客户群服务的多租户应用程序,该应用程序可以为每个客户都创建一个数据库。对于大型单租户应用程序,客户表可以位于与订单表不同的数据库中。分区键通常就是租户标识符(例如,客户 ID)。在下图中,数据集水平分区到三个不同的数据库中,以经过哈希处理的电子邮件作为分区值(即,分区键是电子邮件,分区函数使用这个经过哈希处理的键映射到目标数据库)。

    水平分区。
  • 垂直分区。在垂直分区方法中,数据集根据架构分区分布在多个物理表或数据库中。例如,客户数据和订单数据可能分布在不同的物理数据库中。在下图中,数据集垂直分区到两个不同的数据库中。核心用户信息(姓名、电子邮件)存储在 DB1 中,用户配置文件信息(如虚拟形象图片的 URI)存储在 DB2 中。

    垂直分区。

许多应用程序都混合使用水平分区和垂直分区(混合分区),此外还会纳入其他存储服务。例如,在上例中,用户的虚拟形象图片作为 ID 存储在数据库中,而应用程序会将其展开为完整的 URL。此 URL 随后会映射到 blob 中存储的图像。

在使用横向扩展的关系数据存储区时,可用性的计算极为不同。在分片数目较多的系统中,某个数据段脱机的概率较高,而整个应用程序不可用的概率要低得多。应用程序还需考虑到后端数据存储区不完全可用的情况。利用横向扩展数据模式,数据不再是要么完全可用,要么完全不可用。

对数据重新分区可能有些难度,特别是在使用模式或数据分布随时间变化的情况下。基于范围的分区键,不论是基于固定数字(使用经过哈希处理的分区值的模数)还是基于分区值的分布,都要求在各个分片之间重新平衡分布数据。基于范围的分区方案往往利用二进制拆分或合并来简化重新平衡操作。

例如,固定范围的分区方法(如姓氏首字母)可能最初是平衡分布的,但随着新用户的出现(用户各有各的姓氏,而这些姓氏在字母表中不一定均匀分布),其分布可能迅速转变为高度失衡。需要谨记:随着时间推移,可能要根据需要调整分区机制和重新平衡数据的成本。

基于查找的分区方案实施难度更大,每个数据租户或分区都需要高性能的查找机制,但由于这些方案允许逐个将单个租户重新平衡分布到新分区,所以更适用于细化的重新平衡。这些方案还支持向系统添加更多容量(新数据库等),而无需复制数据。

无论如何搭配使用分片方法,移至横向扩展的分片关系数据库都带有特定的限制,要求采用不同的数据管理和查询方法:

  • 以往,“良好”的 SQL 数据存储和查询设计都利用高度规范化的数据模型进行了存储和一致性方面的优化。这种方法假定利用交叉引用和表间 JOIN(联接)形成了全局一致的数据空间。但随着数据分布到物理上分散的多个节点上,JOIN 和交叉引用仅在单个分片内适用。SQL Database 不支持多个数据库上的分布式查询;需要在客户端层处理数据合并,并且在分片之间进行数据的非规范化处理和复制。

  • 引用数据和元数据通常都被集中到引用表中。在横向扩展方法中,这些引用表以及公共分区键无法分隔的所有数据都需要在分片之间复制并保持一致

  • 由于没有切实可行的方法在分片之间提供可扩展、高性能的分布式事务,所以数据(乃至架构更新!)永远不可能在分片之间保持事务上的一致性。应用程序代码需认定分片之间存在一定程度的松散一致性,并将其作为一种考虑因素。

  • 应用程序代码需要了解分片机制(是水平分区还是垂直分区)才能连接正确的分片。

  • 常用的 ORM(如实体框架)本身并不了解横向扩展数据模型;广泛利用大型 ORM 的应用程序可能需要大规模重新设计才能与水平分片兼容。对于以垂直分片法将租户(客户集)隔离到单个数据库的设计,通常在数据访问层所需的重新设计较少。纯垂直分片模型的缺点是:每个单独分片的容量受单个数据库容量的限制。

  • 需要访问(读取或写入)多个分片的查询要使用散播-聚集模式来实施,这种模式将针对目标分片执行各个查询,随后在客户端数据访问层聚合结果集。

要横向扩展 SQL Database,需要将数据手动分区或分片到多个 SQL Database 上。这种横向扩展方法可随着扩展实现几乎线性的成本增长;但如果需要,弹性增长或按需扩容所需的成本可呈增量式增长。并不是所有应用程序都需要经过大规模重新设计才支持这种横向扩展模式。

  • 不能保证架构更新在事务上的一致性,特别是在更新大量分片时。应用程序要么需要接受计划的停机期间,要么能够处理多个并行的已部署架构版本。

  • 业务连续型流程(备份/恢复等)需要能够处理多个数据分片。

用于解决这些难题的设计建议和最佳实践在本文档的“最佳实践”一节中介绍。

下文中侧重于依据现实世界中的经验和从这些经验中汲取的教训,介绍使用 Azure 和 SQL Database 交付高度可扩展应用程序的最佳实践。每项最佳实践都讨论目标优化和组件、实施方法和固有的优缺点。和任何其他最佳实践一样,这些建议高度依赖于它们的适用环境。根据上一节所述的平台功能评估每项最佳实践的适用性。

note备注
这些经验是从一些未遵从典型 OLTP(联机事务处理)模式的客户项目中总结出来的。务必要意识到:有些最佳实践不能直接套用在要求较强或较严格的数据一致性的应用程序上;只有你自己了解你应用程序及其环境的确切业务要求。

每项最佳实践都将与一个或多个方面的优化有关:

  • 吞吐量。如何增加通过系统的操作(事务、服务调用等)的数量,以及如何减少资源争用。

  • 滞后时间。如何缩短聚合和单个操作上的滞后时间。

  • 密度。在直接上下文(例如,SQL Database 的应用程序代码)和聚合上下文(利用多个存储帐户以提高扩展性)环境中编写服务时,如何减少争用点。

  • 可管理性。诊断、遥测和洞察 - 如何理解大规模部署的服务的运行状况和性能。

  • 可用性。如何通过减小故障点和模式的影响来提高整体应用程序可用性(availability-under-load 将在吞吐量/滞后时间/密度部分中介绍)。

托管服务是 Azure 中的基本扩展单元,所以周密地设计和部署托管服务是提供高度可扩展的可用服务的关键。

  • 托管服务中的实例和升级域数量可能极大影响部署、配置和升级托管服务所需的时间。需要在性能和可扩展性优势与这些优势所带来的复杂性之间权衡利弊。提高可扩展性和灵活性往往会增加解决方案的开发和管理成本。

  • 避免单实例角色;此配置不符合 Azure SLA 要求。在节点发生故障或执行升级的过程中,单实例角色将脱机。所以只应将单实例角色限制用于优先级较低的“维护”任务。

  • 每个数据中心都具有有限(尽管较大)的容量,可以在极少数情况下用作单个故障点。要求最高级别扩展和可用性的服务必须实施支持多个托管服务的多数据中心拓扑结构。但是:

  • 如果不需要最高级别的可用性(请参阅上一个条目),应确保应用程序和依赖服务完全包含在单个数据中心内。在解决方案必须使用多个数据中心的情况下,请遵循以下原则:

    • 避免为实时操作(专门的跨站点同步操作除外)执行跨数据中心网络调用。数据中心之间的长时间延迟变数极大,可能产生意外或用户不希望出现的应用程序性能问题。

    • 只准备好访问其他数据中心中的服务所需的最基本的功能。通常,这些活动与业务连续性和数据复制有关。

对于大型分布式应用程序,对有状态应用程序数据的访问至关重要。应用程序的整体吞吐量和滞后时间通常取决于检索、共享和更新所需数据和上下文的时间能有多快。为了满足此类需求,Azure Caching 和 Memcache 之类的分布式缓存服务应运而生。应用程序应利用分布式缓存平台。请考虑以下原则:

  • 利用分布式缓存平台,将其作为你托管服务中的辅助角色。缓存的这种邻近客户端特性减少了负载平衡器遍历所造成的延迟和吞吐量障碍。Azure Cache 上的角色中缓存在云服务内的辅助角色上承载缓存。

  • 将分布式缓存平台用作访问常用应用程序数据和对象(例如,用户配置文件和会话状态)的主存储库,并且由 SQL Database 或其他持久性存储区提供通读或缓存预留支持。

  • 缓存对象都有生存时间,该时间影响它们在分布式缓存中的有效期。应用程序要么对缓存对象明确设定生存时间,要么为缓存容器配置默认生存时间。应该在可用性(缓存命中率)与内存压力和数据陈旧程度之间权衡生存时间的选择。

  • 缓存具有 key->byte[] 语义;应注意可能产生重叠写入,造成缓存中的数据不一致。分布式缓存通常不会为存储数据的原子更新提供 API,因为缓存无法识别存储数据的结构。

    • 对于要求并发写入严格一致的应用程序,请使用为更新实体提供锁定机制的分布式缓存平台。对于 Azure Caching,可以通过 GetAndLock/PutAndUnlock 来实现。注意:这将对吞吐量产生负面影响。

  • 缓存性能在应用程序层上受限于序列化和反序列化对象所需的时间。若要优化此过程,请利用相对对称(数据编码/解码所需时间相同)、高效的二进制序列化程序,如 protobuf

    • 若要成功使用自定义序列化,请设计要在缓存中序列化的数据传输对象 (DTO),对序列化使用正确的批注、避免循环依赖,并利用单元测试跟踪高效序列化。

默认情况下,各个服务层之间的连接(包括通过负载平衡器传入的连接)要以循环方式分配,但连接次数有限。以下各图演示了各层与外部服务之间形成的典型连接网(左侧演示典型的仅限 Web 层的应用程序)。尽管该连接网没有为轻型连接协议(如 HTTP)显示任何重大性能问题,但有些连接要么因成本过高而无法连接/初始化,要么就是管制(受限)资源。例如,SQL Database 连接就属于这一类连接。要优化这些外部服务和组件的使用,强烈建议你针对特定实例关联资源调用。

连接关联

在上述关系图中,右边的拓扑具有位于同一托管服务内的单独 Web 层和辅助层(角色)。此拓扑还实现了 Web 层和应用程序层的关联,以将来自特定应用程序实例的调用固定到特定数据库。例如,要从数据库 1 (DB1) 请求数据,Web 实例必须通过应用程序实例 1 或 2 请求数据。因为 Azure 负载平衡器当前只实现了循环技术,在你的应用程序中提供关联并不要求进行认真设计和实现。

  • 使用单独的 Web 层和应用程序层设计应用程序,在 Web 层和应用程序层之间实现分区或资源级关联。

  • 实现将内部服务调用透明路由到目标应用程序实例的路由逻辑。使用外部或下游资源(如 SQL Database)使用的分区机制知识。

此多层体系结构的实际实现要求在 Web 层和应用程序层之间使用轻型协议进行高效的服务通信。

Azure 应用程序的开发技术与 Windows Server 的开发技术没有本质不同。但是,前者的弹性结构更需要利用最高效使用计算资源的代码,也更能发挥其优势。

  • 假定所有服务、网络调用和从属资源可能不可靠,导致暂时性故障(例如,本主题下面将介绍如何实现 SQL Database 的重试逻辑):

    • 对所有服务调用(SQL Database、存储等)实现合适的重试策略以处理暂时性故障和连接丢失问题。

    • 在重试逻辑中实现退让策略以避免“护航”效应(对延长中断时间的服务累积的重试)。

    • 实现富客户端遥测以便记录包含上下文信息(目标服务、用户/帐户上下文、活动等)的错误消息和故障事件。

  • 不要直接创建线程来计划作业;请利用现有计划和并发框架(如 .NET 任务并行库)。线程是相对重型的对象,通常不宜创建和释放。针对共享线程池工作的计划程序可以更高效计划和执行作业。此体系结构还提供更高级的语义来描述持续和错误处理。

  • 序列化网络传输优化数据传输对象 (DTO)。如果 Azure 应用程序是高度分散的,则可扩展性受系统的各个组件在网络上通信的效率影响。在通信或存储的网络上传输的任何数据应采用带适当提示的 JSON 文本序列化或更高效的二进制格式,以尽可能减小网络上传输的元数据量(如线路上更短的字段名称)。

    • 如果注重互操作,请使用高效的文本格式(如 JSON)以支持互操作性和带内元数据。

    • 如果注重高吞吐量(如在你控制两端的服务到服务通信中),请考虑采用高效打包的二进制格式(如 bson 或 protobuf)。

      • 避免频繁进行小对象的数据传输。服务之间的频繁通信会将大量系统资源浪费在管理开销任务上,导致变量延迟响应。

      • 序列化和反序列化对象的测试应是自动测试框架的核心组成部分。功能测试确保数据类可序列化,且没有循环的依赖关系。性能测试验证所需的延迟时间和编码大小。

  • 实际实施时,将轻型框架用于组件和服务之间的通信。.NET 堆栈中的很多传统技术提供了丰富的功能集,但是该功能集可能不满足分布式 Azure 的要求。提供意图和执行间高度抽象的组件可能严重影响性能。

    • 如果你不要求协议互操作性或高级协议支持,请考虑使用 ASP.NET Web API 替代 WCF 来实现 Web 服务。

    • 如果你不要求实体框架具有丰富功能,请考虑使用微型 ORM(如 Dapper)来实现 SQL 客户端层。

  • 通过允许将 IIS 中的 HTTP 压缩用于出站数据,减少从数据中心传送的数据量。

  • 关联层之间的连接以减少连接的频率和上下文切换次数。

  • 要减小应用程序的负载,请使用 blob 存储区来存储较大的静态内容 (> 100 kB)。

  • 要减小应用程序的负载,请使用借助 blob 存储区的内容传送网络 (CDN) 来传送静态内容(如图像或 CSS)。

  • 避免将 SQL Database 用于会话数据。请改用分布式缓存或 cookie。

Azure 存储是 Azure 应用程序中耐用的数据支柱。尽管可以提供高度可靠、可扩展的“开箱”体验,但是大型应用程序需要遵循适当的设计和使用准则。

  • 利用多个存储帐户可获得更大的可扩展性,无论是增大大小 (> 100 TB) 还是实现更大的吞吐量(每秒 > 5,000 个操作)。确保你的应用程序代码可以配置为使用多个存储帐户,使用适当的分区函数将作业路由到存储帐户。将添加更多存储帐户的功能设计为配置更改而非代码更改。

  • 谨慎选择表存储的分区函数,以在确保插入和查询性能的前提下支持所需的规模。将基于时间的分区方法用于遥测数据,使复合键基于非时间数据的行数据。使分区大小处在最佳性能的合理范围内;分区过小将限制执行批处理操作的能力(包括查询),分区过大则使查询成本很高(可能导致大量并发插入的瓶颈)。

    • 分区函数的选择也将从根本上影响查询性能;表存储方式提供按 {分区键, 行键} 的高效查找,但是处理 {分区键, 行匹配筛选器} 和 {分区键匹配筛选器, 行键匹配筛选器} 的效率不高。查询要求全局表扫描 ({行键匹配筛选器})。

    • 分区可以像单个实体这样小;这为纯查找工作负荷(如购物车管理)提供了经过优化的性能。

  • 尽可能在存储区中将操作进行批处理。表写入应进行批处理,通常通过使用 .NET 客户端 API 中的 SaveChanges 方法来实现。将一系列行插入表,然后在一个批处理中使用 SaveChanges 方法提交更改。对 blob 存储区的更新也应使用 PutBlockList 方法成批提交。与表存储 API 一样,针对一个块范围调用 PutBlockList 而非使用单个 blocks。

  • 表属性选择短列名;因为元数据(属性名称)在带内存储。列名也算在最大行大小 1 MB 内。过长的属性名称会浪费系统资源。

如“了解 Azure”一节中所述,SQL Database 将整个关系数据库作为服务功能提供,支持以向外扩展的方式对数据存储区进行访问。在大型应用程序中成功使用 SQL Database 要求进行几个谨慎的设计和实现选择;本节将介绍一些重要的设计点和最佳做法。

很多应用程序需要使用元数据表存储诸如路由、分区和租户信息之类的详细信息。将此元数据存储在单个数据库中会导致单个故障点以及无法进行扩展。中央元数据存储区应通过结合使用以下方式向外扩展:

  • 激进缓存:配置数据库中的信息应激进缓存到分布式缓存(如 memcache 或 Azure Caching)。

    • 请注意在应用程序启动时积极尝试从多个工作线程预加载缓存的影响,这通常导致过度加载和数据库限制。如果你的应用程序需要预加载的缓存,请将加载数据的工作委托给具有可配置加载速度的专用辅助角色(或计划的任务)。

    • 如果应用程序性能或可靠性依赖于缓存中是否具有某段数据,你的应用程序应拒绝传入的请求,直到预先填充了缓存。填充数据后,应用程序应返回相应的错误消息或代码。

  • 向外扩展:按垂直(按表)或水平(跨多个分片的段表)方式对数据进行分区,以将负载分配到多个数据库。

已分区的 SQL Database 的总体可扩展性受单个数据库(分片)的规模以及这些分片组合起来以增大规模的效率的影响:

  • 因为事务日志限制了较大的事务(如重建索引),单个表大小不应超过约 10 GB(请注意这个实际限制取决于目标表索引的大小,因此对于你的数据库该值可能大于也可能小于 10 GB)。对于大的单个表,请将它拆分为较小的几个表,并使用分区视图来提供统一覆盖。

    • 保持小的单个表可以减小分阶段升级过程中架构更改或索引重建的影响。对多个较小表的更改可以尽可能减小由于阻塞操作造成的停机时间和延迟时间。

    • 这个分区方式使管理技术复杂化。诸如重建索引等操作必须以迭代方式在所有组件表上执行。

  • 使单个数据库(分片)足够小。一些持续操作(如针对 50 GB 以上数据库的数据库复制或导出)可能需要几小时才能完成(服务取消操作需要运行 24 小时以上)。

进行连续服务交付、管理分布式数据库更新或架构修改时,需要小心谨慎以确保顺利升级。在生产数据存储区中控制架构和元数据操作的所有传统最佳做法比以往更为重要。例如,在 100 个数据库中的某个数据库调试和解决意外删除的存储过程是个比以往复杂得多的任务。

因为架构更新和数据修改在分片上不是事务一致的,在过渡期应用程序更新必须同时兼容旧架构和新架构。这个要求通常表示应用程序的每个版本必须至少与架构的当前版本和上一个版本兼容。

转换到数据库的向外扩展集合会带来一些与连接管理有关的问题。每个 SQL 数据库连接是相对昂贵的资源,这已由客户端 API(ADO.NET、ODBC、PHP 等)中连接池的广泛使用所证实。每个应用程序实例并不维护多个与中央 SQL Server 的连接,而是必须潜在维护与多个数据库服务器的连接。

因为连接是昂贵可能希缺的资源,应用程序必须通过及时退回池中连接来正确管理连接。应用程序代码应使用自动连接释放机制;在 .NET 中,最佳做法是在 using 语句内封装 SqlConnection 的所有使用,例如:

using (var conn = new SqlConnection(connStr))
{
    // SQL client calls here
}

如前文所述,针对 SQL Database 的连接受暂时性连接故障的影响。必须对所有连接和命令使用重试逻辑,以免受这些暂时性故障的影响(有关其他详细信息,请参见下文)。

SQL Database 只支持 TCP 连接(而非命名管道),强烈建议你加密应用程序代码和 SQL Database 之间的连接。要阻止无意识的连接尝试(如尝试使用命名管道),应用程序应按以下方式设置其 SQL 连接字符串格式:

Server=tcp:{servername}.database.windows.net,1433;Database={database};User ID={userid}@{database};Password={password};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;

对于大型应用程序部署,托管的服务部署和 SQL Database 群集内 SQL Database 逻辑服务器(每个服务器具有单独的外部 IP 地址)之间的潜在默认连接数可能以指数级增长。例如,对于包含 100 个实例、50 个数据库的托管服务,在 ADO.NET 中默认连接数为 100 个连接。

MaxConnections=DatabaseCount*Instance Count*MaxConnectionPoolSize

请参考托管服务的网络拓扑;连接的每个端(托管服务和 SQL Database 逻辑服务器)依赖 Azure 负载平衡器存活。. 每个 Azure 负载平衡器在任意两个 IPv4 地址之间最多具有 64k 个连接。此网络拓扑与默认可用连接数的组合导致较大的应用程序出现严重的网络故障

  • 将较大的应用程序部署在多个托管服务上。

  • 在多个订阅中部署数据库(不仅是同一订阅中的多个逻辑服务器)可以获得更为唯一的 IP 地址。

  • 实现多层应用程序以将出站操作关联到针对的应用程序实例(请参见有关托管服务的上一节)。

  • 请记住连接池根据每个唯一连接字符串来维护并对应于每个唯一的数据库服务器、数据库和登录组合。使用 SQL 客户端连接池并显式限制 SQL 连接池的最大大小。SQL 客户端库根据需要重用连接;小连接池可能导致应用程序在等待连接可用时延迟时间增加。

以下列表提供有关减少所需活动连接数的建议:

转换到分布式数据模型可能需要更改数据库架构的设计以及更改某些查询类型。要求跨多个数据库使用分布式事务的应用程序可能具有不合适的数据模型或实现(例如,尝试强制实施全局一致性)。这些设计必须重构。

由于可用性和可扩展性的限制,使用中央序列生成工具时应避免应用程序的任何不寻常的方面。很多应用程序利用序列提供全局唯一标识符,使用中央跟踪机制根据需要增大该序列。这个体系结构导致了一个全局争用点和瓶颈,系统的每个组件都需要与之交互。此瓶颈对于可能断开连接的移动应用程序更为突出。

因此,应用程序应利用可在分布式系统中生成全局唯一标识符(如 GUID)的函数。根据设计 GUID 不是按顺序的,因此在作为大表中的聚集索引时它们可能导致碎片。在大数据模型中为了减小 GUID 的碎片影响,请将数据库分片,使得每个分片相对较小。这允许 SQL Database 在副本故障转移期间对数据库自动整理碎片。

采用分布式数据模型时客户端应用程序代码必须考虑以下几个方面:

  • 分区键:分区键应是每个数据类或模型的一部分,并可能使用属性进行修饰以允许发现分区键。

  • 遥测:数据访问层应自动记录有关每个 SQL 调用的信息,包括目标、分区、上下文、延迟和所有错误代码或重试。

  • 分布式查询:执行跨分片查询将引入几个新问题,包括路由、分区选择和部分成功的概念(一些分片成功返回了数据而另一些分片则没有返回数据)。数据访问层应提供委托来以异步(并行)散播-聚集方式执行分布式查询,返回组合结果。分布式查询还需要考虑限制基础资源:

    • 最大并行度(以避免线程过多,连接压力过大)。

    • 最长查询时间(以减小长时间运行的查询的总体延迟时间或降低分片速度)。

还有几个必须执行的管理任务:

  • 引用表:没有提供全局连贯的查询空间,查询中 JOIN 的引用数据必须复制到每个分片。要求维护并复制数据到每个分片的引用表,以提供足够一致的本地引用数据。

  • 重新平衡分区:单个分片可能成为不平衡的,它们可能占用过多资源并成为阻塞点,也可能因为使用率低下而浪费资源。在这些情况下,必须重新平衡分区以重新分配资源。重新平衡机制在很大程度上依赖于分区策略和实现。在大多数情况下重新平衡通常涉及:

    • 将一个分片内的现有数据复制到一个或多个新分片,可以通过拆分/合并机制(对于基于范围的分区)或使用实体级复制和重新映射(对于基于查找的分区)。

    • 更新分片映射以指向新分片,然后补偿在过渡期间写入旧分片的所有数据。

  • 数据删除:随着应用程序数据的增长和收集,考虑定期删除旧的未使用的数据,以增加主系统的可用空间和容量。删除的数据不是以同步方式从 SQL Database 清除的,而是将它标记为删除,由后台进程进行清理。将数据标记为删除的常用方法是使用一个标记列,它可以表示行为活动、不活动或已标记为删除。这可以阻止在一段时间查询数据,如果用户指示仍需要数据,数据可以轻松移回生产系统。

    删除数据还可以触发碎片,可能需要索引重建以确保查询操作的效率。较旧的数据可以存档到:

    • 一个在线存储区(辅助 SQL Database),这增加了主系统的空间但是没有降低成本。

    • 一个离线存储区(如 blob 存储区中的 bcp 或 bacpac 文件);这增加了主系统的空间并且降低了成本。

    • 位存储桶。你可以选择从主系统永久删除数据以增加空间。

SQL Database 中的几个常见事件可以触发暂时性连接故障,如副本故障转移。应用程序必须实现相应的代码来处理暂时性故障并正确应对资源用尽和限制:

  • 使用重试机制处理暂时性连接故障:数据访问代码应利用策略驱动的重试机制来应对暂时性连接故障。重试机制应检测临时连接故障,重新连接到目标 SQL Database 并重新发出命令。

  • 使用重试和退让逻辑处理限制:数据访问代码应利用策略驱动的重试和退让机制来应对限制情况。重试机制应检测限制情况并逐渐退让尝试以重新发出命令(以避免可能延长限制情况的“护航”效应)。

    • 数据访问代码还应可以实施退让到备用数据存储区,如 blob 存储区。这个备用存储区为捕获活动、数据和状态,避免在出现延长的限制或可用性事件时数据丢失提供耐用的机制。

.NET 应用程序可使用可识别 SQL Database 的重试和退让逻辑框架,如云应用程序框架 (CloudFx)企业库暂时性故障处理程序。这些框架提供常用数据访问类(如 SqlConnection 和 SqlCommand)以及可直接调用的策略的包装。

var retryPolicy = RetryPolicy.Create<SqlAzureTransientErrorDetectionStrategy>(
    retryCount: 3, 
    initialInterval: TimeSpan.FromSeconds(5),
    increment: TimeSpan.FromSeconds(2));
                
using (var conn = new ReliableSqlConnection(connStr))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {

    }
    conn.Close();
}

上一个代码段演示在 SQL Database 上工作时如何使用 CloudFx ReliableSqlConnection 类来处理暂时性连接错误。

请注意记录对服务的 API 调用(如 SQL Database),因为可能出现复杂的故障情况。尝试捕获一些重要的上下文和性能信息。例如,所有 SQL Database 会话携带会话标识符,它可用于支持电话中,帮助直接隔离基本问题。强烈建议对于所有写入 SQL Database 日志的调用、命令和查询,记录:

  • 服务器数据库名称。由于可能有数百个数据库,目标服务器对于跟踪和隔离问题至关重要。

  • 根据需要记录 SQL 存储过程或命令文本。请注意不要在日志文件中泄露敏感信息 – 通常避免记录命令文本。

  • 调用的端到端延迟。通过使用 Stopwatch 或另一轻型计时器在计时委托中包装调用。

  • 调用的返回代码(成功或失败)以及重试次数和失败原因(连接已删除、被限制等)。

  • 连接的会话 ID,可通过 CONTEXT_INFO() 属性(使用 ReliableSqlConnection 时为 SessionTracingId 属性)访问。但是,对于每个客户端调用请不要从 CONTEXT_INFO() 检索会话 ID 属性,因为此命令触发另一个到服务器的往返。

为了更高效使用 SQL Database,针对 SQL Database 的查询和数据插入应尽可能进行批处理和以异步方式执行。这不仅提高了应用程序效率,而且减小了 SQL Database 上的总体系统负载(允许更大的吞吐量)。

  • 成批插入。对于连续数据插入操作(如注册新用户),数据应针对目标分片进行批处理和对齐。这些批应基于触发器定期(异步)写入 SQL Database,如目标批大小或时间窗口。对于小于 100 行的批大小,表值函数通常比大容量复制操作效率更高。

  • 避免聊天式界面。减小执行一个查询或一组操作所需的到数据库的往返次数。聊天式界面具有很高的管理开销,它加大系统负载,降低吞吐量和效率。请尝试合并相关操作,通常使用存储过程来减少往返次数。

作为保持业务连续性总体方案的一部分,在 SQL Database 中存储的数据应定期导出 blob 存储区。Blob 存储区默认情况下支持可用性和地理复制。

实现一个用于将数据库定期导出到 blob 存储区的计划任务。使用专用存储帐户。此任务应在目标数据库所在的数据中心中运行,而不是从内部桌面或服务器运行。

将导出任务安排在系统活动低的时间,以尽可能减小对最终用户体验的影响。导出多个数据库时,请限制并行导出的程度以减小系统影响。

了解你的 SQL Database 的运行状况、性能和容量是影响总体服务交付的关键因素。SQL Database 通过动态管理视图提供所需的原始信息,但是当前没有用于捕获、分析和报告关键度量值的现成基础结构。要为 SQL Database 提供此功能,请考虑以下做法:

  • 实现一个定期任务来收集与系统负载、已用的资源(工作线程、存储空间)和传入公共存储库的数据有关的关键性能数据。例如,考虑 Azure 诊断使用的表。此任务必须从属于你的应用程序的所有数据库收集数据。收集通常以向外扩展的方式执行(即同时从多个数据库收集数据)。

  • 实现定期任务以汇总此信息,获得有关你部署的数据库的运行状况和容量的关键性能指标。

Azure 诊断提供收集应用程序和实例级遥测数据的基线。但是,要了解在 Azure 上运行的大型应用程序的状况需要认真配置和管理数据流。与可以利用丰富的 Windows 诊断实用工具的集中式向上扩展应用程序不同,向外扩展的分布式系统中的诊断必须在系统投入运行前实现 – 它们不可能事后再考虑

错误处理、上下文跟踪和遥测捕获对于了解错误事件、根本原因和解决方法是至关重要的。

  • 不要将活动站点数据和遥测数据发布到同一存储帐户。请为诊断使用专用存储帐户。

  • 为块式(高容量、高延迟、粒状数据)和聊天式(低容量、低延迟、高值数据)遥测创建单独的通道

    • 将标准 Azure 诊断源(如性能计数器和跟踪)用于聊天式信息。

    • 使用通用的日志记录库(如企业应用程序框架库 log4net 或 NLog)来实现大容量记录到本地文件。使用诊断监视器配置中的自定义数据源将此信息定期复制到 blob 存储区。

  • 记录对外部服务的所有 API 调用,记录的信息包括上下文、目标、方法、计时信息(延迟)和结果(成功/失败/重试)。使用块式记录通道以避免诊断系统充斥大量遥测信息。

  • 将写入表存储区的数据(性能计数器、事件日志、跟踪事件)写入时长 60 秒的时间分区。尝试写入过多数据(过多点源、过低的收集间隔)可能填满此分区。请确保错误尖峰信号不触发对表存储区的大量插入尝试,因为这可能触发限制事件。

    • 选择要从这些源收集高值数据,包括关键性能计数器、严重/错误事件或跟踪记录。

    • 选择合适的收集间隔(5–15 分钟)以减少必须传输和分析的数据量。

  • 确保可以在运行时修改日志记录配置,而不必强制执行实例重置。还要验证配置的粒度足以支持记录系统的特定方面,如数据库、缓存或其他服务。

Azure 诊断不提供从属服务(如 SQL Database 或分布式缓存)的数据收集。要提供应用程序及其性能特征的综合视图,请添加基础结构来收集从属服务的数据:

  • 收集从属服务的关键性能和使用数据,并将它们作为性能计数器记录发布到 WAD 存储库。

    • Azure 存储(使用 Azure 存储分析)。

    • SQL Database(使用动态管理视图)。

    • 专用缓存(使用性能计数器或运行状况监视 API)。

  • 定期分析原始遥测数据来创建聚合和汇总(以计划的任务形式)。

显示: