引言:“微服务”是当前软件架构领域非常热门的词汇,能找到很多关于微服务的定义、准则,以及如何从微服务中获益的文章,在企业的实践中去应用“微服务”的资源却很少。本篇文章中,会介绍微服务架构(Microservices Architecture)的基础概念,以及如何在实践中具体应用。
单体架构(Monolithic Architecture )
企业级的应用一般都会面临各种各样的业务需求,而常见的方式是把大量功能堆积到同一个单体架构中去。比如:常见的ERP、CRM等系统都以单体架构的方式运行,同时由于提供了大量的业务功能,随着功能的升级,整个研发、发布、定位问题,扩展,升级这样一个“怪物”系统会变得越来越困难。
单体架构的初期效率很高,应用会随着时间推移逐渐变大。在每次的迭代中,开发团队都会面对新功能,然后开发许多新代码,随着时间推移,这个简单的应用会变成了一个巨大的怪物。
图1:单体架构
大部分企业通过SOA来解决上述问题,SOA的思路是把应用中相近的功能聚合到一起,以服务的形式提供出去。因此基于SOA架构的应用可以理解为一批服务的组合。SOA带来的问题是,引入了大量的服务、消息格式定义和规范。
多数情况下,SOA的服务直接相互独立,但是部署在同一个运行环境中(类似于一个Tomcat实例下,运行了很多web应用)。和单体架构类似,随着业务功能的增多SOA的服务会变得越来越复杂,本质上看没有因为使用SOA而变的更好。图1,是一个包含多种服务的在线零售网站,所有的服务部署在一个运行环境中,是一个典型的单体架构。
单体架构的应用一般有以下特点:
设计、开发、部署为一个单独的单元。
会变得越来越复杂,最后导致维护、升级、新增功能变得异常困难
很难以敏捷研发模式进行开发和发布
部分更新,都需要重新部署整个应用
水平扩展:必须以应用为单位进行扩展,在资源需求有冲突时扩展变得比较困难(部分服务需要更多的计算资源,部分需要更多内存资源)
可用性:一个服务的不稳定会导致整个应用出问题
创新困难:很难引入新的技术和框架,所有的功能都构建在同质的框架之上
微服务架构(Microservices Architecture)
微服务架构的核心思想是,一个应用是由多个小的、相互独立的、微服务组成,这些服务运行在自己的进程中,开发和发布都没有依赖。
多数人对于微服务的定义是,把本来运行在单体架构中的服务拆分成相互独立的服务,并运行在各自的进程中。在我看来,不仅如此。最关键的地方在于,不同的服务能依据不同的业务需求,构建的不同的技术架构之上,并且聚焦在有限的业务功能之上。
因此,在线零售网站可以用图2的微服务架构来简单概括。基于业务需求,需要增加一个账户服务微服务,因此构建微服务绝不是在单体架构中把服务拆分开这么简单。
图2:微服务架构
微服务设计:规模、范围、业务功能
你可能从零开始用微服务来构建应用,也可能重构现有系统,确定微服务的规模,范围和功能都特别重要。让我们讨论一些有关微服务设计的关键问题和对它的误解:
“微”很容易被误解:很多开发者会倾向于把服务往尽量小的颗粒度去做
在SOA方式下,服务都还是以单体架构在运行,用于支持不同的功能。如果依旧采用SAO类似的服务,仅仅是名义上叫做微服务,并不能带来任何微服务的优势。
那我们在微服务中应该怎样设计呢。以下是微服务的设计指南:
职责单一原则(Single Responsibility Principle):把某一个微服务的功能聚焦在特定业务或者有限的范围内会有助于敏捷开发和服务的发布。
设计阶段就需要把业务范围进行界定。
需要关心微服务的业务范围,而不是服务的数量和规模尽量小。数量和规模需要依照业务功能而定。
于SOA不同,某个微服务的功能、操作和消息协议尽量简单。
项目初期把服务的范围制定相对宽泛,随着深入,进一步重构服务,细分微服务是个很好的做法。
微服务消息
在单体架构中,不同功能之间通信通过方法调用,或者跨语言通信。SOA降低了这种语言直接的耦合度,采用基于SOAP协议的web服务。这种web服务的功能和消息体定义都十分复杂,微服务需要更轻量的机制。
同步消息 – REST, Thrift
同步消息就是客户端需要保持等待,直到服务器返回应答。REST是微服务中默认的同步消息方式,它提供了基于HTTP协议和资源API风格的简单消息格式,多数微服务都采用这种方式(每个功能代表了一个资源和对应的操作)。
Thrift是另外一个可选的方案。它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。
图3:REST接口,对外微服务
异步消息 – 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采用WSDL,WSDL过于复杂并且和SOAP紧耦合,不适合微服务。
REST设计的微服务,通常采用Swagger和RAML定义约定。对于不是基于REST设计的微服务,比如Thrift,通常采用IDL(Interface Definition Languages),比如Thrift IDL。
微服务集成 (服务间通信)
微服务架构下,应用的服务直接相互独立。在一个具体的商业应用中,需要有些机制支持微服务之间通信。因此服务间的通信机制特别重要。
SOA体系下,服务之间通过企业服务总线(Enterprise Service Bus)通信,许多业务逻辑在中间层(消息的路由、转换和组织)。微服务架构倾向于降低中心消息总线(类似于ESB)的依赖,将业务逻辑分布在每个具体的服务终端。
大部分微服务基于HTTP、JSON这样的标准协议,集成不同标准和格式变的不再重要。另外一个选择是采用轻量级的消息总线或者网关,有路由功能,没有复杂的业务逻辑。下面就介绍几种常见的架构方式。
点对点方式 – 直接调用服务
点对点方式中,服务之间直接用。每个微服务都开放REST API,并且调用其它微服务的接口。
图4:通过点对点方式通信
很明显,在比较简单的微服务应用场景下,这种方式还可行,随着应用复杂度的提升,会变得越来越不可维护。这点有些类似SOA的ESB,尽量不采用点对点的集成方式。
点对点有下面几个缺点:
非功能的需求,比如用户授权、限制、监控,需要在每个微服务中进行实现
随着功能的演进,服务会变得越来越复杂。
不同的服务直接,客户端和服务直接没有控制功能(监控、跟踪、过滤)
直接通信在大型系统设计中,一般是反面典型。
因此,如果设计一个大型的微服务系统,尽量避免点对点的通信方式,也不能像ESB这样重量级的总线。而是一个轻量级的总线,能够提供非业务功能的抽象。这就是API网关方式。
API-网关方式
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能个。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。
图5:通过API-网关暴露微服务
用我们网上商店的例子,在图5中,所有的业务接口通过API网关暴露,是所有客户端接口的唯一入口。微服务之间的通信也通过API网关。
采用网关方式有如下优势:
有能力为微服务接口提供网关层次的抽象。比如:微服务的接口可以各种各样,在网关层,可以对外暴露统一的规范接口。
轻量的消息路由、格式转换。
统一控制安全、监控、限流等非业务功能。
每个微服务会变得更加轻量,非业务功能个都在网关层统一处理,微服务只需要关注业务逻辑
目前,API网关方式应该是微服务架构中应用最广泛的设计模式。
消息代理方式
微服务也可以集成在异步的场景下,通过队列和订阅主题,实现消息的发布和订阅。一个微服务可以是消息的发布者,把消息通过异步的方式发送到队列或者订阅主题下。作为消费者的微服务可以从队列或者主题共获取消息。通过消息中间件把服务之间的直接调用解耦。
图6:异步通信方式
通常异步的生产者/消费者模式,通过AMQP、MQTT等异步消息规范。
数据的去中心化
单体架构中,不同功能的服务模块都把数据存储在某个中心数据库中。
图7:单体架构,用一个数据库存储所有数据
微服务方式,多个服务之间的设计相互独立,数据也应该相互独立(比如,某个微服务的数据库结构定义方式改变,可能会中断其它服务)。因此,每个微服务都应该有自己的数据库。
图8:每个微服务有自己私有的数据库,其它微服务不能直接访问。
数据去中心话的核心要点:
每个微服务有自己私有的数据库持久化业务数据
每个微服务只能访问自己的数据库,而不能访问其它服务的数据库
某些业务场景下,需要在一个事务中更新多个数据库。这种情况也不能直接访问其它微服务的数据库,而是通过对于微服务进行操作。
数据的去中心化,进一步降低了微服务之间的耦合度,不同服务可以采用不同的数据库技术(SQL、NoSQL等)。在复杂的业务场景下,如果包含多个微服务,通常在客户端或者中间层(网关)处理。
治理去中心化
通常“治理”的意思是构建方案,并且迫使人们通过努力达到组织的目标。SOA治理指导开发者开发可重用的服务,以及随着时间推移,服务应该怎么被设计和开发。治理建立了服务提供者和消费者之间对于服务的协定,告诉消费者能从服务提供获取到什么样的支持。
SOA中有两种常见的治理:
设计时的治理-定义和控制服务的创建、设计和服务策略的实施。
运行时的治理-确保执行过程的策略。
那么微服务中的治理是什么意思呢?
在微服务架构中,不同的微服务之间相互独立,并且基于不同的平台和技术。因此,没有必要为服务的设计和开发定义一个通用的标准。
总结微服务的治理去中心化如下:
微服务架构,在设计时不需要集中考虑治理。
每个微服务可以有独立的设计、执行决策。
微服务架构着重培养通用/可重用的服务。
运行时的治理,比如安全级别保证(SLA),限制,监控,安全和服务发现,可以在API网关层处理。
服务注册和发现
微服务架构下,有大量的微服务需要处理。由于微服务的快速和敏捷研发,他们的位置可能会动态变化。因此在运行时需要能够发现服务所在的位置,服务发现可以解决这个问题。
服务注册
注册中心有微服务的实例和位置信息,微服务在启动时向注册中心注册自己的信息,关闭时注销。其它使用者能够通过注册中心找到可用的微服务和相关信息。
服务发现
为了能找到可用的服务和他们的位置信息,需要服务发现机制。有两种发现机制,客户端发现和服务端发现。
客户端发现 – 客户端或者API网关通过查询服务注册中心或者服务的位置信息。
图9:客户端发现
客户端/API网关必须调用服务注册中心组件,实现服务发现的逻辑。
服务端发现 – 客户端/API网关把请求发送到已知位置信息的组件(比如负载均衡器)。组件去访问注册中心,找到微服务的位置信息。
图10:服务端发现
类似Kubernetes(http://kubernetes.io/v1.1/docs/user-guide/services.html )这种微服务部署解决方案,就提供了服务器端的自动发现机制。
部署
微服务的部署方式也特别重要,以下是关键:
能够独立于其他微服务发布或者取消发布
微服务可以水平扩展(某一个服务比其他的请求量大)
快速构建和发布
微服务之间的功能不相互影响
Docker(一个运行在linux上并且开源的应用,能够协助开发和运维把应用运行在容器中)能够快速部署微服务,包括关键几点:
把微服务打包成Docker镜像
启动容器实例
改变实例的数量达到扩容需求
相对于传统的虚拟机模式,利用docker容器,构建、发布、启动微服务将会变得十分快捷。
通过Kubernetes能够进一步扩展Docker的能力,能够从单个linux主机扩展到linux集群,支持多主机,管理容器位置,服务发现,多实例。都是微服务需求的重要特性。因此,利用Kubernetes管理微服务和容器的发布,是一个非常有力的方案。
图11:构建和部署服务的容器
图11,展示了零售应用的微服务部署。每个服务都在独立的容器中,每个主机有两个容器,通过kubernetes可以随意调整容器的数量。
安全
在实际运行环境中,微服务的安全也非常重要。我们先看下单体架构下安全是如何实现的。
一个典型的单体应用,安全问题主要是“谁调用”,“调用者能做什么”,“如何处理”。服务器接收到请求后,一般都在处理链条的最开始,通过安全组件来对请求的信息进行安全处理。
我们能直接把这种处理方式应用在微服务架构中吗?答案是可以的,需要买个微服务都实现一个安全组件从资源中心获取对应的用户信息,实现安全控制。这是比较初级的处理方式。可以尝试采用一些标准的API方式,比如OAuth2和OpenID。深入研究之前,可以先概括下这两种安全协议以及如何使用。
OAuth2-是一个访问委托协议。需要获得权限的客户端,向授权服务申请一个访问令牌。访问令牌没有任何关于用户/客户端的信息,仅仅是一个给授权服务器使用的用户引用信息。因此,这个“引用的令牌”也没有安全问题。
OpenID类似于OAuth,不过除了访问令牌以外,授权服务器还会颁发一个ID令牌,包含用户信息。通常由授权服务器以JWT(JSON Web Token)的方式实现。通过这种方式确保客户和服务器端的互信。JWT令牌是一种“有内容的令牌”,包含用户的身份信息,在公共环境中使用不安全。
现在我们看下如何在网络零售网站中应用这些协议保障微服务的安全。
图12:通过OAuth2和OpenID解决安全问题
图12中所示,是实现微服务安全的关键几步:
所有的授权由授权服务器,通过OAuth和OpenID方式实现,确保用户能访问到正确的数据。
采用API网关方式,所有的客户端请求有唯一入口。
客户端通过授权服务器获得访问令牌,把令牌发送到API网关。
令牌在网关的处理 – API网关得到令牌后,发送到授权服务器获得JWT。
网关把JWT和请求一起发送到微服务中。
JWT包含必要的用户信息,如果每个微服务都能够解析JWT,那么你的系统中每个服务都能处理身份相关的业务。在每个微服务中,可以有一个处理JWT的轻量级的组件。
事务
在微服务中怎么支持事务呢?事实上,跨多个微服务的分布式事务支持非常复杂,微服务的设计思路是尽量避免多个服务之间的事务操作。
解决办法是微服务的设计需要遵循功能自包含和单职责原则。跨越多个微服务支持分布式事务在微服务架构中不是一个好的设计思路,通常需要重新划定微服务的职责。某些场景下,必须要跨越服务支持分布式事务,可以在每个微服务内部利用“组合操作”。
最关键的事情是,基于单职责原则设计微服务,如果某个服务不能正常执行某些操作,那么这个服务是有问题的。那么上游的操作,都需要在各自的微服务中执行回滚操作。
容错设计
微服务架构相比较单体的设计而言,引入了更多服务,在每个服务级别会增加发生错误的可能性。一个服务可能由于网络问题、底层资源等各种问题导致失败。某个服务的不可能不应该影响整个应用的崩溃。因此,微服务系统必须容错,甚至自动回复,对客户端无感知。
任何服务在任何时间都有可能出问题,监控系统需要能够发现问题,并且自动恢复。微服务环境下有不少常用的模式。
线路中断
微服务中请求的失败率达到一定程度后,系统中的监控可以激活线路中断。当正常请求的数量恢复到一定程度后,再关闭线路中断的开关,使系统回复到正常状态。
这个模式可以避免不必要的资源消耗,请求的处理延迟会导致超时,借此可以把监控系统做的更完善。
防火墙
一个应用会有很多微服务租车,单个微服务的失败不应该影响整个系统。防火墙模式强调服务直接的隔离性,微服务不会受到其它微服务失败的影响。
处理超时
超时机制是在确定不会再有应答的情况下,主动放弃等待微服务的响应。这种超时应该是可配置的。
哪些情况下,如何使用这些模式呢?大多数情况,都应该在网关处理。当微服务不可用或者没有回复时,网关能够决定是否执行线路中断或者启动超时机制。防火墙机制同样重要,网关是所有请求的唯一入口,一个微服务的失败不应该影响到其它微服务。网关也是获得微服务状态、监控信息的中心。
微服务,企业集成,API管理
我们已经讨论了微服务的架构和各种特性,以及如何应用在一个现代的IT系统中。同时也需要意识到,微服务不是解决所有问题的灵丹妙药。盲目追求流行的技术概念并不能解决掉企业IT系统的问题。
微服务有很多优势,但是仅靠微服务不能解决企业IT中的所有问题。例如,微服务需要去除ESB,但是现实的IT系统中,大量的应用和服务是基于ESB而不是微服务。集成现有的系统,需要一些集成总线。实际情况是,微服务和其它企业架构并存。
本文转载