微服务与SOA架构(1)

基于服务架构的世界

微服务和SOA都被认为是基于服务的架构,这意味着这两种架构模式都非常强调将“服务”作为其架构中的首要组件,用于实现各种功能(包括业务层面和非业务层面)。微服务和SOA是两种差异很大的架构模式,但是他们仍有一些相同的特征。 所有基于服务的架构的一个共性是他们一般都是分布式架构,也就是服务组件都是通过远程访问协议来实现的,例如REST、SOAP、AMQP、JMS、MSMQ、RMI或者.NET Remoting。相对于单体式架构和分层式架构,分布式架构有很多优势,包括可伸缩性、解耦能力以及对开发、测试和部署的可控性等。分布式架构中的组件更趋向于自包含,因此其变更管理和维护也更容易,从而使得相应的应用也更稳定,响应也更快。分布式架构也非常适用于各模块之间耦合度较低、更加模块化的应用。 在基于服务的架构语境中,“模块化”指的是将应用的各个部分分别封装为自包含的服务的做法。拆分后的每个服务都可以单独设计、开发、测试和部署,与其他组件或服务之间的依赖性很低甚至没有。模块化的架构还支持用重写的方式来维护组件的做法。随着业务增长,架构可以逐渐地、以很小的部件为单位进行重构或者替换,而不是大张旗鼓地对整个应用进行重构或者替换。 不幸的是,凡事都有代价,享受分布式系统的优点也一样。与优点相伴的缺点则是复杂性的增加和投入的增长。维护服务合约、选择正确的远程访问协议、处理不响应的或不可用的服务、加密远程服务和管理分布式事务,这些还只是构造基于服务的架构时许多复杂问题中的一部分。本章中,我会描述这些与基于服务的架构有关的复杂问题。

服务合约

服务合约是服务提供者(通常是远程的)和使用者(客户)之间使用合约语言(XML、JSON、Java Object等)约定数据输入和数据输出的一份协议。创建和维护服务合约是一项困难的工作,不应该被轻视或者当作补充条款。因此,服务合约的议题在基于服务的架构设计中值得特殊关注。 在基于服务的架构中,存在两种服务合约模型可供使用:基于服务的合约和客户驱动的合约。两者之间的真正差别在于合作程度。在基于服务的合约中,服务是合约的唯一拥有者,一般可以在不考虑服务客户需求情况下演化或修改合约。这种模式强迫服务的所有客户都要接受新的服务合约变更,而不管客户是否需要这些新功能。 与之相反,客户驱动的合约所基于的是服务和与服务客户之间更为密切地合作的一种关系。在这种模型下,服务拥有者和客户有很强的合作(关系),因此任何服务合约变更会充分考虑客户的需求。采用这种模型时,服务(拥有者)一般需要了解客户是谁以及每个客户都是如何使用这些服务的。客户可以对服务合约随意提出变更建议,服务方则可以根据是否影响其他客户而自行决定是否采纳。理想情况下,服务的客户向服务拥有者发起修改建议和测试用例,测试被执行时可以监测该修改是否影响其他客户。开源工具如Pact和Pacto可以帮助维护和测试这类客户驱动的合约。 服务合约的上下文中另一个重要议题是合约的版本。我们必须接受这一现实——在服务和服务客户间实现绑定的合约终究是要变化的。改变程度和范围取决于这些变更如何影响每个客户,以及合约改变后服务的向后兼容性。 合约的版本化使得启用新的、包含合约变更的服务功能时能够为仍旧使用老版本合约的客户提供向后兼容性。从开始开发之前就要为合约的版本化做出规划,即使开始你觉得不需要,到了最后一定需要,这也许是本章最重要的一条建议。有很多开源和商业版本的框架可以用来帮助管理和实施合约版本化策略,不过你也可以选择使用两项基本技术来实现自己的合约版本化策略:同质版本化和异质版本化。 同质版本化是指在同一个服务合约中使用合约版本号。图1-1中,被客户A和B同时使用的合约都用圆形(代表同一服务合约)来表现,但是其中各自包含着不同版本号。举一个简单例子,假定合约是基于XML的,用来表示某些商品的订单,采用合约版本号1.0。现在新版本1.1发布了,其中包含了额外字段用于提供当订单派送而接收人不在家的时候的交货规则。这种情况下,可以通过让新的delivery-instructions字段成为可选字段来保持向后兼容版本1.0的合约。

图1-1

异质版本化则涉及对不同类型合约的支持。这个技术跟本节之前描述的客户驱动的合约很相似。服务引入新的功能特性时,就引入新合约用来支持这一(些)功能特性。注意,图1-1与1-2之间不同在于表示服务合约的形状。1-2中,客户A使用圆圈所代表的合约,而客户B则使用三角形所代表的合约。在这种情况下,向后兼容性是由不同合约而不是同一合约的不同版本所支持的。许多基于JMS的消息系统都是采用这种方式,尤其是使用ObjectMessage消息类型的系统。例如,基于Java的接收端可以使用instanceof关键字来检查通过消息发送的对象类型,然后根据对象类型来采取响应的步骤。或者,XML负载可以通过JMS的 TextMessage来发送(每个不同合约包含完全不同的XML schema),由一个消息属性来指明XML schema和XML类型负载之间的对应关系。

图1-2 合约版本化的主要目的就是提供向后兼容性。谨记,服务必须支持多个版本的合约(或者多个合约),这样才能使得开发团队能够更快地部署新的功能特性或者其它变更,而不必担心破坏与其它服务客户之间的现有合约。需要注意的是,这两种技术也可以组合起来,支持不同合约类型的多个版本。 最后一个关于服务合约中变更合约需要注意的是:一定要从开始就制定一个明确的服务客户沟通策略,以便客户能够及时获知合约变更的信息,或者特定合约类型不再被支持的信息。很多情况下,因为内部/外部客户很多,这类沟通并不可行。这时候,一个集成中心(integration hub),或者说消息中间件,可以在服务的客户与服务之间建立抽象层,实行服务合约的转译。我将在后续“合约解耦”一节种谈到这种可能性。

服务可用性

服务可用性(Availability)和服务响应能力(Responsiveness)是基于服务的架构通常需要考虑的另外两个问题。这两者都涉及服务客户与远程服务之间通信的能力,但他们还是有着轻微不同的内涵,因此客户也会采用不同方式来解决这两个问题。 服务可用性是指远程服务及时地接受请求的能力,例如,与此远程服务建立连接。服务响应能力是指客户及时地从服务接收响应的能力。图1-3展示了这种不同。

图1-3 尽管这两种错误条件造成的结果是一样的(服务请求无法被处理),但是处理方式是不同的。因为服务可用性跟服务的可连接性有关,因此客户能够做的并不多,只能要么多试几次,要么在可能的情况下把请求放入队列,以后再处理。 处理服务响应能力问题就更困难一些。请求成功地发送给服务之后,客户需要等待多久?是否服务仅仅是很慢,又或者服务端发生了什么事情导致无法发送响应?解决超时条件问题对于远程服务的可连接性来说是相当具有挑战性的。一种常见的做法是在有负载的情况下获得最长响应时间作为基准,然后在此基础上添加额外的时延以处理负载的波动。例如,假设运行某个基准测试时,某个特定服务请求的最大响应时间为2000毫秒。那么你可能需要将这个值乘以二,用以处理负载较高的状况,因此预期的超时时长应该是4000毫秒。 尽管看起来这是一个合理的估算服务响应超时时长的解决方案,但有时也会有各种问题。首先,如果服务真的停止了或者未启动,每个请求都要等待四秒才能判定服务无响应。这实在是效率太低,而且会引起最终客户的不满意。另外一个问题在于,你所使用的基准测试可能并不精确,在高负载情况下,服务响应时间可能会是五秒而不是我们所算出来的四秒。这种情况下,服务端实际上能够做出响应,但是客户端会因为超时时长设得太低而拒绝了所有响应。 比较常见的一种解决方案是使用断路器模式(circuit breaker pattern)。如果服务不能及时做出响应或者干脆不响应,软件断路器将会起作用,提醒客户不必浪费时间等待超时事件发生。与物理断路器相比,软件断路器很不错的一点是在实现这种设计模式时,可以在服务端开始发送响应或者变得可用时对自己进行重置。软件断路器有不少开源实现,包括Netfix的Ribbon等。你可以在Michael Nygard的书Release It!中读到更多的关于断路器模式的内容。 在处理超时时长值的问题时,应该尽量避免使用全局的超时值来处理每个请求。恰恰相反,建议你基于上下文来定义超时时长,同时确保这些数值可以从外部通过配置来设置,这样你就可以在负载条件变化的情况下快速做出响应,而不用重建或者重新部署应用。另一种方案是在代码中使用“智能超时值(smart timeout values)”,以便在负载变化情况下自行调整超时值。例如,应用可以在高负载或者网络有问题的情况下自动增加超时时长值。当负载降低,响应时间变短后,应用可以重新计算平均响应时间,并对应地降低超时时长值。

安全性

在基于服务架构下,服务都是远程访问的,很重要的一点是需要确认给定的客户是否被授权访问某个特定服务。根据实际情况,服务的客户可能既需要被认证(authentication)也需要被授权(authorization)。认证是指服务客户是否可以连接到某个服务,一般是通过用户名和密码来建立登录凭证来进行认证。某些情况下,仅仅执行认证还不够:客户可以连接到某个服务并不意味着他可以访问服务所提供的所有功能。授权指的是某个服务客户是否被允许访问服务内部特定的业务功能项。 安全在早期SOA实现中是个大问题。原本被严格隔离的某个应用的功能突然被公开,从企业的全球各处都能访问到。这一变化所引发的问题促使我们换个角度思考,重新认识服务以及如何保护服务不会被不该访问的客户访问到。 对微服务而言,安全问题成为挑战主要是因为没有一个专门处理安全问题的中间件组件。相反地,每个服务必须各自处理安全性问题,或者在某些情况下需要增强API层以使之更加智能地处理应用的安全性问题。我看到的微服务中所实现的一个比较好的设计是将认证工作委托给一个独立的服务,而保留授权工作在微服务内部。尽管这一设计可以修改为将认证和授权都交由单独的服务来完成,我还是倾向于将授权包含在微服务自身当中。这样做可以避免与远程安全性服务之间的太多的不必要交互,还可以使得服务的上下文边界更加稳定,减少对外界的依赖。

事务

事务管理在基于服务的架构中也是很大的挑战。大多数时候我们所讨论的事务是指在大量业务应用中都存在的ACID(atomicity、consistency、isolation和durability)事务。ACID事务用于维持数据库一致性,对同一个请求中的多个数据库更新操作进行协调,使得当事务处理过程中发生问题时,该请求所作出的所有数据库更新都可以回滚。 考虑到基于服务的架构一般都是分布式架构,在多个远程服务之间传播和维护事务的上下文很困难。如图1-4所示,一个服务请求(红色X旁边的方框)可能需要调用多个远程服务来完成请求。红色X表明这种情况下无法实现ACID事务。

图1-4 事务问题在SOA架构中更为普遍,因为与微服务架构不同,SOA架构中通常使用多个服务来完成一个业务请求。我将在对比架构特点一章的“服务编排”一节中详细讨论这个问题。 基于服务的架构一般更依赖于 BASE事务,而不是ACID事务。BASE事务一般包括基本的可用性、软性的状态和最终一致性(basic availability、soft state和eventual consistency)。分布式应用依赖BASE事务来追求数据库中的最终一致性而不是每个中间事务的一致性。一个典型的BASE事务的例子是往ATM机里存钱。当通过ATM机向你的账户中存入现金,大概几分钟甚至几个小时后才会在账号中显现出来。换句话说,钱从离开自己的手到真正存入账户之间有一个软性的转换状态:钱已经离手,但是还没到达你的银行账户中。我们可以容忍这一延迟,并且寄希望于软状态和最终一致性,因为我们知道并信任这笔钱最终一定会到达我们的账号。从全系统视图的角度看,批处理作业也是依赖最终一致性的。 迁移到到基于服务的架构需要我们改变对事务和一致性的认识。对于不能依赖最终一致性和软状态的而必需事务一致性的情况,可以将服务的粒度设计得较粗,从而把业务逻辑包装在一个服务内,最后通过ACID事务来实现事务级别的一致性。另一种方法是使用事件驱动技术,当请求状态变得一致时向相关客户推送通知。这种技术给应用带来了很高的复杂度,不过确实能够在使用BASE事务时实现事务状态管理。

太复杂了?

基于服务的架构相对于单体式应用来说是一种巨大的进步,但是正如你所看到的,他们同时也带来很多问题,例如服务合约、可用性、安全性以及事务管理等等。不幸的是,转向基于服务的架构,如微服务或者SOA,意味着必须在某些方面做出权衡。有鉴于此,除非做好准备并且原意去解决分布式系统所面对的种种问题,否则不要匆忙转向基于服务的架构。 本节讨论的问题很复杂,但它们肯定不是不可克服的障碍。大多数使用基于服务的架构的团队最终都能顺利通过开源、商用和定制化的方案解决和克服这些问题。 基于服务的架构是不是太复杂了?肯定是的。但是,事物总是两面的。伴随着复杂性,基于服务的架构也有一些额外的特性和能力,能够提高开发团队的效率,开发出更为可靠的、更为稳定的应用,同时降低总体开销并缩短产品进入市场的周期。接下来的三节中,我会对比微服务和SOA,帮助你了解哪种架构模式更适合自己。

对比服务特性

OASIS面向服务架构参考模型(OASIS Reference Model for Service Oriented Architecture)中将“服务”定义为“一种支持对一或多种能力进行访问的机制,这里的访问是通过使用预定义的接口来提供的,并且稳定地按照服务描述所规定的约束和策略来执行”。换句话说,一个服务要提供某些业务能力,并提供定义良好的接口以及定义良好的合约以便(客户)进行访问。这个定义没有规定的是如何基于类别、组织所有关系以及粒度『如服务的规模』(classification、organizational ownership and granularity)来进一步地定义服务。理解这些服务特性有助于在特定架构模式下为服务的上下文给出定义。 尽管微服务和SOA都有赖于服务作为其主要的架构组件,他们在服务特性上是有很大的差别的。本章将围绕不同模式下服务如何分类(也就是服务的分类学)、如何基于服务的所有者进行服务之间的协调以及微服务与SOA之间服务粒度上的不同展开讨论。

原文发布于微信公众号 - 智能计算时代(intelligentinterconn)

原文发表时间:2016-09-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云计算与大数据

Envoy——Service Mesh体系中的私人订制,把你安排得明明白白!

最近因工作原因开始了解Service Mesh与Envoy,为系统性梳理所学内容,因此沉淀了此文档,但由于所知有限,如文档中有描述不当之处,希望不吝赐教。

2072
来自专栏潇涧技术专栏

How to know your application’s battery stats

众所周知,Android系统内置了应用的耗电量统计分析功能,但是并没有提供相应的API和文档,只是可以查看耗电量排行榜前10的应用的耗电百分比。此外,随着And...

1742
来自专栏Java架构师学习

多研究些架构,少谈些框架——一名阿里架构师的笔记

微服务架构和SOA区别 微服务现在辣么火,业界流行的对比的却都是所谓的Monolithic单体应用,而大量的系统在十几年前都是已经是分布式系统了,那么微服务作为...

3678
来自专栏IT笔记

SpringBoot开发案例从0到1构建分布式秒杀系统

最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路。俗话说,脱离案例讲架构都是耍流氓,最终使用Spri...

63812
来自专栏SDNLAB

Unikernel初体验

引言: 2016年1月21日,应用容器引擎 Docker 宣布收购了英国的 unikernel 实现初创企业 Unikernel System,但具体交易金额并...

4116
来自专栏跨界架构师

分布式系统关注点——99%的人都能看懂的「熔断」以及最佳实践

那么在这样的背景下,如果某个服务A需要发布一个新版本,往往会对正在运行的其它依赖服务A的程序产生影响。甚至,一旦服务A的启动预热过程耗时过长,问题会更严重,大量...

832
来自专栏Android机动车

Android模块化开发方案

随着业务的不断发展壮大,移动端所承担的功能也越来越重,特别是代码几易其主之后开始变得杂乱无章,牵一发而动全局的事情时常发生。为了应对团队壮大之后的开发模式,我们...

1442
来自专栏CSDN技术头条

《英雄联盟》支撑最高750万同时在线用户的聊天服务打造

【编者按】在2013年初马化腾被问及“过去两年腾讯在海外投资中最成功的案例是什么”时,他毫无疑问的回答:“投资美国的Riot Games,做出《英雄联盟》。”在...

24510
来自专栏智能计算时代

区块链101:区块链和数据库的区别是什么?

正如我们的指南中所说的“区块链技术是什么?”传统数据库和区块链之间的区别始于体系结构,或者技术是如何编排的。 在万维网上运行的数据库经常使用客户机-服务器网络体...

3533
来自专栏玉树芝兰

安装 Python 软件包遇错误,怎么办?

本文通过一个命令行转换 pdf 为词云的例子,给你讲讲 Python 软件包安装遇挫折时,怎么处理才更高效?

1432

扫码关注云+社区