赞
踩
好的架构可以成就软件,缺乏架构则会破坏软件。
在典型的Web应用程序中,服务器接受前端的HTTP请求并处理请求。在保存到数据库之前,数据可能会经过多个应用层。最终,后端将生成一个响应——它可以是JSON形式或完全渲染的标记语言的形式——该响应将被发送回客户端(如下图所示)。当然,一旦考虑到负载均衡、事务、集群、缓存、消息传递和数据冗余等元素之后,大多数系统将变得更为复杂。这样的软件大多需要在数据中心或云服务器上运行,而这些服务器需要被管理、维护、修补以及备份。
PaaS是云计算的一种形式,它为用户提供了一个平台来运行软件,但隐藏了部分底层基础设施。
在 SOA(面向服务)架构方法中开发者创建通过消息进行通信的自治服务,并且通常有一个模式或一个契约定义了消息是如何被创建或交换的。服务的可重用性、自治性、可组合性、粒度以及可发现性都是与SOA相关的重要原则。
微服务和无服务器架构与面向服务的架构一脉相承。它们保留了前面提到的许多原则和思想,同时试图解决老式的面向服务的架构的复杂性。
开发者倾向于认为微服务是围绕一个特定的商业目的或能力而建设的小型的、独立的、完全不相干的服务。理论上来说,微服务应该很容易更换,因为每个服务都是用一个适当的框架和语言编写的。
可以说,无服务器架构也体现了许多来自微服务的原则。毕竟,每个计算功能都可以被认为是它自己的独立服务,这取决于你如何设计系统。但是如果你不想,你也不需要全面拥抱微服务。
无服务器架构给你或多或少的自由去应用微服务原则,这取决于你自己,它不强迫你一条路走到黑。这本书展示的架构实例中,单体系统的一部分就被重新实现为无服务器架构,但没有使用所有微服务原则。这就可以基于你的需求和喜好,由你来决定你的架构。
无服务器架构有助于解决分层和修改层太多的问题。开发者可以通过将系统分解为多个功能,并允许前端安全地与服务甚至数据库直接进行通信,来消除或最小化分层,如下图所示。所有这些都可以通过有组织的方式来实现,通过明确定义服务边界,允许Lambda函数自治,并规划功能和服务如何交互,来防止意大利面式的实现和依赖项噩梦。
无服务器方法解决不了所有问题,它也无法消除系统的底层复杂性。然而,如果实现得当,它还是可以为减少、组织和管理复杂性提供机会。
层是一种模块边界,它的存在是为了提供系统主要组件间的隔离。用户看得见的表示层与包含业务逻辑的应用层被隔离开。而数据层是另一个独立的系统,负责数据的管理、持久化和访问。按层划分在一起的组件在物理上可以驻留在不同的基础设施上。
层次是逻辑分片,它们负责执行应用程序中特定的职责。每个层里面可能有多个层次负责不同的功能元素,比如领域服务。
无服务器架构原则:
1.根据需要使用计算服务执行代码(没有服务器)。
2.编写单一用途的无状态函数。
3.设计基于推送的、事件驱动的管道。
4.创建更厚更强大的前端。
5.拥抱第三方服务。
无服务器架构是SOA概念的自然延伸。在无服务器架构中,所有自定义代码作为孤立的、无依赖的而且通常是细粒度的函数来编写和执行,这些函数运行在AWS Lambda之类的无状态计算服务中
Lambda是一种计算服务,它在AWS基础设施上执行用JavaScript(node.js)、Python、C#或Java编写的代码。源代码(Java或C#的话就是JAR或DLL)将被打包并部署到孤立的容器上,该容器有单独分配的内存、磁盘空间和处理器。代码、配置和依赖项的组合通常被称为Lambda函数。
Lambda并不是唯一的计算服务,Microsoft Azure Functions、IBM Bluemix OpenWhisk和Google Cloud Functions可能是你应该关注的其他计算服务。
为Lambda等计算服务编写的代码应该以无状态方式来创建。不能想当然地认为本地资源或进程会在当前会话之后生存下去。无状态性非常强大,因为它让平台可以迅速扩展,来处理数量不断变化的传入事件或请求。
最灵活、最强大的无服务器设计是事件驱动型的。
构建基于推送的事件驱动系统常常可以降低成本和复杂性(不需要运行额外代码来轮询变更),同时可能让整个用户体验更流畅。当然,虽然基于推送的事件驱动模式是一个美好的目标,但它们并非在所有情况下都是适合的或可以实现的。有时候,不得不实现一个Lambda函数来轮询事件源或定期运行。
因为Lambda定价基于请求数量、执行时间长短以及分配的内存量。在Lambda中做的事情越少越省钱。
在线资源之间更少的跳和更低的延迟会得到更好的应用程序性能和可用性。换句话说,你不需要通过计算服务来路由到每个地方。你的前端应该可以直接与搜索提供商、数据库或其他有用的API进行通信。
数字签名的令牌让前端可以与不同的服务(包括数据库)以安全的方式直接进行通信。
总有些秘密无法透露给客户端设备。处理信用卡或向订阅用户发送电子邮件只能由不受最终用户控制的服务来完成。在这种情况下,就必须通过计算服务来协调操作、验证数据,并保证安全。
另外要考虑的重要一点是一致性。如果前端负责写入到多个服务,可是中途出现故障,就会导致系统处于不一致的状态。在这种情况下,就应该使用Lambda函数,因为它被设计成可以优雅地处理错误,并且重新尝试失败的操作。
如果第三方服务能产生价值、减少定制代码,那自然欢迎它们加入。然而,不言而喻的是,当考虑第三方服务时,必须评估诸如价格、能力、可用性、文档和支持等因素。
无服务器方式的一个优势是现有的应用程序可以逐步向无服务器架构转换。如果开发者面对的是一个单体代码库,他们可以逐步地把它进行拆分,然后创建现有应用程序可以与之通信的Lambda函数。
从一个传统的基于服务器的应用程序过渡到一个可扩展的无服务器架构需要时间去调整到位。它需要谨慎而缓慢地推进,开发者需要在开始之前就制定出一个完善的测试计划和一个全面的DevOps策略。
无服务器不是适用于所有情况的银弹。对于延迟敏感的应用程序或具有特定的服务级别协议(Service-level Agreements,SLA)的软件来说,它可能不是很适合。对于企业和政府客户来说,供应商锁定就是一个问题,而服务的去中心化也是一个挑战。
因为Lambda运行在公有云中,所以关键任务型应用程序就不一定要基于它。执行大规模交易的银行系统或病患生命支持系统需要的性能和可靠性超过了公有云系统所能提供的水平。这些组织很可能投入专用的硬件或者使用私有云或混合云来运行它们自己的计算服务,来满足它们对可服务性和可靠性的要求。在这种情况下,应该采用这些架构。
AWS有一个SLA,但只针对某些服务,不适用于此外的其他服务。
SLA(Service-Level Agreement,服务等级协议)在AWS PaaS中被定义为一组保持各项服务质量和连续性的指标,其目的在于通过对PaaS资源/服务指标的连续监控和分析,帮助客户的技术团队及时发现服务质量隐患,并为解决或改善问题提供诊断线索。
从一个单体方式过渡到更加去中心化的无服务器方式也不会自动减少底层系统的复杂性,而且该解决方案的分布式特性将引入其自身的一些挑战。因为需要进行远程调用而不是进程间调用,并且需要处理跨网络故障和延迟。
无服务器架构允许开发者专注于软件设计和代码,而不是基础设施。可扩展性和高可用性也更容易实现,而且因为你只需要为所使用的服务付费,所以定价通常更公道。重要的是,通过无服务器最小化所需要的层次的数目以及代码量,就有机会降低一些系统的复杂性。
诸如服务器配置和管理、修补以及维护等任务将由供应商负责,这有助于节省时间和金钱。
受益于并行处理,计算的无状态和可扩展性可以解决许多问题。使用无服务器架构,CRUD应用程序、电子商务、后台办公系统、复杂Web应用程序以及各种移动端软件和桌面软件的后端都可以被快速地建立起来。
作为一个开发者,不需要使用无服务器架构来替换掉整个后端,如果你不想或是不能这样做。可以使用Lambda来解决特定的问题,特别是如果它们能从并行化中受益的时候。毫无疑问,无服务器系统可以比传统系统更容易地进行扩展。
无服务器技术一个常见的用途是数据处理、转换、操作和转码。
数据的摄入,如日志、系统事件、事务或用户点击,可以使用如Amazon Kinesis Streams(关于Kinesis的更多信息请参见附录A)的服务来完成。
Amazon API Gateway和Lambda(我们已经见过好几次了)的一个创新使用场景是我们所说的遗留API代理。在这里,开发者使用API Gateway和Lambda在遗留API和服务上创建一个新的API层,使它们更易于使用。
Lambda函数可以按计划运行,这使得它们可以有效地用于重复任务,如数据备份、导入导出、提醒以及警告。我们已经看到有开发者使用Lambda函数定期地检测他们的网站,来检查它们是否在线,如果它们不在线就会发出电子邮件或短信提醒。配置完成后就可以忘掉这些任务了。
Lambda函数和无服务器技术的另一个普遍应用是建立机器人(机器人是一个运行自动化任务的应用程序或脚本),这主要是针对某些服务,比如机器人聊天服务。
两个主要架构是计算即后端(compute as back end,即Web与移动应用程序的后端)和计算即胶水(compute as glue,为执行工作流所构建的管道)。
这两种架构是互补的。
很可能出现的情况是,无论你最终工作在哪种现实中的无服务器系统上,你将创建并组合这两种架构。
计算即后端架构展示了一种方式,如Lambda以及第三方服务之类的无服务器计算服务被用于为Web、移动以及桌面应用程序建立一个后端。
在下图中你会注意到前端直接连接到数据库和一个身份认证服务。这是因为,如果前端可以安全地与服务进行通信(比如使用委托令牌,第5章和第9章中会更详细地讨论这一点),我们不需要把每个服务都放在API Gateway后面。这种架构的目标之一就是允许前端与服务通信,在Lambda函数中完成自定义逻辑,并通过REST风格的接口提供对函数的统一访问。
遗留API代理架构是无服务器技术在解决问题方面创新的一个例子。那些拥有过时的服务和API的系统很难在当前最新的环境中使用。它们可能不符合现代的协议或标准,这可能导致与当前系统的协同操作变得更加困难。缓解这个问题的一种方法是在这些遗留服务之上使用API Gateway和Lambda。API Gateway和Lambda函数可以转换客户端发出的请求并直接调用传统服务。
无服务器技术与架构不是一个非白即黑的命题。它们可以与传统系统一起使用。如果现有的一部分基础设施已经在AWS中,那么混合可能就是一种特别有效的手段。
由于REST(Representational State Transfer,表现层状态转移)的已知问题(多轮往返、过度抓取和版本控制方面的问题),GraphQL被设计成REST的替代品。GraphQL试图通过从单一终端节点(例如api/graphql)层次化、声明式地执行查询的方式来解决这些问题。
计算即胶水架构描述了我们可以使用Lambda函数创建强大的执行管道和工作流的想法。这通常会涉及在不同的服务之间使用Lambda作为胶水,协调并调用它们。
借助这种架构风格,开发者就可以聚焦在管道、协调以及数据流的设计上。
Amazon Kinesis Streams是一种可以帮助处理和分析大量流数据的技术。这些数据可以包括日志、事件、事务、社交媒体摘要,几乎所有你能想到的东西。这是一个持续收集可能随时间变化的数据的好方法。对Kinesis Streams来说,Lambda是一个完美的工具,因为它能针对需要处理的数据量而自动扩展。
模式是软件设计问题的架构解决方案。其设计目的是解决软件开发中常见的问题
可以设计一个由特定Lambda函数控制并调用其他函数的系统,可以将其连接到API Gateway或手动调用它,并将消息传递给它以让它可以调用其他Lambda函数。
在软件工程中,命令模式被用来“将请求封装为一个对象,从而使你可以通过不同的请求、队列或日志请求来对客户端进行参数化,并支持可以撤销的操作”。
因为“只需要向对象发起请求,而无须了解请求中的操作或者请求接受方的任何细节”。
命令模式允许你将操作的调用方与执行处理的实体进行解耦。
消息传输模式在分布式系统中非常流行,因为它们允许开发者将功能和服务之间的直接依赖解耦,同时允许在队列中存储事件/记录/请求,从而构建出可扩展的健壮的系统。可靠性来自于这样的一个事实,即如果消费方服务离线了,消息将保留在队列中而且稍后仍可以得到处理。
此模式的主要特点是一个消息队列,发送方可以发送消息到这个队列而接收方可以从这个队列中获取消息。
扇出是消息传输模式的一种类型。通常,扇出模式用于将消息推送到一个特定队列或消息管道的所有侦听/订阅客户端。
管道和过滤器模式的目的是将一个复杂的处理任务分解成一系列可管理的、离散的服务,并将它们组织成一条管道。设计用于转换数据的组件习惯上被称为过滤器,而将数据从一个组件传递到下一个组件的连接器则被称为管道。
只用为Lambda提供函数代码,Lambda会按需执行。你不知道它是如何执行的,也不知道在哪里执行。你不用在虚拟机上费心,也没有服务器机群容量、太多服务器空载、服务器不够或者集群扩展这些烦心事。你不可能分配过高或者过低的Lambda执行能力。它就只是你想要的计算功能,而Amazon只会在它执行时收费。这就是Lambda和诸如Azure Functions、Google Cloud Functions和IBM OpenWhisk这些类似的无服务器计算服务对计算而言是巨大进步的原因,就像S3之于存储一样。
一些人更喜欢用缩写FaaS(Function as a service,函数即服务)来描述像Lambda这样的技术。
无服务器是一个包含了FaaS的概括性术语,而FaaS只是无服务器技术和架构必须提供的众多能力中的一种(尽管它是非常重要的一种能力)。
Lambda函数在一个容器(沙盒)中执行,它隔离了其他函数并分配资源,诸如内存、磁盘空间和CPU。对Lambda的高级用法而言,理解容器重用十分重要。当函数第一次初始化的时候,一个新的容器会被初始化而函数代码会被加载(第一次加载时,我们说函数是冷的,冷函数)。如果函数再次运行(在一定时间内),Lambda可能重用同样的容器并跳过初始化的过程(我们说函数现在是暖的,暖函数),这样可以让代码执行得更快。
应该尝试着减少冷启动(函数很久没有运行且需要全面初始化)来让应用程序响应更迅速。如果常常遇到冷启动的情况,可以试试下面这些步骤来提升性能:
1.调度函数(使用定期的事件)让它周期性地运行来保持函数是暖的。
2.把初始化和设置代码移出事件处理程序。如果容器是暖的,这些代码就不用运行。
3.增加分配给Lambda函数的内存。CPU份额受分配给函数的内存影响(成正比)。函数拥有的内存和CPU份额越多,它的初始化就越快。
4.尽可能减少你的代码。去掉不必要的模块和require()导入语句,减少引入和初始化模块有助于优化启动的性能。
5.试试其他的语言。Java的冷启动时间最长。也许未来会发生变化,但如果你注意到使用Java时冷启动时间较长,试试其他语言。
无服务器架构是全能的。可以使用它构建整个后端,或者把一些服务黏合在一起来完成一个特定的任务。构建一个正确的后端需要开发位于客户端和后端服务之间的应用编程接口(API)。在AWS里,API Gateway就是那个让开发者可以创建REST风格API的关键AWS服务。
API Gateway被认为是后端服务(包括Lambda)和客户端应用程序(Web、移动或者桌面)之间的一个接口。
前面我们提到,你的前端应用程序应该直接与服务通信。但是考虑到安全和隐私,在很多情况下这是不可能或者不可取的。有些操作只应该由后端服务执行。例如,给所有用户发送一封邮件这个操作就应该通过Lambda函数来完成。你不应该在前端做这件事,因为这需要在某个用户的浏览器中加载每个用户的邮件地址。这是很严重的安全和隐私问题,会导致你的用户迅速流失。所以不要信任用户的浏览器,也不要在其中执行任何敏感的操作。对于那些可能让你的系统处于不正常状态的操作来说,浏览器也是执行这些操作的糟糕环境。相反,应该在后端Lambda函数执行操作,操作完成时再通知UI呈现出来。
无服务器应用程序比传统的基于服务器的竞争对手更容易创建和维护,API Gateway就是成就这种优势的技术的例子。在一个传统的系统里,你可能需要配置EC2实例、使用ELB(Elastic Load Balancer,弹性负载均衡器)配置负载均衡,并维护每一台服务器上的软件。API Gateway省去了这些操作,它可以分分钟定义出API并连接到服务。
Amazon的Simple Storage Service(S3)是一项可靠的文件存储服务。它于2006年3月推出。
Firebase是由Google开发的一个平台,它集合了多个产品,包含数据库和身份验证、消息传递、存储和托管等服务。
Firebase数据库是一个实时、无模式,托管在云端的NoSQL解决方案(接下来的Firebase指的是数据库而不是整个平台)。Firebase可以通过HTTP(使用WebSockets)将数据同步到客户端,这体现了它的实时性,而在客户端断开重连后也要同步数据。Firebase将数据存储为JSON,这使得理解和编辑变得简单。然而,简单也会带来局限性。Firebase在数据结构化和查询方面显得不够灵活。这会带来很多冗余,而这是作为一个默认非规范化数据库的天然副作用。
在一个纯粹的微服务架构中,每个服务都有其自己的数据存储。这样做的一个好处就是,一个微服务的数据库模式的变化不会影响到另一个服务。各个开发团队可以拥有自己独立的微服务,实现、部署和进展会更加快速。另一个好处是,开发者可以针对每个微服务的自身需求,选择合适的数据库和存储机制。
毫无疑问,迁移到真正的微服务架构会引入其自身的一些挑战。我们需要一种方法来同步数据并回滚错误。最终一致性需要解决,并发更新必须协调一致,可能还要考虑实现事件溯源。
DNS能够将可读的主机名如www.google.com解析成IP地址。当需要对DNS进行更改时,因为需要更新权威域名服务器以及刷新缓存DNS服务器,这可能需要一点时间来进行传播。某些客户端可能比其他人更快地获取最新数据,但最终,所有的客户端都会得到同样的结果(系统将收敛)。DNS就是最终一致系统的一个示例。最终,所有请求都将得到相同的数据。
AWS指出:“最终一致性读取可能不会反映最近完成的一次写入操作的结果。所有数据副本的一致性通常可在一秒钟之内达成。在短时间之后再次读取,应返回更新后的数据。
理解最终一致性是理解分布式系统和如何建立微服务架构的关键。如果正在建设一个有多个微服务和数据存储的分布式应用,最终都会面对的情况就是,某些服务比其他服务拥有更多的最新数据。但如果系统已经拥有合理的架构,一切最终都会收敛,经过一段时间所有服务都应该拥有相同的一致的数据。
请注意,在图10.1中,服务是相互耦合的。Checkout(结账)服务必须要知道其他服务以便调用它们。它还必须等待它们的响应才能终止。如果你在构建微服务,而服务之间有一个紧耦合(也就是说,一个服务必须发起同步API的调用并等待回应),你可能要重新考虑你的方法。微服务之间的紧耦合可能会限制它们开发和部署的速度。
图10.2展示了一个更加解耦的方法,在这里使用Kinesis流作为一个消息传递机制。在这个例子中,服务并不需要了解对方,但是它们必须订阅像Kinesis流这样的消息系统来接收事件或消息。在这种情况下,服务上的工作变得更容易,因为团队可以独立开发和发布微服务。另一方面,在这种情况下,故障恢复和错误处理可能会更为棘手。如果一条消息成功地更新了一个服务中的数据库,而在另一个服务中失败了,那么如何回滚呢?如果数据在多个服务之间不同步,你又将如何处理这种情况?
回头看一下图10.1和图10.2,想象一下顾客购买产品的情景。交易执行了,但不幸的是客户微服务遭遇了一个灾难性的失败(宕机)。交易必须中止,其他任何服务和数据库的改动必须回滚。系统必须能够自动恢复,特别是如果其他服务在假设整个交易成功的情况下,已经更新了它们的数据。在图10.1中,一个总体的结账服务也许能处理这个问题,可以调用所有它有过交互的服务让它们回滚。但是在图10.2中呢?没有一个总体的服务负责交易,一切都是分布式的。
要解决这个问题,可以尝试的一种方法是创建一个错误处理微服务。如果出现问题,此服务可以通知或回滚其他服务。你的架构中的每个服务都需要一个方法来通知错误处理服务,同时有一个方法将正确的上下文信息传递给它(关于发生的事情)。图10.3展示了一个包含错误处理服务的架构案例。
死信队列(Dead Letter Queue,DLQ),死信就是无法被消费的消息,死信队列(Dead Letter Queue)本质上同普通的Queue没有区别,只是它的产生是为了隔离和分析其他Queue(源Queue)未成功处理的消息。
Lambda支持死信队列(Dead Letter Queue,DLQ)的概念,它可以帮助你从故障中恢复出来。每当Lambda未能成功处理一个事件(并且已经完成了默认的重试次数)时,它将自动把消息发送到死信队列,它可以是一个SNS主题或一个SQS队列。可以写一个Lambda函数从这个SNS主题或SQS队列中读取消息,并执行相关的补偿操作、发出警报等。
主动监控或看门狗(watchdog)服务是系统中的监管者,它可以检测并主动解决问题。你的微服务可以定期发送包含状态信息的数据到你的主动监控(或者看门狗可以自己探测并监控服务)。
如果看门狗判断出有问题发生,它可以执行补偿动作。其中一个例子就是数据同步。不时地检查不同服务之间的数据是否一致是值得去做的事。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。