微服务架构
作者:DevOps亮哥
来自:DevOps探路者
对于面向对象的设计,我们遵循SOLID原则。对于微服务设计,我们建议开发人员遵循IDEALS原则:接口分离(Interface segregation),可部署性(deployability),事件驱动(event-driven),可用性胜于一致性(Availability over Consistency),松耦合(Loose coupling)和单一责任(single responsibility)。
在2000年,Robert C. Martin编写了面向对象设计的五个原则。Michael Feathers后来将这五个原则的首字母组成了缩略词,也就是SOLID。从那时起,用于OO设计的SOLID原则就在业界被广为人知。这五个原则是:
几年前,我在给其他人讲授微服务设计时,一个学生问:“SOLID原则适用于微服务吗”,经过一番思考,我的回答是“部分”。后面的几个月,我发现我一直在寻找微服务的基本设计原则(包含一些缩略词)。为什么这样的问题会如此重要呢?
作为一个行业,我们已经设计和实施基于微服务的解决方案已经超过六年了。在此期间,越来越多的工具、框架、平台和支撑产品围绕微服务建立了一个极其丰富的技术环境。在这种情况下,一个新手微服务开发人员在面对一个微服务项目中许多设计决策和技术选择时会感到迷惑。在这个领域,一组核心原则可以帮助开发人员更好的理解基于微服务的解决方案。
虽然有些SOLID原则适用于微服务,但面向对象是一种设计范式,它处理的元素有类、接口和继承等,与一般分布式系统中的元素(特别是微服务)有着根本的区别。
因此,我们提出以下一套微服务设计的核心原则:
这些原则并没有覆盖基于微服务的解决方案的所有设计决策,但他们涉及到创建现代基于服务的系统的关键关注点和成功因素。下面对微服务的“IDEALS”的原则进行详细的解释。
最初的接口分离原则是指防止面向对象中类使用“胖”接口。换句话说,就是每种类型的客户端应该有单独的接口,而不是提供一个满足所有客户端需要的所有可能方法的接口。
微服务体系结构风格是面向服务体系结构的一种特殊化,其中接口(即服务契约)的设计一直是最重要的。从21世纪初开始,SOA文档就规定了所有客户端都应该遵守的规范模型和规范模式。然而,自从SOA的旧时代以来,我们处理服务契约设计的方式发生了改变。在微服务时代,同一个服务逻辑通常有许多客户端程序。这就是将接口分离应用于微服务的主要目的。
实现微服务的接口分离
微服务接口分离的目标是每种类型的前端都能看到最适合其需求的服务契约。例如,一个移动应用程序系统调用端点,这些端点返回简短的JSON格式数据作为响应。当另一个web应用程序需要返回完整的JSON格式数据作为响应。与此同时,还有一个桌面应用程序调用同一个服务,但需要返回完整的XML格式数据。不同的客户端也可能使用不同的协议。例如,外部的客户端希望使用HTTP来调用gRPC服务。
我们没有试图在所有类型的服务客户机上强加相同的服务契约(使用规范模型),而是通过“分离接口”,以便每种类型的客户机都能看到它需要的服务接口。我们怎么做?一个突出的选择是使用API网关,它可以进行消息格式转换(message format transformation),消息结构转换(message structure transformation),协议桥接(protocol bridging),消息路由(message routing)等。一个流行的替代方案是BFF(Backend for Frontends)模式。在这种情况下,我们为每种不同类型的客户机提供了一个API网关,也就是通常说的,为每种客户机提供不同的BFF,如下图所示:
几乎在整个软件历史上,设计工作都集中在与实现单元(模块)如何组织以及运行时元素如何交互相关的设计决策上。架构策略、设计模式和其他设计策略为在层中组织软件元素、避免过度依赖、为特定类型的组件分配特定的角色或关注点以及软件空间中的其他设计决策提供了指导。对于微服务开发人员来说,有一些关键的设计决策超出了软件元素。
作为开发人员,我们早就意识到将软件正确打包并部署到适当的运行时环境中的重要性。然而,我们从没有像今天的微服务那样关注部署和运行时监控。在这里称为“可部署性”的技术和设计决策领域已经成为微服务成功的关键。主要原因是一个简单的事实,即微服务显著增加了部署单元的数量。
因此,IDEALS中的D代表微服务开发者有责任确保软件及其新版本随时可以部署到环境中供用户使用。总之,可部署性包括:
实现良好的可部署性
自动化是实现高效部署的关键。自动化包括明智的使用工具和技术,这是自微服务出现以来不断看到最大变化的领域。因此,微服务开发人员应该在工具和平台方面寻找新的方向。但总是质疑每一个新选择的好处和挑战。(这里可参考Thoughtworks技术雷达和软件架构与设计趋势报告)
以下是开发人员在任何基于微服务的解决方案中为提高可部署性而应该考虑的策略和技术列表:
微服务架构风格用于创建后端服务,这些服务通常使用以下三种类型的方式进行调用:
前两个通常是同步的,HTTP调用也是最常见的方式。通常,服务需要调用其他服务进行组合,太多时候,组合中的服务调用是同步的。如果异步,需要连接和接收Queue/Topic里的消息,那么我们将创建一个事件驱动的体系结构。(我们可以讨论消息驱动和事件驱动的区别,但都可以表示网络上的异步通信,使用消息中间件产品(Apache Kafka、RabbitMQ和Amazon SNS)提供的Queue和Topic)
事件驱动体系结构的一个重要好处是提高了可伸缩性和吞吐量。这是因为:消息发送者在等待响应时不会被阻塞,并且同一个消息/事件可以由多个接收者以发布-订阅的方式并行使用。
事件驱动的微服务
IDEALS中的E就表示使用事件驱动对微服务进行建模。因为他们更能满足当今软件解决方案的可伸缩性和性能要求,这种设计还促进了松耦合,因为消息发送方和接收方——微服务——是对立的,彼此不了解。可靠性也得到了提升,因为这个设计可以处理微服务的临时中断,当微服务恢复后可以处理排队中的消息。
但事件驱动的微服务,也称为反应式微服务,也会带来挑战,比如异步处理和并行执行,可能需要同步点和相关标识符。设计需要考虑错误和丢失的消息——校正事件和撤销数据更改的机制(如Saga模式)通常是必须的。对于事件驱动体系结构带来的面向用户的事务,应仔细考虑用户体验,以使最终用户了解进度和事故。
CAP理论本质上给了我们两个选择:可用性或者一致性。我们看到业界为了让我们选择可用性而付出了巨大努力,从而最终实现一致性。原因很简单:今天的最终用户不会容忍服务不可用。假如一个网络商店,如果我们在浏览产品时显示的库存量和购买时更新的实际库存量之间强制执行强一致,那么数据变更将会带来巨大的开销,如何任何更新库存的服务暂时无法访问,那么页面无法显示库存信息,结账将停止服务。相反,如果选择可用性,用户浏览产品时显示的库存量和购买时更新的实际库存量之间会有偶尔的不一致。当用户在下单买时,然后再去查询真实的库存量,如果没有库存,再提示用户没有库存。从用户的角度来看,这个场景比由于系统要实现强一致而让整个系统不可用或超级慢对所有用户来说要好很多。
有些业务操作确实需要很强的一致性。然而,正如Pat Helland指出,当你面对你是想要正确?还是想要现在?的问题时,人们通常想要的是现在而不是正确的答案时,就需要考虑强一致。
最终一致性的可用性
对于微服务来说,保证可用性选择的主要策略是数据复制。可以采用不同的设计模式,有时可以组合使用。
我们经常使用的CQRS模式通常如下图所示:一个可以更改数据的HTTP请求由后台一个REST服务处理,该服务可以操作一个集中式的Oracle数据库。其他只读的HTTP请求转到另一个后台服务,该服务可以从基于文本的Elasticsearch数据存储中获取数据。一个Spring Batch Kubernetes cron任务定期将在Oracle数据库中的变更同步到ES中,这个设计使用两个数据存储之间的最终一致性。即使Oracle DB和cron任务不起作用,查询服务也是可用的。
在软件工程中,耦合是指两个软件元素之间相互依赖的程度。对于基于服务的系统来说,传入耦合是指服务用户如何与服务交互。我们知道这种交互应该通过服务契约来实现,并且该服务契约不应该与实现细节和特定技术紧密结合。服务是可以由不同程序调用的分布式组件。有时候,服务提供方不知道所有服务用户在哪里(公共API服务通常就是这样)。因此,服务契约应该避免变更。如何服务契约与服务逻辑或技术紧密耦合,那么当逻辑或技术需要演化时,它也需要同时发生变化。
服务通常需要与其他服务或其他类型的组件交互,从而产生传出耦合。这种交互建立了直接影响服务自治的运行时依赖关系。如果一个服务的自治性较低,它的行为就不那么可预测:最好的情况就是,该服务将与他需要调用的最慢的,最不可靠的和最不可用的组件一样快速、可靠和可用。
微服务的松耦合策略
IDEALS中的L表示要关注服务及微服务的耦合。可以使用并组合多种策略来改进传入和传出的松散耦合。这些策略包括:
最初的单一职责原则( Single Responsibility Principle,SRP)是关于在OO类中具有内聚功能。在一个类中拥有多个职责自然会导致紧耦合,并导致脆弱的设计,在变更时会发生意想不到的结果。SRP这个想法很简单,也很容易理解,但要用好并不容易。
单一职责的概念可以扩展到微服务中服务的内聚性。微服务体系结构风格部署单元应该包含一个服务或几个内聚服务。如果一个微服务包含太多的职责,也就是说,有太多不太具有内聚力的服务,那么它就可能会承受一个巨大的痛苦。膨胀的微服务在功能和技术栈方面变得更难发展。而且,持续交付将会变得繁重,因为他们将会使许多开发人员在同一个部署单元开发不同的内容。
另一方面,如果微服务过于细粒度,则其中的几个服务可能需要交互来满足用户请求。在最坏的情况下,数据变更可能会跨多个微服务,可能会产生分布式事务的场景。
粒度适中的微服务
微服务设计成熟度的一个重要方面是创建粒度适中的微服务的能力。这里的解决方案不是任何工具或技术中,而是在适当的领域建模上。为后端服务建模并为其定义微服务边界可以通过多种方式完成。业界流行的一种驱动微服务范围的方法是遵循领域驱动设计(Domain-Driven Design,DDD)原则。简而言之:
一个服务(例如:REST服务)可以具体DDD聚合的作用域。一个微服务的作用域可以是DDD限定的上下文。该微服务中的服务将对应于该限定上下文的聚合。
对于微服务间的通信,我们可以使用:当异步消息满足需求时使用领域事件(Domain Event);当请求-响应更适合时,使用某种形式的中间层进行API调用;当一个微服务需要另一个可用区的大量数据时,可以使用数据复制保证最终一致性。
IDEALS是在大多数典型的微服务设计中要遵循的核心设计原则。然而,遵循IDEALS并不是使我们的微服务设计成功的良药。通常,我们还需要对质量需求有一个很好的理解,并使设计决策意识到他们的权衡。此外,我们还应该学习可以用来帮助实现设计原则的设计模式和架构策略。还应该掌握可用的技术选型。
多年来,我一直使用IDEALS设计、实现和部署微服务,在设计研讨会和讲座中,我与来自不同组织的数百名软件开发人员讨论这些核心原则以及每一个原则背后的许多策略。有时候,会让人觉得微服务的工具、框架、平台和模式层出不穷,我相信,对微服务IDEALS更好的理解,能够帮助我们更清晰的了解技术领域。
翻译自:
https://www.infoq.com/articles/microservices-design-ideals/?itm_source=infoq&itm_campaign=user_page&itm_medium=link