ODCA最佳实践翻译:Architecting Cloud-Aware Applications (二)

云应用架构原则

本节总结了云计算应用架构的基本原则。下表列出了影响应用程序如何组织、编码以及用户交互方式的结构性原则。后面的章节还总结了整个云系统的部署、操作以及与其它系统组件进行交互的运维原则。

这些原则适应于云计算应用架构的不同时间阶段,原则中标记为高优先级的属于开发云计算应用时强烈推荐的,低优先级相对来说重要性下降。

结构原则

本节详细介绍了云计算应用程序的结构原则。这些原则用来指导应用程序如何组织、编码以及与最终用户进行交互。

容忍失败

在一个大规模的云环境中,应用程序运行的基础设施容易由于某些固定类型的故障导致无法正常工作。这里潜在的安全漏洞包含计算、网络或者存储的硬件失败,或者组件及服务的软件错误,或者由于某个反应迟钝的组件导致的网络失败,或者瞬间网络连接的中断等等。为了适应这些类型的故障,应用程序的架构设计必须能够无缝地处理它们并且通过降低性能层次或者优雅的功能降级而无干扰地继续运行。

松耦合可以最大限度地减少组件之间的依赖关系,使组件被替换而不影响别人。例如,我们很可能决定将key-value存储用一个更高性能和可靠性的实现来替换。松耦合同时提供了一种管理失败和延迟的方式,一个给定的组件可能会失败,但是由于不静态绑定到固定的实例,可以将失败的组件进行动态替换避免了应用程序从失败中恢复所需要做的操作。

开发人员经常忽视对网络可靠性问题进行弹性设计的重要性。此类型的失败是很难检测以及优雅处理的。例如,如果关键数据由于访问数据库失败而未能写入,这时代码如何优雅地去处理该问题?可能有人争论说如果网络挂掉了后果如此严重,那么最简单的办法就是应用程序返回失败,让用户后续再次尝试。照他们使用移动设备的经验,用户应该知道网络不是100%的可靠。但是在云计算中忽略对失败的弹性设计,会导致大型分布式应用中单点失败的级联,最终造成整个系统服务的失败,而不仅仅只对某一个用户带来不便。

在某些场景下,间歇性故障是可以接受的。通过网络负载均衡功能可以减少多种类型的失败,提高弹性和性能。然而,在某些情况下未能优雅地处理网络问题可能产生不能接受的用户体验。想象一下如果由于远端的服务的网络中断导致旋转门无法确认乘客的车票让乘客无法入站。再想象一下一个游戏无法启动是由于不能和排名服务建立连接(该服务并是不游戏启动的必须条件)。这些不可接受的后果,以及由于网络问题导致的级联后果,使得架构师必须考虑系统的整体视图。

“系统必须在存在失败的情况下也能够持续响应,分布式系统要设计的可以容忍其所依赖的其它系统的失败。
如果系统down了,我们可以降低对客户的响应质量,但是还应该能够响应。如果我们系统的搜索部分变得缓慢,但是流媒体还是应该继续正常工作。”

  • John Ciancutti, Netflix Tech Blog: “5 Lessons We’ve Learned Using AWS”

对云计算所设计的应用程序必须可以以多种形式容忍基础设施的失败。

法则: 做一个悲观主义者,当基于云做架构设计时,必须假设所有事情都可能失败。换句话说,总是在设计、实现以及部署中考虑如何使得应用程序可以从失败中自动恢复过来。
特别是,认为你的硬件将会故障。认为机器会发生当机,认为应用程序会发生故障,认为某一天会遭受信令风暴,认为你的软件会随着时间发生失败。做一个悲观主义者,你可以在每次设计的时候都去考虑恢复策略,这可以让你的系统整体设计的更好。
如果你意识到事情会随着时间推移而失败,把这种思想融入到你的架构设计中,建立机制来通过一个可扩展的架构在灾难爆发之前进行失败处理,你将最终建立一个适合云的容错架构。

  • Jinesh Varia, Technology Evangelist, Amazon: “Architecting for the Cloud: Best Practices”

容忍延迟

云应用程序通常运行在一个多租户、共享的环境中。由于同环境中其它应用产生的负载通常会让网络的响应时间和延迟变得不稳定。云应用必须设计的能够妥善处理时延。云应用的开发者需要为时延专门进行弹性设计。由于基础设施故障导致的连接时延通常会有多种形式,包括:

  • 网络拥塞或者网络分区;
  • 对基础设施中的共享资源出现竞争请求;
  • 存储系统出现I/O带宽饱和;
  • 共享服务出现软件故障;
  • DoS攻击导致服务失败或者资源枯竭;

移动应用程序(mobile application)的开发人员已经开发了用于解决不可靠网络连接的技术,这些技术同样适应于云。例如:

  • 用队列缓存请求。实现一个请求队列和重试机制来确保请求不会丢失,这样可以保证应用程序不会出现崩溃。不需要在所有情况下都进行重传,例如,如果下次应用程序主动向服务poll数据时数据是有效的,那么可以允许某次请求超时。而对于写入请求则不应该进行丢弃。
  • 优雅地处理失败。如果失败发生了,需要温和地处理它。例如,不要因为一个服务响应慢而导致应用程序阻塞,相反可以返回响应表明请求的处理进度。

延迟是所有分布式网络系统的一个重要性能指标。光的速度决定了两个网络节点之间的最小时延。在最好的情况下,一个运行在旧金山和另一个运行在纽约之间的服务通讯延迟是13.8ms,或者往返27.6ms。在现实中,延迟比这个还要高,由于网络链接降低信号传播速度、网络协议栈开销以及网络交换机和拥塞引起的时延。根据AT&T的测量,在旧金山和纽约之间的时延有70ms。

延迟随着时间会发生变化,这些取决于网络流量和路由。当应用托管在第三方云基础设施上,这时开发者是缺少控制权的,云提供商的网络情况以及其他用户如何使用云都是不透明的。由于数据中心中流量的情况非常复杂较难控制,所以云环境中的延迟变化会非常明显。

“AWS被设计为共享资源的模型。其中包括硬件、网络以及存储等都是多租户的。这样网络的吞吐是在不同层次间变化的。你要么愿意放弃任何特定的任务执行,要么就在AWS下管理好你的资源避免多租户对你的影响。”

  • John Ciancutti, Netflix Tech Blog: “5 Lessons We’ve Learned Using AWS”

分布式系统中往往具有大量节点,延迟可能会由服务之间的依赖进行累积,例如A依赖于B,B依赖于C,这样一个深的依赖栈会导致响应延迟大而且难以扩展。定位及解决这些服务依赖可以改善性能。当在云中架构应用时,开发者需要减少服务的嵌套层次,并确保依赖于时间的任务不会穿越较深的服务栈。例如,数据存储的API提供了一个写入操作保证提交操作最终可以写完成。应用发出写命令后就可以继续处理其它任务而不用等写入存储完成,应用在这期间要能够处理存储数据和程序状态不一致的问题,否则,就会引起用户的二义性。

减少网络请求的数量有利于管理时延。设计交互较少的协议,尽量一次获得所需的数据,而不是多次交互积累数据。例如:不要一次请求用户的ZIP码,一次请求用户的电话号码,改成在一次请求中获取所有用户联系信息。

“在Netflix的数据中心中,我们的网络容量很大,速度很快而且高可靠,所以我们设计了很多和远端系统频繁交互的协议API。而AWS网络中时延变化幅度很大,这时我们就必须从结构上考虑尽量减少网络交互,即使我们已经是一个高分布式系统。”

  • John Ciancutti, Netflix Tech Blog: “5 Lessons We’ve Learned Using AWS”

这种方式也有一定局限性,那就是在单一响应中增加有效负荷,提高了对带宽的占用。

另一种解决方案是使用节点间或者节点上的缓存。利用缓存,可以减少网络上请求的数量。缓存的另一个好处是如果所依赖的节点由于网络问题变得不可靠,如果设计得当,服务借助缓存中的数据仍然可以继续运行。

安全

无论应用是否在网络上开放,安全都无处不在!企业和公共网络环境中的连接越来越多,应用和服务也越来越变得对外开放。应用中的数据传输需要被加密,包括在存储时也应该是密文的。应用需要实现安全认证和授权访问控制,开发者需要掌握安全编码的一些实践。

在有防火墙保护的数据中心,开发者认为网络是相对安全的,所以在数据中心中的通讯不用加密,只需要关注和用户客户端程序或者web浏览器之间的安全通讯。但是对一个云托管应用程序,应用程序和后端组件之间没有防火墙保护,即使这些组件不需要对外部开放,由于本身处于一个多租户环境中,安全也是没有保证的。如果网络是不能信任的,那么开发者就必须对应用程序的通讯以及开放的API进行安全设计。

即使在私有云下部署的应用程序,开发者也不应该低估安全的重要性,因为应用程序很可能日后过度到公有云或者混合云上。安全设计应该作为全流程的考虑,而不应该只是一种事后补充。

  • 传输数据安全。分布式应用依赖网络来发送请求以及接受响应。数据在网络中传输的完整性和安全性需要被保护。正如在公共互联网上传输敏感信息要使用安全协议一样,开发者在云环境中也应该采用同样的方法。网络安全协议,如SSL和HTTPS用于防止网络监听以及中间人攻击。基本上云上的所有网络通信都应该有安全保护。
  • API访问控制。应用组件和服务暴漏的API必须被保护。通过使用API管理解决手段,例如使用OAuth来授权和控制对API的方式。利用这些技术,开发人员可以控制API和它的消费者之间的沟通,只对认证的客户授权。不幸的是API管理并不是大多数公共云所提供的标准特性,开发者必须自行实现自己的安全访问控制(类似OAuth)。
  • 数据存储安全。如果网络是不安全的,那么通过网络访问进行数据存储也是有风险的。应该程序需要保证敏感数据写入存储时是加密的。云服务商可以提供存储加密服务,以保证存储数据被第三方访问的安全性。但是如果秘钥是由云提供商保管,那么可能会保护不足。尤其需要保护个人身份信息,确保信用卡信息以及敏感的个人健康信息不被泄露。

只有在网络通讯、API访问和数据都在控制之下,应用程序才能在云中安全的运行。

位置无关

云托管应用程序和传统应用最重要的一个区别可能就是在云中网络拓扑肯定是会发生变化的。例如,云运营商可能会动态改变网络路由以减少拥塞。一个共享的服务可能会被移动到另一个区域的新虚拟机上,或者由于云的拥塞导致应用程序的组件被重新分布到多个云提供商的网络上。

如果对应用程序的配置进行了地址硬编码,这将会使应用程序非常脆弱,不能很好地适应变化,并会限制应用程序自动弹性伸缩的能力。

在云环境下服务或者组件的实例会随着时间而变化,要充分利用云环境的能力,应用程序不能假设哪些请求应该由哪些固定的服务进行响应,以及不能假设这些服务的网络位置。随着负载的增加,新的实例被弹性伸缩创建出来以处理负载。云服务供应商提供自动弹性伸缩功能,用来根据负载监控和开发人员制定的规则添加或者删除服务实例。针对自动弹性伸缩,负载均衡需要负责分发负载到新的实例上或者让有问题的实例下线。

开发人员应该设计应用程序的架构来支持云的动态特性。一些最佳实践如下:、

  • 不应该有硬编码的配置信息(例如服务的静态IP地址)。可以实现一个发现服务,应用组件可以向它动态查询给定服务的地址。
  • 应用组件应该是无状态的,对应用程序的当前状态一无所知。这时一个实例由于弹性伸缩被销毁不会有任何数据损失。相反,当一个新的实例被启动时,它不应该决定当前的程序状态。
  • 应用程序可以使用例如HTTPS这类协议,让组件间松耦合。
  • 应用程序应该在设计上利用云基础设施提供的自动弹性伸缩功能。

在运维上,考虑下面的建议:

  • 利用API代理去屏蔽最终的API接口,使消费者免受API接口的变化。

弹性扩展

弹性是云计算的基本原则。弹性使得应用程序能够根据负载的变化自动伸缩。这种可扩展性使得对基础设施的利用率更高。应用程序必须进行专门设计才可以支持灵活的可伸缩性。简单地把应用部署到云上并不会获得弹性扩展的能力,为了得到该能力组件需要设计的小,并且无状态。

面向服务架构/组合能力

动态发现的能力使得应用程序可以动态定位组件,而不是静态绑定到某一固定实例上。这种能力对于云中的组件来说十分重要,因为在云中存在各种各样的组件实例,故障是常见的,所以随时会有新的实例弹出来。通过这种发现能力,新的组件启动无需更改任何配置就可以处理负载。

将应用程序分解为许多职责单一的组件,有利于重用、弹性伸缩以及支持敏捷开发。例如,将原来单一的消息存储组件拆分成更小的消息处理组件、消息检索组件和消息查询组件等。通过这种方法,对消息查询组件的改进可以不影响其余消息组件的代码。

可管理性设计

在云环境中,应用程序依赖于基础设施和云服务提供商所管理的服务。通常情况下,提供商的目标与开发人员的目标一致,都想要提供可靠的服务和保证其高可用。然而,提供商并不为开发人员的程序负责。提供商专注于整个共享云服务的整体可用性。例如,云提供商可能关闭某些服务,故意让网络分区以隔离故障,或者在排除故障的过程中故意让基础设施出现错误。所有这些都会影响到开发人员的应用程序。不幸的是,应用程序的所有者并不会拥有云管理员的控制权限,也不会获得像在自己的数据中心中运维团队的运维细节,也没有对云基础设施的解决方案和升级方案的发言权。

鉴于云管理员并不会为开发人员的应用程序来管理和优化基础设施,所以应用程序的所有者需要自行对其管理。这要求开发人员了解云基础设施中正在发生什么。一种方式是编写代码探测及可视化云基础设施内部的问题,例如缓慢的请求或者服务超时。通过这些确保应用程序的所有者可以获得管理和监控数据,以便对基础设施中的错误主动响应。

监控程序可以让程序行为的各个方面可视化,包括性能、故障、资源消耗以及用户事务等。这些监控数据可以让应用程序更容易维护,以及在高度动态的环境中进行问题定位。这些数据应该被记录下来以便定位人员分析问题,或者分析用户行为以提高应用程序的用户体验。

与基础设施解耦

与基础设施解耦是一个一贯推荐的软件工程实践。在云环境中使用云供应商提供的特有接口会导致软件锁定到固定的云供应商,这会导致软件难以或者不可能运行在其它云平台上。通过建立一个基础设施抽象层,可以让你更换底层服务的实现时无需重新应用程序的代码。

在数据中心中,异构性是很普遍的。一个应用服务可能运行在Linux虚拟机上,同时依赖一个运行在windows服务器上的数据库。开发人员根据它们开发和运行的需求来选择应用的平台,系统工程师通常在异构的环境中为不同类型的服务提供标准的配置。

云环境经常是多种多样的,它们提供一组虚拟机镜像,预先配置了特定的操作系统和软件。不幸的是,默认的镜像可能并不符合应用程序的要求。例如,一个Windows镜像可能并没有满足SQL Server要求的操作系统补丁。依赖于某一镜像可能会导致不可移植性,因为不同云提供商之间的镜像可能会存在不一致。这种镜像间微小的差异看似问题不大,但是却很可能引起应用程序运行时错误。

针对镜像的不一致化问题的解决方案是为应用提供自定义镜像,有以下一些可以采用的方式:

  • 在确定的环境下,根据组件的需要提供精确化自定义的虚拟机镜像;
  • 提供一个通用镜像,然后根据启动时的配置让镜像自行定制;
  • 提供一组与应用程序匹配的基础设施配置的首选项,提供给管理员以便他可以为应用程序定制合适的基础设施配置;

运行时自配置可以让应用通过类型patch的方式让实例保持最新的配置,甚至可以个性化配置。例如,实例从标准的Linux镜像启动,然后进行安全配置加载,之后更新特定版本的Java,随后配置与Java版本适合的容器,每一个配置都在前一个的基础上动态进行。这种运行时配置的缺点则是它会在弹性伸缩的时候加长实例的启动时间,造成性能上的影响。

由于广泛采用TCP作为互联网的标准传输协议,将下层的网络异质性进行了屏蔽。应用层的协议如HTTP又提供了进一步的抽象。流行的REST模式建立在HTTP之上,提供了一套方便的通过JSON或者XML更轻量级数据传输协议进行资源描述的方式。REST的一个缺点则是在发送和接收格式里面缺少API的标准设计,例如可以将API的版本放在资源路径中(例如:/api/v1/user),或者当做请求参数(/api/user?version=1),或者作为报文的包头资源(Accept: application/json;apiversion=1)

应用程序在跨不同API提供者的时候不能让API的设计不一致。使用标准协议如HTTP/HTTPS,开发人员应该建立类似REST的API,使得他们的API保持一致性且易用使用。这种标准化的程度将越来越重要,最终将发现和消费的方式进行模式化定义,而不是被硬编码进程序中。

租赁设计

除非最初目标就是作为一个SaaS应用,否则大多数企业应用程序不会将租赁作为其设计的一部分。然而,最佳实践建议对于云计算应用程序来说理解和处理租赁模式是一个不可或缺的组成部分。考虑一组用户想要使用你的应用程序服务,但是他们的数据由于安全性必须被隔离,这会为你的程序带来哪些潜在的影响。

使最终用户自服务

许多企业应用程序需要某种类型的授权,当用户请求接入的时候会给用户按照特定的角色分配权限。这时会发生一些配置过程,创建一类包含用户访问信息的账户。对于客户端应用程序,用户使用自注册工具提供个人信息。同样的,用户开始期望企业应用程序也有相同的能力。

带宽感知

在设计云应用程序的时候很容易假设带宽不是问题。对于单个请求来说,负载和可用带宽比起来确实微不足道。但是对于云计算中的大规模应用程序,每秒成千上万的请求穿越许多服务,消耗的带宽非常可观。

正如前面提到的,云应用程序运行在共享的不受开发人员控制的网络基础设施上。虽然理论上可以预测应用程序的带宽占用率,但是往往可用带宽比预想的要小。与传统的数据中心情况不同,云上缺乏对网络拓扑的控制,开发人员不能假定他们的应用组件之间通过高速交换直接连接在一起。

由于带宽有限,所以需要尽量少用,以下一些技术可以采用:

  • 定义交互较少的协议;
  • 仅针对需要的组件进行精简的答复;
  • 通过使用缓存减少重复的请求;

对于带宽要求很高的应用程序,例如视频流,最好将这些高带宽部分移到云的外面。例如Netflix公司,将视频缓存在内容分发网络(CDN)上而非亚马逊的云端。然而,Netflix也在使用云中做视频转码计算,利用云的弹性可以并行的进行视频编码,在这个过程中网络带宽利用相对较少,因为这个过程是非实时的,即使对计算限制带宽也并不会对最终用户的延迟感受造成影响。

资源消耗感知

云提供商有一个资源消耗的计费模型,对于计算、存储、数据传输以及其它服务进行收费。开发人员设计应用程序架构的时候要尽可能降低成本,例如减少网络中的数据传输规模,采用许多减少成本的技术,例如缓存等等。

最小化传输开销

传输成本主要有两个方面:

  • 在数据传输过程中的打包和解包造成的时间和处理成本;
  • 传递数据的经济成本;

在传统的数据中心中,应用程序的网络流量不会被收费。然而,在云上所有类型的资源消耗都是要被收费的,包含网络数据传输。像亚马逊之类的云提供商对数据传输采用关税制度,根据数据传输的源和目标地址进行统计计费。例如将应用转移到EC2上是免费的,但是要再迁出来就是相当昂贵的。很多云提供商通过收取退出费用将客户锁定到他们的平台上。如果一个应用程序产生大量的数据(例如支持用户上传内容的应用),迁移到其它云服务提供商的费用可能会非常的高。

构建云应用程序的时候,开发人员应该将数据传输的成本作为设计的一个重要因素。对于一个成功的分布式应用程序来说数据传输是一个关键因素。开发人员应该努力减少流量。例如Netflix将视频存储在EC2之外的CDN中是有道理的,CDN中的存储和传输成本比在EC2中将数据发送到外部互联网中的收费开销要少很多。数据传输成本对Netflix非常关键,以至于他们甚至研发了自己的CDN硬件并在经营区域使用自己的CDN服务。

同样,打包和解包的成本也应该被关注。例如选择数据压缩以减少数据传输成本,却会增加额外的CPU开销,处理压缩数据产生的负载可能会需要更多的虚机实例导致成本增加。

一般情况下,开发人员应该创建资源消耗感知的应用程序以便寻求应用程序性能和成本之间的平衡。

“换句话说你需要一个监控应用,它需要设计的高效尽量不去影响被监控服务的行为。另外关注你的监控粒度,是每次API调用,还是每百万次,还是每CPU/hour?”

  • Gartner. “Cloud Characteristics, Principles and Design Patterns.”

开发人员可以使用以下方式减少数据传输开销:

  • 减少负载大小
    • 提供的API只返回客户需要的数据(“partial response");
    • 采用数据压缩,但是权衡编解码带来的CPU成本;
  • 减少数据传输次数
    • 缓存不可变数据;
    • 避免频繁交互的协议设计;
  • 使用监控
    • 跟踪数据的传输过程定位潜在的优化点;
    • 使用负载生成工具人工产生流量去检验优化带来的影响;

posted on 2016-05-28 07:10  MagicBowen  阅读(313)  评论(0编辑  收藏  举报

导航

作者 : MagicBowen, Email:e.bowen.wang@icloud.com , 转载请注明作者信息,谢谢!