云应用程序的十个设计原则 精简版
遵循这些设计原则可以提高应用程序的可伸缩性、复原能力和易管理性。
1 自我修复设计 。
在分布式系统中,故障时有发生。 设计应用程序以在故障发生时进行自我修复。
设计应用程序以在故障发生时进行自我修复
在分布式系统中,可能会发生故障。 硬件可能发生故障。 网络也有可能发生暂时性故障。 极少数情况下,整个服务或区域可能会遇到中断,但这些故障必须在计划之内。
因此,设计的应用程序在故障发生时可进行自我修复。 这需要从三个方面入手:
- 检测故障。
- 从容应对故障。
- 记录和监视故障,获取操作见解。
如何应对特定类型的故障可能取决于应用程序的可用性需求。 例如,如果需要非常高的可用性,则可能在区域中断期间自动故障转移到次要区域。 然而,这将使成本高于单区域部署。
此外,不要只考虑像区域中断这类大事件,因为这种情况通常鲜有发生。 应该尽可能将注意力集中在处理本地短期的故障上,例如网络连接故障或数据库连接失败等。
建议
重试失败的操作。 发生暂时性故障的原因可能有:短暂的网络连接中断、删除了数据库连接或服务因繁忙而超时。 在应用程序中构建重试逻辑来处理暂时性故障。
保护故障远程服务(断路器)。 在暂时性故障后最好进行重试,但如果故障仍然存在,最终可能会有非常多的调用方攻击故障服务。 因为请求进行了备份,这可能导致级联故障。 使用 [断路器模式],无需在操作发生故障时) 进行远程调用。
隔离关键资源(隔层)。 子系统中的故障有时会发生级联。 如果某个故障导致某些资源(例如线程或套接字)无法及时释放,导致资源耗尽,则可能就会发生这种连锁反应。 为了避免此问题,请将系统分区为独立的组,使一个分区中的故障不会导致整个系统瘫痪。
执行负载分级。 应用程序可能会遇到突发流量高峰,导致后端上的服务瘫痪。 为了避免此问题,请使用[基于队列的负载调节模式]使工作项排队进行异步运行。 队列充当可平缓负载高峰的缓冲区。
故障转移。 如果无法访问某个实例,请故障转移到另一个实例。 对于 Web 服务器之类的无状态对象,请在负载均衡器或流量管理器后放置一些实例。 对于数据库之类的存储状态的对象,请使用副本和故障转移。 根据数据存储和复制方式,可能需要应用程序处理最终的一致性。
补偿失败的事务。 一般情况下,需避免分布式事务,因为它们需要协调服务和资源。 相反,应该用较小的单个事务组成操作。 如果在中途操作失败,请使用[补偿事务]撤销已完成的所有步骤。
检查点长时间运行的事务。 如果长时间运行的操作失败,检查点可以提供复原能力。 当操作重新启动时(例如,它被另一个 VM 选中),它可以从上一个检查点恢复。
正常降级。 有时某个问题无法解决,但可以提供仍然有用的缩减版功能。 假设某个应用程序显示图书目录。 如果该应用程序无法检索封面的缩略图图像,它可能显示占位符图像。 整个子系统可能对应用程序不重要。 例如,在电子商务网站,显示产品建议可能没有处理订单重要。
限制客户端。 有时,少量的用户会产生过多的负载,降低了应用程序对其他用户的可用性。 在这种情况下,可以在一段时间内限制客户端。
阻止错误执行组件。 仅仅限制客户端并不意味着客户端的行为是恶意的。 它只意味着客户端超出其服务配额。 但如果客户端持续超出其配额或在其他方面具有不良行为,则可能需要进行阻止。 定义一个带外进程,供用户请求解除阻止。
使用领导选拔。 当需要协调任务时,请使用[领导选拔]选择协调器。 这样,协调器不是单一故障点。 如果协调器失败,则选择一个新的协调器。 与其从头开始实施领导选举算法,不如考虑现成的解决方案,比如 Zookeeper。
使用故障注入进行测试。 通常,成功的路径会得到精心的测试,而失败的路径却不会。 系统在生产中长时间运行后,才会执行失败路径。 通过触发实际故障或模拟故障,使用故障注入来测试系统对故障的复原能力。
采用混沌工程。 混沌工程通过将故障或异常情况随机注入到生产实例中,扩展了故障注入的概念。
2 实现全面冗余 。
在应用程序中构建冗余,以避免出现单一故障点。
在应用程序中构建冗余,以避免出现单一故障点
有弹性的应用程序围绕故障路由。 标识应用程序中的关键路径。 该路径中的每个点是否都存在冗余? 当子系统出现故障时,应用程序是否会故障转移到其他内容?
建议
考虑业务需求。 在系统中生成的冗余量会影响成本和复杂度。 体系结构应反映业务需求,如恢复时间目标 (RTO)。 例如,多区域部署比单区域部署昂贵,其管理也更复杂。 需要使用操作过程处理故障转移和故障回复。 可能为一些业务方案(而不是其他方案)考虑额外的成本和复杂性。
将 VM 放在负载均衡器之后。 请勿将一个 VM 用于任务关键的工作负载。 而是将多个 VM 放置于负载均衡器之后。 如果任何 VM 变得不可用,负载均衡器会向其余正常运行的 VM 分配流量。
[图片上传失败...(image-dcfbec-1623323401386)]
复制数据库。 自动复制区域内的数据,且可以跨区域启用异地复制。 如果使用 IaaS 数据库解决方案,请选择支持复制和故障转移的解决方案。
启用异地复制。 异地复制在一个或多个次要区域中创建数据的可读次要副本。 出现故障时,数据库可以故障转移到供写入的次要区域。
为提高可用性而分区。 数据库分区通常用于提高可伸缩性,但它还可以提高可用性。 如果一个分片出现故障,仍可以访问其他分片。 一个分片中的故障仅中断总事务的子集。
部署到多个区域。 为实现最高可用性,请将应用程序部署到多个区域。 这样,在极少数情况下,当某个问题影响到整个区域时,应用程序可以故障转移到另一区域。
[图片上传失败...(image-83976d-1623323401386)]
同步前端和后端故障转移。 使用流量管理器将前端故障转移。 如果无法访问一个区域中的前端,流量管理器会将新请求路由到次要区域。 可能需要协调数据库的故障转移,具体取决于数据库解决方案。
使用自动故障转移,但手动进行故障回复。 将流量管理器用于自动故障转移,而不用于自动故障回复。 自动故障回复存在风险,即可能在区域尚未完全正常之前切换到主要区域。 请改为验证所有应用程序子系统均正常运行,然后再手动进行故障回复。 此外,可能需要在故障回复前检查数据一致性,具体取决于数据库。
为流量管理器包括冗余。 流量管理器是一个潜在的故障点。 查看流量管理器 SLA,然后决定仅使用流量管理器是否能满足高可用性的业务需求。 如果不能,请考虑添加另一个流量管理解决方案作为故障回复机制。 如果流量管理器服务出现故障,请将 DNS 中的 CNAME 记录更改为指向其他流量管理服务。
3 尽量减少协调 。
最大程度地减少应用程序服务之间的协调以实现可伸缩性。
最大程度减少应用程序服务之间的协调以获得可伸缩性
大多数云应用程序包含多个应用程序服务 — web 前端、数据库、业务流程、报告和分析等。 若要实现可伸缩性和可靠性,其中每一个服务都应在多个实例上运行。
当两个实例尝试执行影响某种共享状态的并发操作时会发生什么? 在某些情况下,须跨节点进行协调,例如保留 ACID 保证。 此图中,Node2
正在等待 Node1
释放数据库锁定:
[图片上传失败...(image-72b918-1623323401386)]
协调限制了水平缩放的优点,且会形成瓶颈。 在此示例中,当横向扩展应用程序并添加更多实例时,锁定争用会增加。 而最糟的情况是前端实例将花费大部分时间等待锁定。
“仅一次”语义是发生协调的另一个常见原因。 例如,一个顺序必须仅处理一次。 两个辅助角色正在侦听新顺序。 Worker1
选取一个顺序进行处理。 应用程序须确保 Worker2
不会重复工作,并且如果 Worker1
崩溃,不会删除顺序。
[图片上传失败...(image-f72ee4-1623323401386)]
可使用计划程序代理监督程序等模式在辅助角色之间进行协调,但在这种情况下,采用对工作进行分区的方法可能更好。 每个辅助角色都分配有某范围的顺序(比如按照计费区域)。 如果某辅助角色故障,新的实例会在前一个实例停止的位置启用,但不会出现多个实例争用的情况。
建议
采用最终一致性。 分布数据时,需要协调来强制执行可靠的一致性保证。 例如,假设通过一项操作更新两个数据库。 最好系统能调节最终一致性(方法是在发生故障后,使用[补偿事务]模式进行逻辑回滚),而不是将其置于单个事务范围。
使用域事件同步状态。 [域事件]是一种事件,可记录域中发生的重要事情。 关注的服务会侦听事件,而不是使用全局事务来协调多个服务。 如果使用此方法,系统必须允许最终一致性(请参阅上一项)。
请考虑 CQRS 和事件源等模式。 这两种模式有助于减少读取工作负载和写入工作负载之间的争用。
- [CQRS 模式]将读取操作从写入操作中分离。 在某些实现中,读取数据通过物理方式从写入数据中分离。
- 在[事件源模式]中,状态更改作为一系列事件被记录到仅追加数据存储中。 将事件追加到流是一种原子操作,需要最小锁定。
这两种模式互为补充。 如果 CQRS 中的只写存储使用事件源,则只读存储可以侦听相同的事件,以创建当前状态的可读快照(已针对查询进行优化)。 但是,在采用 CQRS 或事件源之前,请先了解此方法的难题。
分区数据。 避免将所有数据放入一个由多个应用程序服务共享的数据架构中。 微服务体系结构通过使每个服务对自己的数据存储负责来强制执行这一原则。 在单个数据库中,将数据分区到不同分片可以提高并发性,因为写入到一个分片的服务不会影响写入其他分片的服务。
设计幂等运算。 如果可能,请将操作设计为幂等操作。 这样一来,可使用“至少一次”语义处理这些操作。 例如,可将工作项放入队列。 如果某辅助角色在操作期间故障,另一个辅助角色会选取此工作项。
使用异步并行处理。 如果某项操作需要多个异步执行的步骤(例如远程服务调用),可并行调用,然后聚合结果。 此方法假定每个步骤不依赖上一步的结果。
尽可能使用开放式并发。 悲观并发控件使用数据库锁定来防止冲突。 这可能会导致性能不佳,可用性降低。 对于乐观并发控件,每个事务修改数据的副本或快照。 提交事务时,数据库引擎会验证事务并拒绝会影响数据库一致性的任何事务。
请考虑 MapReduce 或其他并行的分布式算法。 根据要执行的工作的数据和类型,可将工作拆分为独立的任务,这些任务可以由并行工作的多个节点执行。
使用前导选举进行协调。 如果需要协调操作,请确保协调器不会成为应用程序中的单一故障点。 使用[领导选择模式],一个实例始终是领导并充当协调器。 如果该领导失败,会选择新的实例作为领导。
4 横向扩展设计 。
设计应用程序,使其能够扩大,根据需要添加或删除新实例。
设计应用程序,使其可进行水平缩放
云的主要优点是可以弹性缩放 — 能够根据需要使用容量,在负载增加时扩大,在不需要额外容量时缩小。 设计应用程序,使其能够扩大,根据需要添加或删除新实例。
建议
避免实例粘性。 在来自相同客户端的请求始终路由至同一台服务器时,才会出现粘性或会话相关性。 粘性限制了应用程序的横向扩展能力。例如,来自高容量用户的流量不会分布在多个实例中。 粘性的成因包括在内存中存储会话状态以及使用特定于计算的密钥加密。 请确保任何实例都可处理任何请求。
确定瓶颈。 扩大并不是解决每个性能问题的万能方式。 例如,如果后端数据库是瓶颈,添加再多 Web 服务器也于事无补。 首先识别并解析系统中的瓶颈,然后针对该问题引发更多实例。 系统中有状态的部分最有可能引起瓶颈问题。
通过可伸缩性要求分解工作负荷 应用程序通常由多个工作负荷组成,它们的缩放要求各不相同。 例如,应用程序可能有面向公众的网站和单独的管理网站。 公众网站可能会遇到流量突然激增的情况,而管理网站的负荷更小,且更具可预测性。
卸载资源密集型任务 需要大量的 CPU 或 I/O 资源的任务应尽可能移动到[后台作业]以减少处理用户请求的前端上的负载。
使用内置自动缩放功能。 许多计算服务有内置的自动缩放支持。 如果应用程序具有可预测的常规工作负荷,则可按计划扩大。 例如,在营业时间期间扩大。 否则,如果工作负荷不可预测,则可使用 CPU 或请求队列长度等性能指标来触发自动缩放。
请为关键工作负荷考虑自动缩放。 对于关键工作负荷,你希望供应一直超过需求。 在负载加重的情况下,最好是快速添加新实例来处理额外的流量,然后逐渐缩减。
缩小式设计。 请记住,使用弹性缩放时,应用程序会有缩小时期,此时实例会被移除。 应用程序必须妥善处理正在移除的实例。 以下是一些处理缩小功能的方法:
- 侦听关闭事件(可用时),然后全部关闭。
- 服务的客户端/使用者应支持暂时性故障处理和重试。
- 对于运行时间长的任务,请考虑使用检查点,或者[管道和筛选器]模式分解工作。
- 如果实例是在处理过程中移除的,请将工作项放在队列中,以便另一个实例可以接收工作。
5 通过分区解决限制 。
使用分区来解决数据库、网络和计算限制。
使用分区来解决数据库、网络和计算限制
在云中,所有服务都有纵向扩展的限制。 限制包括内核数、数据库大小、查询吞吐量和网络吞吐量。 如果系统增长到足够大,可能会命中一个或多个限制。 使用分区来解决这些限制。
有很多方法可用于分区系统,例如:
- 分区数据库,以避免对数据库大小、数据 I/O 或并发会话数的限制。
- 分区队列或消息总线,以避免对请求数或并发连接数的限制。
- 分区应用服务 Web 应用,以避免对每个应用服务计划的实例数的限制。
可以水平、垂直、或按功能分区数据库。
- 在水平分区(也称为分片)中,每个分区保存总数据集的子集的数据。 这些分区共享相同的数据架构。 例如,名称以 A–M 开头的客户进入一个分区,以 N–Z 开头的进入另一个分区。
- 在垂直分区中,每个分区保存数据存储中项字段的子集。 例如,将经常访问的字段放在一个分区中,将较不经常访问的字段放在另一个分区。
- 在功能分区中,根据系统中每个界限上下文使用数据的方式对数据进行分区。 例如,在一个分区中存储发票数据,在另一个分区中存储产品库存数据。 架构是独立的。
建议
分区应用程序的不同部分。 数据库显然很适合用于分区,但也需考虑存储、缓存、队列和计算实例。
设计分区键以避免热点。 如果分区数据库,但一个分片仍然获取大多数请求,那么问题还未解决。 理想情况下,负载会在所有分区中均匀分布。 例如,按客户 ID 而不是客户名称的首字母进行哈希分区,因为某些首字母会更集中。 分区消息队列时,该原则也同样适用。 选择一个可以在队列集中平均分布消息的分区键。 有关更多信息,请参阅[分片]。
通过分区解决订阅和服务限制。 单个组件和服务有限制,但订阅和资源组也有限制。 对于非常大的应用程序,可能需要进行分区来解决这些限制。
在不同级别分区。 请考虑部署在 VM 上的数据库服务器。 VM 有一个由 Azure 存储支持的 VHD。 存储帐户属于 Azure 订阅。 请注意,层次结构中的每个步骤都有限制。 数据库服务器可能有连接池限制。 VM 有 CPU 和网络限制。 存储有 IOPS 限制。 订阅有 VM 内核数的限制。 一般来说,在较低的层次结构更容易分区。 仅大型应用程序需要在订阅级别进行分区。
6 运营设计 。
合理设计应用程序,使运营团队获得所需的工具。
合理设计应用程序,使运营团队获得所需的工具。
云已经显著改变了运营团队的角色。 他们不再负责管理托管应用程序的硬件和基础结构。 即便如此,运营仍是成功运行云应用程序的关键环节。 运营团队的一些重要功能包括:
- 部署
- 监视
- 升级
- 事件响应
- 安全审核
可靠的记录和跟踪对于云应用程序非常重要。 在设计和规划中涉及操作团队,以确保应用程序为其提供成功所需的数据和见解。
建议
确保可以观测到所有内容。 部署和运行解决方案后,日志记录和跟踪的结果将是对系统的主要见解。 “跟踪”就是记录系统中的路径,有助于找出瓶颈、性能问题和故障点。 “记录”就是捕获单个事件,例如应用程序状态更改、错误和异常。 请在生产时记录,否则将在最需要它的时候缺乏见解。
用于监视的手段。 通过监视可了解应用程序在可用性、性能和系统运行状况方面的表现是否良好。 例如,监视可指示是否符合 SLA。 在系统的常规运行期间都会进行监视。 应尽可能实时监视,以便操作人员可以迅速对问题作出反应。 理想情况下,监视可在导致严重故障之前,帮助避免问题的出现。
用于根本原因分析的手段。 根本原因分析是查找故障的根本原因的过程。 它发生在故障出现后。
使用分布式跟踪。 使用专为并发、异步和云规模设计的分布式跟踪系统。 跟踪应包括跨服务边界的关联 ID。 单个操作可能涉及对多个应用程序服务的调用。 如果操作失败,关联 ID 可帮助找出失败的原因。
将日志和指标标准化。 运营团队需要在解决方案中聚合来自各种服务的日志。 如果每种服务使用各自的日志格式,将很难或不可能从中获取有用的信息。 定义包括关联 ID、事件名称、发送者 IP 地址等字段的常见架构。 单个服务可以派生继承基础架构并包含附加字段的自定义架构。
自动化管理任务,包括预配、部署和监视。 自动化任务具有可重复性并且可以减少人为错误。
将配置视为代码。 通过将配置文件签入版本控制系统,可以对更改进行跟踪和版本控制,并在需要时回滚。
7 使用托管服务 。
尽量使用平台即服务 (PaaS) 而不是基础结构即服务 (IaaS)。
如果可能,请使用平台即为服务 (PaaS),而不是基础结构即服务 (IaaS)
IaaS 就像有一盒零件。 你可以构建任何东西,但必须自己组装。 PaaS 选项更易于配置和管理。 无需提供 VM、设置 VNet、管理补丁和更新,以及与在 VM 上运行软件相关的所有其他开销。
例如,假设应用程序需要一个消息队列。 可以使用类似 RabbitMQ 的东西在 VM 上设置自己的消息传递服务。 但 Azure 服务总线已经提供了可靠的消息传递作为服务,而且它更易于设置。 只需创建一个服务总线命名空间(这可以作为部署脚本的一部分来完成),然后使用客户端 SDK 调用服务总线。
当然,应用程序可能具有某些特定要求,会使 IaaS 方法更合适。 但是,即使您的应用程序基于 IaaS,查找在哪里可以自然地包含 PaaS 选项。 其中包括缓存、队列和数据存储。
8 使用最佳的数据存储完成作业 。
选择最适合数据的存储技术,并了解如何使用该技术。
选择最适合数据的存储技术,并了解如何使用该技术
现在再也无需将所有数据粘贴到大型 SQL 关系数据库。 关系数据库非常擅长通过关系数据为事务提供 ACID 保证。 但这会产生一些费用:
- 查询可能需要高成本联接。
- 数据必须规范化,且符合预定义架构(写入架构)。
- 锁定争用可能会对性能产生影响。
在任何大型解决方案中,单个数据存储技术很可能满足不了所有需求。 关系数据库的替代方案包括键/值存储、文档数据库、搜索引擎数据库、时间序列数据库、列系列数据库和图形数据库。 每个方案都有其优缺点,不同类型的数据适合其中不同的方案。
例如,可将产品目录存储在文档数据库(如 Cosmos DB)中,这可实现灵活架构。 在这种情况下,每个产品描述是一个自包含文档。 若要查询整个目录,可以索引目录并将索引存储在 Azure 搜索中。 产品清单可以存储在 SQL 数据库中,因为这类数据需要 ACID 保证。
请记住,数据不仅仅包括持久化的应用程序数据。 还包括应用程序日志、事件、消息和缓存。
建议
请勿在任何情况下都使用关系数据库。 请考虑在适当的时候使用其他数据存储。 请参阅[选择适当的数据存储]。
采用混合持久性。 在任何大型解决方案中,单个数据存储技术很可能满足不了所有需求。
请考虑数据的类型。 例如,将事务数据存储在 SQL 中,将 JSON 文档存储在文档数据库中,将遥测数据存储在时序数据库中,将应用程序日志存储在 Elasticsearch 中,并将 Blob 存储在 Azure Blob 存储中。
优先考虑可用性而非(强)一致性。 CAP 定理意味着分布式系统须权衡可用性和一致性。 (的网络分区、CAP 定理的另一条脚永远无法完全避免。 ) 通常,可以通过采用 最终一致性 模型来实现更高的可用性。
请考虑开发团队的技能组合。 使用混合持久性有一些好处,但也有可能适得其反。 采用新的数据存储技术需要新的技能组合。 开发团队须了解如何充分利用此技术。 他们必须了解恰当的使用模式、如何优化查询、调整性能等。 请在考虑存储技术时将此因素考虑在内。
使用补偿事务。 混合持久性的副作用是单个事务可能会将数据写入多个存储。 如果出现故障,使用补偿事务来撤消任何已完成的步骤。
查看界限上下文。 界限上下文是域驱动设计中的一个术语。 界限上下文是域模型的显式边界,它定义模型适用于域的哪个部分。 理想情况下,界限上下文将映射到业务域的子域。 系统中的界线上下文是考虑混合持久性的自然位置。 例如,“产品”可能会出现在产品目录子域和产品清单子域,但这两个子域很可能对存储、更新、查询产品的要求不同。
9 演变设计 。
所有成功的应用程序会不断变化。 进化型设计是持续创新的关键。
进化型设计是持续创新的关键
无论是修复 bug、添加新功能、引入新技术,还是使现有系统更具伸缩性和弹性,所有成功的应用程序都在随着时间而不断变化。 如果应用程序的所有部分都紧密耦合,则很难将更改引入系统。 应用程序中一个部分的更改可能会破坏另一部分,或者改变整个代码库。
这个问题并不局限于单片应用程序。 应用程序可分解为服务,但仍会表现出那种紧密耦合性,使系统变得刚性和脆弱。 但当服务设计为可以改进时,团队可以创新并不断提供新功能。
微服务正在成为实现进化设计的一种热门方式,因为它们可以解决此处列出的许多值得注意的问题。
建议
强制执行高度内聚和松散耦合。 如果服务提供逻辑上具有共同所属的功能,则该服务具有内聚性。 如果可以在更改一个服务的同时不会更改另一个服务,则服务具有松散耦合性。 高度内聚通常意味着更改一个函数时还需更改其他相关函数。 如果发现更新某个服务时需要对其他服务进行协调更新,则可能表示该服务不具有内聚性。 域驱动设计 (DDD) 的目标之一就是标识这些边界。
封装域知识。 客户端使用服务时,强制执行域的业务规则的责任不应落在客户端上。 相反,服务应封装属于其责任范围内的所有域知识。 否则,每个客户端都必须强制执行业务规则,最终域知识会分散在应用程序的不同部分。
使用异步消息传递。 异步消息传递是一种将消息创建者与使用者分离的方法。 创建者不依赖于使用者回复消息或采取任何特定操作。 有了 pub/sub 体系结构,创建者甚至可能不知道谁在使用消息。 新服务可以轻松地使用消息,而不需要对创建者进行任何修改。
不要将域知识构建到网关中。 在微服务体系结构中,网关对于请求路由、协议转换、负载均衡或身份验证等操作非常有用。 但网关应该仅限于这种基础结构功能。 它不应实施任何域知识,以避免成为严重的依赖项。
公开开放接口。 避免在服务之间创建自定义转换层。 相反,服务应该公开具有明确定义的 API 协定的 API。 API 应拥有版本控制,以便在保持向后兼容性的同时改进 API。 这样就可以更新服务,而无需对依赖它的所有上游服务进行协调更新。 面向公众的服务应通过 HTTP 公开一个 RESTful API。 因性能原因,后端服务可能会使用 RPC 样式的消息传递协议。
针对服务协定进行设计和测试。 在服务公开了明确定义的 API 后,可以针对这些 API 进行开发和测试。 这样可以开发和测试单个服务,而无需启动所有的依赖服务。 (当然,仍然可以针对实际服务执行集成和负载测试。)
分清基础结构与域逻辑。 不要将域逻辑与基础结构相关的功能(如消息传递或暂留)混在一起。 否则,更改域逻辑时需要对基础结构层进行更新,反之亦然。
将跨领域问题转移到单独服务上。 例如,如果多个服务需要对请求进行身份验证,可将此功能移到各自的服务中。 然后便可改进身份验证服务(例如,通过添加新的身份验证流),而无需涉及使用它的任何服务。
独立部署服务。 DevOps 团队可以独立地为应用程序中的其他服务部署单个服务时,更新就会更快,更安全。 Bug 修复和新功能便能按更常规的节奏推出。 同时设计应用程序和发布过程以支持独立更新。
10 根据业务需求构建 。
每个设计决策必须与业务要求相称。
必须根据业务需求作出每一个设计决策
此设计原则看似不言自明,但在设计解决方案时请务必遵循此原则。 预期的用户数是数千还是数百万人? 是否可接受一个小时的应用程序中断? 是否预计流量过大或可预测的工作负荷? 最终,必须根据业务需求作出每一个设计决策。
建议
定义业务目标,包括恢复时间目标 (RTO)、恢复点目标 (RPO) 和可容忍的最长中断时间 (MTO)。 这些数字应为决策提供有关体系结构的信息。 例如,若要实现较低的 RTO,可能会向次要区域实施自动故障转移。 但如果你的解决方案可以接受较高的 RTO,则可能不需要该程度的冗余。
记录服务级别协议 (SLA) 和服务级别目标 (SLO),包括可用性和性能指标。 你可能会构建可用性为 99.95% 的解决方案。 这够了吗? 答案是需要业务决策。
围绕业务领域为应用程序建模。 首先分析业务需求。 根据这些需求为应用程序建模。 请考虑使用领域驱动设计 (DDD) 方法创建反映业务流程和用例的[域模型]。
捕获功能性和非功能性需求。 可以通过功能性需求判断应用程序是否执行了所需操作。 可以通过非功能性需求判断应用程序的操作执行是否则正常。 具体而言,确保你理解自己对可伸缩性、可用性和延迟的需求。 这些需求将影响设计决策和技术选择。
按工作负荷分解。 在此语境中,术语“工作负荷”是指某个离散的功能或计算任务,可以将它和其他任务逻辑分离。 不同的工作负荷在可用性、可伸缩性、数据一致性和灾难恢复方面具有不同的需求。
规划增长。 某解决方案可能满足你当前对用户数、事务量和数据存储等方面的需求。 但是,可靠的应用程序可在不进行主要体系结构更改的情况下实现增长。 请参阅[设计为横向扩展]和[分区避开限制]。 也请注意,你的业务模型和业务需求很可能在一段时间后发生更改。 如果应用程序的服务模型和数据模型过于死板,则很难将应用程序用于新的用例和方案。
管理成本。 在传统的本地应用程序中,按资本支出为硬件付费。 在云应用程序中,为使用的资源付费。 确保了解所使用服务的定价模型。 总成本包括网络带宽使用量、存储、IP 地址、服务消耗和其他因素。 也需考虑操作成本。 在云中,无需管理硬件或其他基础结构,但仍需管理应用程序,包括 DevOps、事件响应和灾难恢复等。