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

云应用设计模式

下面的章节详细介绍了一些设计模式,这些现有的设计模式能够有效地应用到云服务应用程序设计中去。算法

电路开关

电路开关(Circuit Breaker)设计模式由Michael Nygard所提出,Netflix将它做为云服务的一部分进行了进一步的发展。在该模式下,一个“电路开关”被插入在发送请求和处理请求的组件之间。就像在一个电路中,一个开关有两个状态:闭合或者断开。当电路开关闭合则整个电路激活,能量被传输;对于软件来讲,请求被发送到正常处理它的组件上。当电路开关断开则意味着正常路径被打断,则系统执行一个备份的代码路径。电路开关用于检测电路中的故障而后使电路倒换到一个安全的备份状态。如下条件能够触发软件电路断开:数据库

  • 一个到远端服务的请求超时;
  • 远端服务的请求队列满了,意味着远端服务不能处理更多的请求了;

这些因素能够设计一个错误率做为阈值,当阈值超过期,电路开关进入到断开状态。设计模式

当电路开关断开,组件转移到备用状态,有三种备用状态的策略:api

  • 失败透明。经过设计一个备用API的方式产生一个替代响应,例如该方法能够根据缓存构建返回响应或者根据默认值返回一个响应。
  • 失败静默。对请求返回空值。在返回值内容对于呼叫服务并不关键的场景是有用的。
  • 快速失败。产生一个包含错误码的响应,这意味着呼叫服务必须处理这些对用户来讲有意义的错误。

理想状况下,组件最好应该失败透明,但实际上每每不是总能作到这点。缓存

注意电路开关和根据条件进行响应(“若是请求超时,则返回错误码500”)并不一样:安全

  • 电路开关的触发条件是随着时间变化的,根据一个滑动窗口,而不是一个单一的失败;
  • 当电路开发触发后会进行保持,备份状态会持续到开关被重置;
  • 电路开关的状态组件外部是可见的,工具能够看到电路开关的状态以便进行故障处理;
  • 电路开关能够由组件外部进行控制;

在Netflix,电路开关周期性地让部分请求经过,若是请求处理成功,则开关重置,全部的请求都容许经过。下图描述了电路开关的逻辑:服务器

Netflix经过仪表盘将电路开关的状态可视化出来。这使得组件状态更加一目了然,比传统的使用异常码的组件更加容易状态可视化。网络

下面的截图显示了一个电路开发服务在仪表盘中可视化的样子:架构

** 电路开关模式 **
如下是电路开关设计模式的要点:并发

  • 优势
    • 加强容错性
    • 对于组件失败可视化(经过对电路开发状态的可视化)
  • 使用场景
    • 为了减小因为网络或者虚机实例失败形成的超时或者延迟
    • 为了不错误级联以及复杂的上游错误处理(失败透明,失败静默)

请求队列

请求队列设计模式包含一个组件,将请求消息(或者任务)放入一个或者多个待处理队列中。计算节点从队列中取出请求消息进行处理。队列做为请求和处理服务之间的缓冲区能够防止重负载致使的服务失败或者超时。这种模式是生产者消费者或者基于队列的负载均衡模式的一个变种。

将请求放入队列,能够方便负载被分布的计算集群进行处理。若是一个计算节点失败,其它的工做者能够继续处理队列中的请求。这提供了分级的容错性能够确保云计算应用程序的高可用性。

消息队列能够充当阀门的做用,能够经过阻止请求进入控制服务的资源消耗。若是某个服务超过了每秒给定的消息配额,能够阻塞后续的请求进入该服务。对于进入的请求在入队前能够应用性能计数器,一旦某一客户端的请求超过了云应用程序的配置阈值,则后续的消息被阻塞。在互联网应用中,能够给客户端发送HTTP 429错误码(含义:请求过多)以及经过报文头中的Retry-After指示多久服务没法响应客户端的请求。下图用Microsoft Developer Network描述了队列是如何有效地进行服务间的负载均衡的。

** 消息队列模式 **
下面是消息队列设计模式的特色:

  • 优势
    • 增长容错性以支持高可用
    • 对于高访问的API能够提高性能
  • 使用场景
    • 须要管理应用程序的故障转移(容错)
    • 对于高访问的API下降负载
    • 将阀门机制做为自动伸缩的替代策略

合并请求

合并请求设计模式将对同一API的相邻请求进行缓存合并。例如,播放视频的网络应用程序须要从数据中心得到视频的元信息(例如片长),传统的实现中每一个用户单独发送请求获取视频的元信息,利用请求合并,必定时间间隔内的请求能够并合并成一个,这样减小了网络带宽消耗以及API的负载,使得API端能够水平扩展支持更大量的并发用户。

为了合并请求,对API使用一个代理(Proxy)将给定时间窗(例如10ms)内的请求进行排队,周期地将队列内的消息合并成一个请求发给API。响应消息则并发地分发给全部请求者。这种优化对高并发的请求颇有意义。若是队列中只有一个消息,它也得等够时间窗的时间长度才能发送,这会引入必定的延迟可能会影响性能。通常请求会落在时间窗内的任何位置,因此引发的平均时延等于时间窗的一半(对于10ms的时间窗,平均延迟为5ms)。下图给出请求合并的一个例子:

对象变动通知

传统的紧耦合的软件架构中,系统组件间的关系是固定的。例如两个组件A和B存在依赖,当A变化的时候须要通知B,这时A知道B只有一个实例,同时也知道B的位置。若是B因为某种缘由不可访问了,A没法将通知发送给B,这时A必须实现某种机制去处理这种场景。A和B之间的这种依赖致使了系统存在单点失败,影响系统的可扩展性。

分布式系统中若是存在许多这样的静态依赖,每个都会下降系统总体的弹性。对该问题的一个解决方式是为架构引入冗余,用一对多的关系代替组件间的一对一关系。例如A再也不依赖于固定的B,而是存在B的多份实例,任意B的失败给A带来的影响能够忽略不计。

对象变动通知模式,也被称做观察者模式,在这种处理模式下使用能够加强系统的弹性。

该模式下,通知组件(A)实现一个可观察(Observable)的接口,观察者(B)经过该接口注册本身感兴趣的变化。当A发生一些变化,它则将变化通知给全部的观察者们。观察者能够经过变化通知消息直接获取变化内容(push mode),也能够根据事件内容再去向被观察对象请求变化细节(pull mode)。

云环境下该模式可使得应用程序具有弹性扩展的能力。由于组件再也不是紧耦合,额外的实例能够动态的添加以处理增长的负载。这对于计算或者I/O密集型的任务作并行化颇有价值。例如Netflix为了适应不一样终端设备和网络带宽,须要将视频转换成不一样码率的好多版本。利用对象变动通知模式,当第一个新的视频加载到存储时,一个代理检测到存储设备发生了变化则触发产生一组转码任务去并行处理不一样码率的视频版本,当计算完成,这些转码任务自行关闭。

** 对象变动通知模式 **
下面是对象变动通知模式的要点:

  • 优势:
    • 提升容错性
    • 增长可扩展性
    • 更高的资源利用率
  • 使用场景
    • 为了将计算密集型任务并行化
    • 为了减小单点失败
    • 为了减小组件间的耦合以即可以替换组件的实现

服务发现

在分布式应用中,组件须要知道对端服务地址才能向对端发送请求。在传统的分层架构中,服务的主机名存在配置文件中。DNS用来查询主机的实际IP地址。为了可扩展性,IP能够指向一个负载均衡使得负载能够分发给多个服务实例。

云环境具备高度的动态性,服务和组件实例不断的因为负载的变化或者故障缘由而产生和消失。在云中基于DNS的负载均衡可能会将请求转给不存在或者已经失败的服务实例。解决方案是实现一套云服务的发现机制,只有正常的服务实例是可被定位的。这样的服务发现机制应该具有如下能力:

  • 提供给组件一种直接将请求发给可用实例的方式
  • 支持服务实例的动态注册和注销
  • 对于给定实例能够查询它的状态

Netflix实现的服务注册“Eureka”具有上述能力。它是一个分布的服务,同时嵌入在应用程序的服务组件和客户端中。

以下图,Eureka服务维持了一组健康服务的注册列表。注册的服务须要按期发送心跳更新它们的状态。若是和已注册服务之间链接失败,则发现服务在一个超时周期后将它从注册列表里移去。当新的服务启动的时候,它们向Eureka注册,一样当实例终止或者失败他们自动的从注册中去掉不会影响别的服务。注册机制意味着只有准备好处理消息的服务才会在注册列表里面,正在上线过程当中的服务不会。

每一个客户端组件内嵌了Eureka的客户端程序,它维护了一个从Eureka服务端拷贝下来的注册服务的一份缓存。经过这种方式可用服务的知识在整个环境中备份着,这自然为系统提供了容错性,避免Eureka服务端故障致使网络不可用。Eureka客户端根据某种算法将请求负载均衡到不一样的处理服务上,默认的负载均衡算法是轮询,能够对算法进行修改。

实际上,Eureka将决定请求发送给哪个服务的决策放到每个客户端,将这种行为封装在客户端的代码库中,服务端开发者则下降了对服务端不可用的设计。由于注册列表中的同类服务会轮流处理请求,Eureka要求服务是无状态的。这是可扩展性和弹性的先决条件,是云应用架构的广泛原则。

由于客户端组件缓存着可用服务实例,因此它们可以在Ereka服务端故障的时候继续运行。当分布式环境跨越多个数据中心或者云,这时网络发生分区故障后每一个区域的Eureka服务能够继续运行,由于服务的注册信息在每一个区域都存在冗余。

** 服务发现模式 **
如下是服务发现模式的要点:

  • 优势:
    • 提升容错性
    • 简化基础架构的管理
    • 简化动态环境的应用程序开发
  • 使用场景:
    • 在须要支持自动扩展的场景

微服务

微服务设计模式中,一个单体(monolithic)服务的功能被分解成一系列良好设计的单一职责的微服务。例如,邮件消息系统中的提供消息建立、读取、更新和删除的消息存储服务,能够将其每一个功能分解成一个独立的服务,其中读取服务仅提供读取消息的功能。微服务有不少优势,包含更好的弹性和性能、更高的可靠性以及易于部署。

弹性的提升是由于微服务下每一个小的服务均可以独立的伸缩。例如,若是消息的读取远大于写的请求,这时额外的读服务能够被建立来消化负载。而在原来消息存储做为一体的状况下,整个服务都须要复制,这时水平扩展就会引发没必要要的资源浪费。

微服务简化了软件的开发和部署。软件开发人员能够拥有一个微服务完整的开发和独立发布的权利,若是消息读取服务的开发者改进了消息的缓存功能,则能够直接将此发布到生产环境而不用与其它消息存储功能进行集成。这种解耦的特性开发使得开发人员能够按照本身的时间表并行工做。

"部署到云上最好的方法是什么,最好的方法是编写可重用的代码,咱们如何快速响应业务变化?答案彷佛是微服务。经过建立小的独立的服务,程序能够专一将一件事作好 - 咱们减小了开销,增长了可伸缩性和弹性。开发成百上千的小的程序,去除过多的耦合,促使开发人员从新审视与重构架构,最终建立能够快速响应的、具备弹性的云应用程序。"

  • Michael Forhan, AppFirst blog: “Lessons from Distill”

Netflix已经开发了一套微服务升级部署的方法。这种方法中再也不一次升级服务的全部实例,而是部署新的版本的一个实例进行冒烟测试。若是新的版本失败了,对整个系统的影响很小,开发人员能够修复问题快速的从新部署。若是微服务的新版本运行正常,一组新的实例则被建立分担现存在实例的负载。新的实例会被密切监控几小时以便确认没有内存泄露等其它问题。若是发现存在问题,则将负载从新路由回旧的实例。若是新的微服务版本最终功能一切正常,旧的实例则会在必定时间后自动关闭。Netflix经过持续交付的方式将这一过程自动化,从而加快部署,实现了快速迭代,并且下降了错误的可能。Nerflix采用了一套简单的哲学:错误不可避免,可是须要可以快速恢复而不影响总体服务。

微服务的另外一个好处是若是发生故障,很容易定位出故障的缘由,由于错误范围每每内聚在微服务的边界内,容易定位出哪次升级致使了故障。

** 微服务模式 **
下面是微服务设计模式的要点:

  • 优势:
    • 减小升级部署的负担
    • 减小因为服务共筑引发的反作用
    • 提升弹性
    • 对于问题根源的可视化程度更高
    • 提升开发效率
  • 使用场景:
    • 在大的系统中,须要大规模的弹性和成本效益
    • 为了消除单点失败
    • 为了改进性能
    • 为了支持对关键系统的频繁升级

服务无状态化

无状态要求服务不保存每一个客户请求之间的客户端状态,相应的每一个请求消息必须携带服务须要处理的全部上下文信息。这一设计模式对于分布式系统提供了不少的好处,包括:

  • 可靠性。可靠性体如今一个客户端能够对一个失败的请求进行重试,而不用服务端重构失败前的状态;
  • 可扩展性。无状态化能够经过几个途径提升可扩展性。由于服务端实例不存储状态,任何实例均可以处理任何客户端的任何请求,随着请求负载的增长,在不影响现有实例的状况下能够动态地启动其它实例来进行负载分担。此外因为服务端不存储任何客户端状态,服务的资源消耗相对轻量级,能够提升总体资源利用率使得每一个服务端能够支持更高的请求负载。
  • 可视化。从运维的角度看,每一个请求和响应消息的净荷中包含了全部交互信息,这使得能够容易使用请求代理等方案,对请求的语义具备更大的可视化空间。
  • 简单。当服务再也不存储状态,则就没有必要管理请求在屡次事务间的数据。无状态服务的实现也相对容易,更容易调试。无状态服务可预测性更高,因为巧合引发的bug也会被消除。

无状态服务的缺点则是消息的负载会比较大,由于消息中携带了全部服务端须要处理它的上下文。然而无状态设计总体带来的优势要比缺点更多。

** 无状态服务模式 **
下面是无状态服务设计模式的要点:

  • 优势:
    • 增长可靠性
    • 增长可扩展性
    • 提升了请求消息管理和监控的可视化
    • 实现简单
    • 相比状态敏感的服务bug更少
  • 使用场景
    • 任何须要高扩展性的分布式系统

配置服务

传统应用程序的运行时配置一般依靠应用的文件系统上的一个多个文件。在某些场景,能够对这些文件进行运行时修改(也就是说文件被热加载)。更通常的场景下这些配置文件在应用启动的时候进行加载,这意味着配置修改须要从新启动或者加载应用程序,这时该过程会致使应用停机以及付出额外的管理成本。

一般这些配置文件被存储和管理在应用程序本地,致使应用程序部署的每一个节点上都须要冗余地存储配置文件。当应用程序的集群增加,每一个节点配置文件的管理开销开销也随之增加。

外部配置存储

将应用程序的配置文件持久化在外部数据存储区中,为云计算应用程序的配置提供一个中心化的管理仓库。将配置移出本地应用实例,可让跨越应用程序的配置管理和共享变得容易。外部配置数据存储能够能够在不一样的运维环境中变化。

数据库或者文件仓库能够提供对配置文件读取和写入的最大灵活性。敏感信息如密码等能够在应用程序动态构建的时候动态注入,或者经过应用程序启动时动态加载的环境变量进行加载。下图说明了外部配置存储和应用程序加载过程。

运行时重配

良好设计的云计算应用程序应该具有高可用性,尽可能减小停机时间。任何须要应用程序从新部署的配置变化都会增长停机时间。运行时重配须要应用程序可以检查配置变动从而在运行时从新加载配置。

支持运行时重配须要应用程序源代码设计可以处理配置变动通知消息。通常这会经过观察者模式来实现。应用程序向中心配置服务注册配置变动消息。当通知消息被触发,例如新的配置文件上传,应用程序将获得通知并得到最新的配置,将其加载到内存。

Apache Zookeeper是Apache软件基金会用来支持运行时重配的开源项目。ZooKeeper提供了一个集中服务用来管理配置信息、命名、并提供分布式同步和组服务。ZooKeeper能够被用做共享配置服务,也就是用来作集群协调。

使用ZooKeeper来存储配置信息具备两个主要的好处:

  1. 新的节点能够接收到如何链接到ZooKeeper的指令,而后从ZooKeeper上下载全部的配置信息同时决策它们在集群中的合适角色。
  2. 应用程序能够订阅配置的更改,在运行时容许经过ZooKeeper客户端得到最新的配置完成云计算应用程序的集群行为修改。ZooKeeper运行在称做“ensemble”的服务器集群上进行应用程序的数据状态共享,它能够协调云计算应用程序集群的分布式一致性。

** 配置服务模式 **
下面是配置服务设计模式的要点:

  • 优势:
    • 对于配置管理的独立集中的数据存储;
    • 减小应用程序配置管理的负担;
    • 减小应用停机;
  • 应用场景:
    • 任何须要应用程序配置的分布式系统;
    • 须要减小停机时间的高可用场景;

受权模式

在云环境下,不能假定网络是安全的,由于不少因素都不在应用程序全部者的控制下。这意味着第三方可能监听或者重定向传输的数据。另外的风险是API可能会被多租户云环境中的其它租户访问,或者被外部互联网进行访问。这种暴漏性致使了风险,API可能成为DoS攻击的对象,或者致使信息泄露以及使服务遭到恶意破坏。云应用程序更容易受到这些攻击,由于它们是由互相依赖的服务组成,每个网络依赖的接口都会增长应用程序的风险。解决方案是为网络流量进行加密以及为API提供受权保护机制。

API受权

API受权保证了只有被信赖的API客户可以访问API,防止服务被恶意访问,还能够支持对不一样客户端分配不一样的权限。例如,一些客户端仅被受权只读权限,而其它有读写权限。受权还能够用来限制哪些客户能够访问服务。例如,数据库的API能够经过访问限制来确保客户端使用合适的数据访问权限。

应用程序一般使用用户名和密码来进行认证。这种方法有两个主要的问题:

  1. 客户端须要保存用户名和密码,这使得帐户信息可能被泄露,而密码机制则是出了名的脆弱的。
  2. 管理每一个组件的用户名和密码增长了复杂性和管理开销以及配置错误的风险。

OAuth2.0协议(RFC6749)引入了基于令牌进行访问控制的概念来解决这些问题。在OAuth,受权服务器给客户特定的令牌来提供特定的访问权限。

以下图所示,对于OAuth控制服务:

  • 客户端向受权服务请求对待访问服务的认证令牌(1);
  • 受权服务校验身份验证凭据,若是有效返回访问令牌(2);
  • 客户端在全部对服务端的请求中携带访问令牌(5);
  • 对每个请求,服务端经过受权服务检查令牌的合法性(6);
  • 受权服务对被访问服务提供和令牌相关联的一组权限(7);
  • 服务端使用这组权限来决定每一个请求的操做权限,执行相应的动做而后给客户端返回响应数据(8);

除了在每一个请求中传递令牌,为了安全性还会给令牌设置生命周期。当令牌过时后,客户端使用一个刷新令牌来请求一个新的访问令牌。

** 受权认证设计模式 **
下面给出了受权认证设计模式的要点:

  • 优势:
    • 提升安全性;
    • 细粒度的服务访问控制;
  • 适合场景:
    • 对于全部客户端和服务端之间的请求;
相关文章
相关标签/搜索