9000字长文:详解微服务实践 从架构到部署
前段时间公司事情多,这篇文章写了放、放了写…耽搁了一些进度。各个自媒体的更新也慢了很多,这里给大家说句抱歉了。
现如今“微服务”遍地开花,已经成软件架构领域最受欢迎的热门话题之一。网上和书籍中都有很多关于微服务基础和优势的学习材料,但是我们可能会发现这东西在现实世界企业场景中似乎好像没那么普及,真正能跑的微服务资源其实也不是很多,大多是停留在概念和摸索阶段。
在这篇文章里,我打算讲讲微服务架构(MSA)的关键架构概念,以及如何在实践中将这些架构原理应用起来。
前因:单片结构
企业应用,因为服务于众多业务需求,因此会有些特定的软件应用提供许多功能,而一般惯例是把这些功能都堆在单个单片应用中。例如,ERP,CRM和其他各种软件系统被规划构建为具有数百个功能的整体。这种带坑应用一经部署,在往后的故障排除、扩展和升级场景中就是一场接一场的恶梦了。
面向服务架构(SOA)旨在通过引入“服务”的概念来克服以上的限制,“服务”是从应用程序提供的类似功能中提取出来的聚合和分组。因此,使用SOA,软件应用程序就会被设计为“粗粒度”服务的组合。然而,SOA中服务的范围非常广泛,这又引出了复杂而庞大的服务与一大堆操作(功能)以及复杂无比的消息格式和标准(如WS *标准)。
monolith2多数情况下,SOA中的服务彼此独立,只不过它们与所有其他服务一起部署在相同的运行时间里(只需考虑将部署到同一个Tomcat实例中的多个Web应用)。和单片软件应用类似,这些服务通过积累各种功而具有随时间推移的习惯。图1是一个非常好的一个单片架构的例子,显示了包括多个服务的零售软件应用,所有这些服务都能部署到同一应用程序。
说了那么多,大家是不是有点不明所以?我总结了一些重点,以下就是基于单片架构的应用程序的一些特性归纳:
单片应用程序被设计、开发和部署为单个单元。
单片应用相对复杂,导致维护,升级和新特性困难重重。
难以用单片架构来实践敏捷开发和交付方法。
想更新某部分,很可能要重新部署整个应用。
规模需求冲突的情况下,若想做单点扩展,要做好多倍资源甚至推倒重来的准备(例如,想给A服务更多CPU,B服务可能要重做,也可能要补两三倍memory)
可靠性:一个不稳定的服务可以拖慢整个应用性能。
难创新:采用新技术和框架非常困难,因为所有的功能必须建立在均匀的技术/框架上。
单片架构的天然特性,直接给微服务架构的异军突起带来了绝佳的机会。
后果:微服务架构
微服务架构(MSA)的基础是将单片应用作为一套小型和独立服务来开发,这些服务都在自己的空间独立开发、部署和运行。在微服务体系结构的大多数定义中,它普遍被解释为将巨大的可用服务隔离成一套独立服务的过程。
不过我对这些有自己的理解,微服务的逻辑不仅是将整个大容量服务分为独立服务,关键在于通过查看从整体提供的功能,我们就可以确定所需的业务能力。而这些业务能力可以具体实现为各自独立的细粒度和自包含(微)服务。它们可能在一个技术堆栈上实现,每个服务都处理一个非常具体和有限的业务范围。
因此,上面解释的在线零售系统场景可以通过如图2的微服务架构实现。通过微服务架构,零售软件应用程序被规整为一套微服务。而且从图2最底下一层可以看出,根据业务需求,还多了一个从原始服务组合中额外创建的一个微服务。换句话说,想跟进微服务,要有超大容量服务可能会分裂的准备,不过我相信相比于进步,这点麻烦值得付出。
microservices所以,铺垫了那么多,现在我们深入了解微服务的关键架构原理,这里重要的是最好带着实践的思路来思考问题。
设计微服务:大小,范围和功能
通过Microservices Architecture可以从头开始构建软件应用或改造现有应用程序/服务…无论是哪种方法,正确判定Microservices的大小,范围和功能,都至关重要。我感觉,这可能是在Microservices Architecture实践最初(对的,“最初”这个词用的没错)最难的事了。
现在聊聊关于微服务的规模、范围和功能的关键实践问题和对一些坊间误解的辟谣。
代码行数/团队规模都是坑B指标:这里先引入一个概念,双披萨团队,有兴趣的可以去深入了解。根据实施代码或团队规模,有几个讨论决定了Microservices的大小。然而,这些被认为是非常不切实际和糟糕的指标,即便我们仍然可以使用较少的代码/双比萨团队做规模开发,但这种思路完全违反了微服务架构主体。
'Micro'有点误导性的词语:大多数开发人员倾向于认为他们应尽可能的减少服务,这是一个天然的误解。
在SOA上下文中,服务通常被实现为具有数十个操作/功能的单片全局。所以,像SOA这样的服务就算命名为微服务,不会给你带来微服务架构的好处。
那么那么我们应该如何在微服务架构中正确设计服务?
Microservice_EasyOps微服务设计指南
单一责任原则(SRP):为微服务提供有限和重点突出的业务范围,有助于我们满足开发和提供服务的敏捷性。
在微服务的设计阶段,我们应该找到各服务的边界,并将其与业务能力(也称为域驱动设计中的有界环境 )保持一致。
微服务设计要确保敏捷/独立开发和部署服务的丝滑稳定。
我们的重点应该放在微服务的范围上,而不是使服务更"小"。服务的大小应该是指所需的范围大小,以促进给定的业务能力。
与SOA中的服务不同,给定的微服务应该具有很少的操作/功能和简单的消息格式。
随着时间的推移,首先开始具有相对广泛的服务边界,重构到较小的服务界限(基于业务需求),这是一个很好的做法。
在我们的零售用例中,整体功能分为四个不同的微服务,即“库存”,“会计”,“运输”和“存储”。他们正在解决一个有限但重点突出的业务范围,使每个服务彼此完全脱钩,并确保开发和部署中的敏捷性。
微服务中的消息传递
在单片应用中,使用函数调用或语言级方法调用不同处理器/组件的业务功能。在SOA中,这种转移趋向于更为松散耦合的Web服务级别消息传递,主要基于不同协议(如HTTP,JMS)之上的SOAP。而具有数十种操作和复杂消息模式的Web服务是Web服务普及的关键阻力。对于Microservices架构,它需要简单而轻量的消息传递机制就行。
同步消息 - REST,Thrift
对于同步消息传递(客户端期望从服务及时得到响应并等待到达),在微服务体系结构中,REST是主流标配,因为它提供了基于资源API风格的HTTP请求响应实现的简单消息传递方式。因此,大多数微服务实现都使用HTTP以及基于资源API的风格(每个功能都以资源和在这些资源之上执行的操作来表示)。
ms_rest另外,Thrift (可以在其中为微服务定义接口定义)可以作为REST / HTTP同步消息传递的替代方法。
异步消息 - AMQP,STOMP,MQTT
对于某些需要使用异步消息传递技术(客户端不期望立即响应或根本不接受响应)的微服务场景。多看看诸如AMQP, STOMP或MQTT之类的异步消息协议就好了。
消息格式 - JSON,XML,Thrift,ProtoBuf,Avro
决定微服务最适合的消息格式是另一个关键因素。传统单片应用使用二进制格式(习惯了,表示还好不算复杂);基于SOA / Web服务的应用使用基于复杂消息格式(SOAP)和模式(xsd)的文本消息。而在大多数基于微服务器的应用中,简单基于文本的消息格式即可,如JSON和XML,反正就是基于HTTP资源API风格跑起来。在我们需要二进制消息格式的情况下(文本消息在某些用例中可能会变冗长),微服务可以利用二进制消息格式,如Thrift,ProtoBuf或Avro…
服务合同 - 定义服务接口 - Swagger, RAML, Thrift IDL
若你把业务能力实现为服务,就需要定义和发布服务合同。传统单片应用几乎找不到这样的功能来定义应用的业务功能。在SOA/Web服务的世界里,WSDL用于定义服务合同。不过众所周知,WSDL不是用于定义微服务合同的理想解决方案,因为WSDL非常复杂且与SOAP紧密耦合。
由于我们在REST架构风格之上构建微服务器,所以我们可以使用相同的REST API定义技术来定义微服务器的合同。因此,微服务更多情况下用标准的REST API定义语言(如Swagger和RAML) 来定义服务契约。
对于不基于HTTP/REST(如Thrift)的其他微服务实现,可以用协议级别的“接口定义语言(IDL)”(如Thrift IDL)。
集成微服务(Inter-service / process communication)
在微服务架构中,软件应用程序被构建为一套独立的服务。因此,为了实现业务用例,需要在不同的微服务/流程之间建立通信结构。这就是为什么微服务之间的服务间/过程通信至关重要的原因。
在SOA实现中,服务之间的服务间通信通过企业服务总线(ESB)来实现,大部分业务逻辑都位于中间层(消息路由,转换和业务流程)中。然而,微服务架构促进消除中央消息总线/ESB,并将“智能”或业务逻辑移至服务和客户端(称为“智能端点”)。
由于微服务使用诸如HTTP,JSON等标准协议,因此在微服务之间的通信中,与不同协议集成的要求是最小的。Microservice通信中的另一种替代方法是使用轻量级的消息总线或网关,它们有最小的路由功能,并且仅用作在网关上实现业务逻辑的“dumb pipe”。基于这些风格,在微服务架构中不可避免的出现了几种通信模式。
Microservice_EasyOps点到点风格 - 直接调用服务
以点对点的形式,消息路由逻辑的整体跑在每个端点之上,服务可以直接通信。这里每个微服务器都暴露一个REST API,一个给定的微服务器或外部客户端可以通过对应的REST API调用另一个微服务器。
msa_integration显然,这个模型适用于相对简单的基于微服务的应用。随着服务数量增加,复杂就会压倒一切的源头。传统SOA实现中使用ESB也是相同的原因,不过是为了摆脱混乱的点对点集成链接。总结微服务通信中点对点风格的缺点:
须在每个微服务级别实现终端用户认证,限制,监控等非功能性要求。
由于不可避免的复制一些常用功能,每个微服务实现可能会变得复杂。
服务和客户端之间的所有通信都无法控制(哪怕只是监控,跟踪或过滤)。
通常直接沟通风格被认为是用于大规模微服务实现的微服务反向模式。
特征说完,总结:对于复杂的微服务用例,可以考虑轻量级的中央消息总线,而不是点对点连接或中心ESB,思路是为微服务提供一个抽象层,并可用于实现各种非功能的能力,这种风格被称为API网关风格,往下看。
API网关样式
API网关风格背后的关键思想是使用轻量级消息网关作为所有客户端/消费者的主要入口点,并在Gateway级别实现常见的非功能需求。通常,API网关允许通过REST/HTTP使用受管API。因此,在这里,我们可以将通过API-GW实现作为微服务的业务功能公开为管理API。Microservices架构和API管理的组合,这个算是最佳选择了。
msa_api_gw在我们的零售业务场景中,如图5,所有的微服务都通过API-GW公开,这是所有客户端的单个入口点。总结下API-GW风格优势:
能够在现有微服务的网关级别提供所需的抽象。例如,API网关可以为每个客户端提供不同的API,而不是提一个所有类型的API。
网关级、轻量级消息路由/转换。
非功能性功能(如安全性,监控和调节)的应用能如臂指使。
API-GW模式让非功能性要求在Gateway级别实现,微服务得以瘦身。
比较推荐API-GW,我没具体了解过,但这个应该也是应用最广泛的模式。
Message Broker风格
微服务还可以和异步消息的场景集成,比如队列或主题的单向请求以及订阅。给定的微服务可以做消息生成,并且它可以异步地将消息发送到队列或主题。那么消费的微服务器可以消耗来自队列或主题的消息。这种风格将消息生成与消息消费分离,中间消息代理缓冲直到消费处理。
msa_async消费/生产之间的通信基于异步消息标准(例如AMQP,MQTT等)的消息代理来实现。
分散式数据管理
单片架构中,应用将数据存储在单个和集中的数据库中,以实现应用的各种功能/功能。
monolith_db在微服务体系结构中,功能分散在多个微服务器中,如果我们使用相同的集中式数据库,那么微服务器将不再彼此独立(例如,如果数据库模式已经从给定的微服务器发生变化)。因此,每个微服务器都必须拥有自己的数据库。
msa_db以下是在微服务架构中实施分散数据管理的关键方面:
每个微服务器都可以有一个私有数据库来保存实现从其提供的业务功能所需的数据。
给定的微服务器只能访问专用私有数据库,而不能访问其他微服务器的数据库。
某些业务场景中,可能单个事务需要更新多个数据库。在这种情况,其他微服务器的数据库应仅通过其服务API进行更新,而非直接访问。
非集中式数据管理提供完全解耦的微服务器,以及选择不同数据管理技术(SQL或NoSQL等,按需分配,可能不同数据库管理系统)的自由。再者对于涉及多个微服务的复杂事务用例,事务行为必须使用从每个服务提供的API来实现,并且逻辑位于客户端或中间(GW)级别。
权力下放的治理思维
微服务架构倾向于分散治理。一般来说,SOA治理指导了可重用服务的开发,建立如何设计和开发服务以及这些服务随时间的变化。它确定了服务提供商和这些服务的消费者之间的协议,告诉消费者他们期望什么以及提供者有义务提供什么。在SOA治理中,共有两种类型的治理:
设计时治理 - 定义和控制服务策略的服务创建,设计和实施;
运行时管理 - 执行期间执行服务策略的能力;
那么,微服务环境中的治理究竟怎么理解?在微服务架构中,微服务通过各种技术和平台构建为完全独立和解耦服务。因此,不需要为服务设计和开发定义一个共同的标准。总结下微服务的权力下放治理能力:
在微服务架构中,管理不需要集中设计。
微服务器可以因地制宜,决定其设计和实现。
微服务架构促进了共享/可重用服务的共享。
一些运行时管理方面,如SLA,节流,监控,常见安全要求和服务发现可以在API-GW级别实现。
服务注册和服务发现
在微服务架构中,需要处理的微服务器数量相当高。而且,由于微服务的快速和敏捷的开发/部署性质,他们的位置一般来说也会动态变化。因此,你需要在运行时间内找到微服务器的位置。解决此问题的方法是使用Service Registry。
服务注册表
服务注册表保存微服务实例及其位置。Microservice实例在启动时注册到服务注册表,并在关机时注销。消费者可以通过服务注册表找到可用的微服务及其位置。
服务发现
要找到可用的微服务及其位置,我们要一个服务发现机制。有两种类型的服务发现机制,客户端发现和服务器端发现,来看看这些服务发现机制各原理如何。
客户端发现:
在这种方法中,客户端或API-GW通过查询服务注册表来获取服务实例的位置。
msa_client_side_discovery这里客户端/API-GW必须通过调用Service-Registry组件来实现服务发现逻辑。
服务器端发现:
通过这种方法,客户机/API-GW将请求发送到在众所周知的位置运行的组件(如负载平衡器)。该组件调用服务注册表并确定微服务器的绝对位置。
msa_server_side_discoveryKubernetes等微服务部署解决方案提供了服务端发现机制,自媒体里链接不能发就算了。
部署
在微服务架构方面,微服务的部署同样起着至关重要的作用,关键要求如下:
能够独立于其他微服务部署/部署。
必须能在每个微服务级别做扩展(给定的服务可能比其他服务获得更多的流量)。
快速构建和部署。
一个微服务的故障不得影响任何其他服务。
Docker提供了一种很好的方式来部署满足上述要求的微服务器,所涉及的关键步骤如下:
将微服务作为(Docker)容器镜像打包。
将每个服务实例部署为容器。
基于更改容器实例的数量来进行缩放。
构建,部署和启动微服务将会快得多,因为我们使用Docker容器(这比常规VM快得多)
Kubernetes通过允许将一组Linux容器作为单个系统进行管理,在多个主机上管理和运行Docker容器,提供容器的共同位置,服务发现和复制控制,从而扩展Docker功能。其实大多数这些功能在微服务环境中也很重要,因此使用Kubernetes(在Docker上面)用于微服务部署已经成为一种非常强大的方法,特别是对于大规模微服务部署。
msa_deployment图11,显示了零售应用的微服务部署概述。每个微服务实例被部署为容器,每个主机有两个容器。同时可随意更改在给定主机上运行的容器数。
安全
现实很骨感,无论是不是微服务都应当得到安全方面的保护。在进入微服务安全性篇章之前,我们先快速过一下如何在单片应用层面实现安全。
在典型的单片应用程序中,安全性是关于发现“谁是呼叫者”,“呼叫者可以做什么”,以及“我们如何传播该信息”。
这通常在位于请求处理链的开始处的公共安全组件中实现,该组件使用底层用户存储库(或用户存储)填充所需的信息。
那么,我们可以直接将这种模式转化为微服务架构吗?是的,但是这需要在每个微服务级别实现安全组件,该级别正在与集中式/共享用户存储库进行通信,并检索所需的信息。这是解决Microservices安全问题的一种非常乏味的方法。
还有,划重点,我们可以利用广泛使用的API-Security标准(如OAuth2和OpenID Connect)来找到我们的Microservices安全问题的更好的解决方案。在深入了解之前,我来总结下这些标准。
OAuth2 - 是访问委派协议。客户端使用授权服务器进行身份验证,并获取称为“访问令牌”的不透明令牌。Access令牌具有关于用户/客户端的零信息。它仅引用只能由授权服务器检索的用户信息。因此,这被称为“副参考令牌”,即使在公共网络/互联网中也可以安全地使用该令牌。
OpenID Connect的行为与OAuth类似,但是除了访问令牌之外,授权服务器还会发出一个包含用户信息的ID令牌。这通常由JWT(JSON Web令牌)实现,并由授权服务器签名。因此,可以确保授权服务器与客户端之间的信任。因此,JWT令牌被称为“副值令牌”,因为它包含用户的信息,显然不能在内部网络之外使用它。
现在,让我们看看我们如何使用这些标准来保护我们零售业的微观服务。
msa_security如图12,这些是实现微服务安全性的关键步骤:
将身份验证保留到OAuth和OpenID Connect服务器(授权服务器),以便microservices成功提供访问权限,因为有人有权使用数据。
使用API-GW样式,其中所有客户端请求都有一个入口。
客户端连接到授权服务器并获取访问令牌(按引用令牌)。然后将访问令牌与请求一起发送到API-GW。
网关上的令牌翻译:API-GW提取访问令牌,并将其发送到授权服务器以通过值令牌来检索JWT。
GW将该JWT与请求一起传递到微服务层。
JWT包含有助于存储用户会话等的必要信息。如果每个服务都可以了解JSON - Web令牌,那么已经分发了你的身份机制,那你就被允许在整个系统中传输身份。
在每个微服务层,我们可以有一个处理JWT的组件,这是一个非常简单的实现。
交易
微服务中的交易支持如何?其实,支持跨多个微服务的分布式事务是一个非常复杂的任务。
微服务架构本身鼓励服务之间的无事务协调。这个想法是给定的服务是完全独立,基于单一的责任原则。跨多个微服务器分布式事务的需要通常是微服务体系结构设计缺陷的症状,通常可以通过重构微服务器的范围进行整理。
然而,如果强制性要求跨多个服务进行分布式交易,则可以通过在每个微服务级别引入“补偿操作”来实现这种情况。关键思想是,给定的微服务是基于单一责任原则,如果给定的微服务器未能执行给定的操作,那么我们可以认为这是整个微服务的失败。
设计失败
Microservice架构引入了一系列分散的服务,与单片设计相比,增加了在每个服务级别发生故障的可能性。由于网络问题,基础资源不可用,给定的微服务可能会失败。不可用或无响应的微服务器不应玩砸了整个基于微服务器的应用。因此,微服务应该有容错余地,能够在可能的情况下恢复,客户端也必须妥善处理。
Microservice_EasyOps此外,由于服务可能随时失败,因此能够快速检测(实时监控)故障,并尽可能自动恢复服务(故障自愈)很重要。
在微服务玩家的眼里,处理错误有几种常用模式。
断路器
当你对微服务器进行外部呼叫,就可以在每次调用时配置故障监视器组件,并且当故障达到某个阈值时该组件将停止对服务的任何进一步调用(跳闸电路)。在打开状态(可以配置)的一定数量的请求后,将电路更换回关闭状态。
这种模式对于避免不必要的资源消耗,由于超时引起的请求延迟是非常有用,也让我们有机会更积极的监控系统(基于活动的开路状态)。
隔板
基于微服务器的应用的一部分的故障不应影响其他应用,隔板模式是关于隔离应用的不同部分,以便应用的某个部分的服务失败不会影响任何其他服务。
超时
超时是一种主观加入的合理机制,当你认为结果无法返回,则允许停止响应。
模式这里叨叨完。那么,我们在哪里和如何使用这些模式与微服务?大多数情况下这些模式都适用于Gateway级别,这意味着当微服务不可用或没有响应时,在网关级别,我们可以决定是否使用断路器或超时模式将请求发送到微服务。此外,在Gateway级别实现诸如隔板之类模式也很重要,因为它是所有客户端请求的单个入口点,也因此发送服务中的故障不应影响其他微服务器的调用。
另外,Gateway可以作为中心点,我们可以通过Gateway调用每个微服务来获取每个微服务器的状态和监视。
微服务,企业集成,API管理和遐(瞎)想
我们专门研究过微服务架构的各种特点,以及如何在现代企业IT环境中实现它们。但是,我们应该记住,微服务不是灵丹妙药。流行语概念的盲目适应不会解决“真实”企业IT问题。
它微服务是有很多优势,我们也应该充分利用。但是我们还必须记住,解决所有企业IT问题与微服务之间联系不一定就是强相关。例如,Microservices架构促进消除ESB作为中央总线,但是当涉及到现实世界的IT时,现在有很多不是基于Microservices的应用程序/服务。所以,整合是避不开的解决思路,要做集成。
所以,理想情况下,Microservices和其他企业架构概念(如Integration)的混合方法将更为现实。以后再聊了,这个能写的太多。 希望这能让你更清楚地了解如何在企业中使用Microservices。