微服务
微服务技术现在获得了很多的关注:论文,博客,社交媒体的讨论和会议展示。他们在加德纳技术成熟度曲线(Gartner Hype Cycle)上迅速的驶向巅峰。与此同时,软件社区的怀疑者认为微服务没有什么新东西。有些反对者声称微服务只是重新定义SOA(Service-Oriented Architecture)而已。然而,抛开夸张的宣传和怀疑言论,微架构模式有很多优点,特别是它使敏捷开发和交割复杂企业应用成为可能。
本系列会讨论设计,构建和部署微服务。你将会学习实现微服务的途径以及比较它和传统单一架构模式(Monolithic Architecture)的区别。这个系列会描述各种不同的微服务架构元素,是否适合于你的项目,以及怎样应用它。
首先我们考虑为为什么你会考虑使用微服务。
构建单体架构应用
让我们假设你打算开始构建一个全新的出租车呼叫应用,目标是和Uber和Hailo竞争。经过些前期的会议和需求整理,你打算创建一个新的项目,这个项目要么手动生成或是用一些平台工具产生,比如Rails,Spring Boot,Play或Maven。这个新的应用会有一个模块(Hexagonal Architecture) 如图1-1
应用的核心是商业逻辑,它是被不同的模块实现,这些模块中定义服务,领域对象和事件。核心的外围是一些适配器,通过这些适配接口和外围交互。适配器的例子包括数据库访问组件,信息交互组件(产生和消费消息)和Web组件,Web组件要么暴露API要么实现用户界面(UI)。
尽管拥有逻辑模块架构,这个应用仍然被打包成单一整体部署。实际的格式依赖于应用的语言和框架。例如,许多的Java应用打包成WAR文件并部署到应用服务器上,如Tomcat或Jetty。其他的Java应用打包成自我包含的可执行JARs。相似地Rails和Node.js应用打包成一个目录层次结构。
这种单一架构的应用非常常见。他们容易开发,因为我们的IDE和其他工具适于构建单一架构应用。这类的应用也易于测试。你可以简单地启动应用和测试界面利用Selenium实现端到端的测试。单一体的应用易于部署。你仅需要拷贝打包过的应用到服务器上。你可以运行多个应用副本,通过负载均衡器扩容应用。在项目的前期这种方式工作的很好。
驶向单体架构地狱
不幸的是,这种简单途径有非常严重的缺陷。成功的应用会有一个惯性,就是随着时间的推移,它会成长变得非常庞大。在每一迭代期(Sprint),你的开发组实现一些用户需求(User Story),当然了,这意味着增加很多行代码。经过几年的时间。你的小的,简单的应用会成长成异常庞大的单一体(Monstrou Monolith)。举个极端的例子,最近我和一个开发人员聊天,他正在写一个工具,用于分析几百万行代码的应用和成千上万JARs之间的依赖关系。我确信为了开发这样一个野兽级的应用,肯定会用掉很多开发人员几年的工作时间。
一旦你的应用变成庞大和复杂的单一体架构,你的开发组织肯能会在痛苦中。任何尝试敏捷开发和交割都会很挣扎。一个主要的问题是这个应用异常复杂。几乎不可能让单一一个开发者完全理解它的全部。这就会使修复缺陷(Bug)和实现新的功能(new features)变得困难和耗时。更进一步,这是下降中的漩涡。如果基本代码难于理解,那么代码更不易更改。你将会陷于麻烦和不能理解的泥沼中。
应用的大小也会减慢开发速度。应用越大,它的启动时间越长。我统计过开发者关于单一体应用软件大小和性能的关系,有些启动用时12分钟。我也听说过花费40分钟启动应用的例子。如果开发者经常地重启应用服务器,那么他们一天的大部分时间在等待,开发效率肯定很低。
体量大,复杂的单体式应用的另一个问题是部署困难。现在,以现在的技术状态,像SaaS这类的应用,一天会被更新几次。这种方式对于单体式应用的部署会异常的困难,因为即使小的更新,你必须重新部署整个应用。耗时的启动时间也是件麻烦事。通常地,因为更新没有理解清楚,很可能你需要大量的说动测试区验证。结果会导致持续部署几乎也是不可能任务。
当不同模块资源需求矛盾时,单一体架构的应用也很难扩容。举例说,一个模块或许实现耗计算资源的(CPU-Intensive)图片处理逻辑,那么理想情况下是应当部署到Amazon计算优化的实例(EC2 Compute Optimized)上去。另外一个模块或许是内存数据库,那么最好是应该部署到内存优化的实例中去(Memory-Optimized Instances)。然而,以为这些模块被封装到了一起,你不得不折中你的硬件选择。当然应用就无法调整到最优。
单体应用的另外一个问题是可靠性。因为所有的模块运行于同一进程中,任何一个模块中的缺陷(Bug),比如内存泄漏,有可能使整个进程崩溃。更进一步,因为所有这个进程的实例是一一样的,这个缺陷将会影响整个应用的使用。
最后但同等重要的是,单体架构应用很难采用新的架构和语言。比如,让我们假设你的应用用XYZ框架写成,大约有2百万行代码。这会令用ABC框架重写你的应用变的异常昂贵(时间和成本),即使ABC框架好很多。这结果导致当你想采用新技术刷新(TEC Refresh)你的应用时候,不得不面对这巨大的障碍。在这个项目的开始时,你会被困在框架和架构选择上。
总结一下,你有个成功的商业应用,它已成长为体量巨大的单体,很少有开发人员能完全理解它。它用一些陈旧的不是很高产的技术实现,这让雇佣非常有才能的开发人员变异常困难。这个应用很难扩容并且不可靠。结果导致敏捷开发和交割变得几乎不可能。
那么,接下来,我们该怎么做?
微服务-处理复杂性
很多的组织,如Amazon,eBay和Netfix公司,已经用微服务架构模式(Microservice
Architecture Pattern)解决了这个问题。不去构建体量庞大的单体式应用,而是分解你的应用成更小的,相互连接的服务。
一个服务通常实现一组独特的功能,比如,订单管理,客户管理等。每个微服务是一个迷你小应用(mini-application),每个这样的应用都有自己的架构(Hexagonal Architecture)包括各种适配器实现的商业逻辑。有些微服务会提供API给其他的微服务和应用的客户端。其他微服务或许或实现Web界面。在运行态,每个实例通常是虚拟机器(VM)或是Docker容器。
举例说来,一个可能的前面提到系统的分解如下图1-2
应用的每个功能区域现在是被它自己的微服务实现。更进一步讲,在出租车呼叫服务系统中,每个Web应用被分解成一组更小的Web应用-比如一个是为乘客的应用,一个是为司机的应用。这种分解简化了部署并提供给特定用户,设备和特定的用例独特的体验。
每个后端服务暴露一组REST API接口,并且大多数的服务都调用其他的服务。举例说,司机管理服务(Driver Management)调用通知服务(Notification)告知一个可用的司机关于一个可能的行程(TRIP)。UI服务为了渲染页面调用其他的服务。服务或许用异步的,基于消息通信。服务之间通讯在以后提供更多的细节。
一些REST API也暴露给司机和乘客移动App。然而,这些APP不直接访问后端的服务,而是通过中中间的API网关访问。API网关负责负载均衡,缓存,访问控制,API测量和监控。API网关可以借助于NGINX技术实现。下一章节将会讨论API网关的细节。
微服务架构模式相当于规模方块(Scale Cube)的Y轴扩容,规模方块是非常优秀的”扩容艺术“(The Art of Scalability )这本书中提出一种3D扩容能力模型。
其他的两个扩容轴是X轴和Z轴,X轴扩容相当于运行多个相同应用副本,通过负载均衡器均衡访问。Z轴扩容(数据分区),一个请求的属性(如,行的主键或是客户的ID)用于路由这个请求到一个特定的服务器。
应用通常会同时采用这三种扩容方式。Y轴扩容分解应用成微服务,如图1-3。
在运行时,X轴扩容运行多个服务实例,每个服务通过服务均衡器提供吞吐量和可用性。有些应用或许也会用Z轴扩容分区服务。图1-4展示行程管理服务如何用Docker容器部署在Amazon EC2。
在运行时态,行程管理服务包括多个服务实例。每个服务实例是一个Docker容器。为了高可用,容器运行在多个云虚拟主机上(VM)。服务实例的前端是负载均衡器比如NGINX,均衡器在实例之间分配服务请求。均衡器或许也会处理像缓存,存取控制,API测量和监控等。
微服务架构模式极大地影响应用和数据库之间的关系。不是不同的服务分享同单一个数据库,而是每个服务有自己的数据库模式。另外,这种途径和企业级数据模型的想法是不相合的。它会导一些致数据的重复。然而,每个服务拥有自己数据库是能从微服务获益的关键所在,因为它确保了服务之间松度耦合。图1-5数据库架构应用的样例。
表面上看,微服务架构模式类似于SOA。两种架构都包含一组服务。然而,一种说法是微服务架构没有商业化,Web服务规范(ws-*)和企业服务总线(Enterprise Service Bus-ESB)包袱。基于微服务的应用倾向于简单,轻量级协议比如REST,而不是WS-*。这种架构自身中也尽量避免使用类ESB总线功能。微服务架构模式也拒绝使用SOA的其他部分,比如数据访问的规范架构模式(Canonical Schema)。
微服务优点
微服务架构模式有很多的优点。首先,它能处理复杂的问题。它分解体量庞大单体应用成一组服务。总功能保持不变,同时,应用已经被分解成可控的服务。以RPC(Remote
Procedure Call)驱动或是消息驱动(Message-Driven)API方式提过服务,每个服务有自己很好的边界定义。微服务强化了模块级别,这些是在单体架构基础代码很难取得的隔离。因此,单个服务开发更快,更容易理解和维护。
第二点,这种架构使每个服务被一个Team独立的开发,这个Team可以专注于这个服务上。提供了服务的协议,开发人员可以任意选择合理的技术去实现。当然了,大多数的开发组织为避免完全混乱会限制一些技术选择。然而,这种自由意味着开发者不必继续沿用在项目开始时存在的过时的技术。当写一个新的服务,他们能选择用当下流行的技术。更近一步,因为每个服务相对很小,用当下的技术重写一个过时的服务是可行的。
第三,微服务架构模式使每个微服务可以被独立的部署。服务的本地更新,开发人员不需要协调部署其他的服务。这类更改被测试后可以立即被部署。比如UI组可以执行A|B测试,并迅速地迭代UI的变化。微服务架构使持续部署成为可能。
最后,微服务架构的每个服务可以被独立的扩容。你能部署合理的实例数量,以满足容量和可用性约束的需求。更进一步,你可以用最合理的硬件配置满足服务对资源的需求。比如,为计算量大的图片处理服务可以部署到EC2计算优化(Compute Optimized Instance)的实例上去,并且部署内存数据库到EC2内存优化(Memory-Optimized)的实例上去。
微服务的缺点
正如大约30年前,Fred Brooks在”人月神话“(The Mythical Man-Month)里写到,没有”银弹“可以解决所有问题。像其他的技术一样,微服务架构模式也有缺陷。一个缺陷是它自己的名字。术语”微服务“过度的强调服务的大小。事实上,一些开发者拥抱构建极端的10-100行代码的服务。尽管小服务是可取的,但一定要记住小服务是一种手段但这不是它的主要的目标,微服务的目标是最大限度地分解应用以便于敏捷开发和部署。
微服务另一个缺点是源于这样一个事实,微服务应用自身是分布式的。开发人员需要选择和实现基于RPC或消息的进程间通信机制。还有就是,开发者必须写代码处理局部失败,因为一个请求的目的地或许不可用或慢。尽管他们不是什么高不可测的高科技(Rocket Science),但是他们比单体架构更复杂,单体模块间的调用是语言基本的方法或程序调用。
微服务的另一个挑战是分区的数据库架构。商业交易中更新多个实体是相当普遍的操作。这类的交易的实现在单体模式下是很简单的。因为他们只需处理一个数据库。而在以微服务为架构的应用中,你需要更新多个数据库,这些数据库是属于不同的服务的。采用分布式交易事务通常不是一个选项,不仅是因为CAP理论(CAP Theorem)的限制。而是因为他们基本不被现在的NOSQL数据库和消息代理器所支持。你不得不采用一种最终基于一致性的方法(Eventual Consistency-based Approach)去解决,不过这种方法对开发者来说有些复杂。
测试一个微服务架构的应用是更复杂。比如,以现在的框架如Spring Boot,写一个测试案例,它启动一个单体Web应用测试其RESP API是件很简单事。相反的相似的测试案例去测试基于微服务的应用就会很复杂,因为你不得不启动这个服务和它依赖的服务,要么就是需要写一些代码模拟其他服务。一样地,这也不是什么高级技术,但是也不能低估实现其的复杂性。
微服务架构还有一个挑战是实现一些跨服务的更改。比如,让我们想象你去实现一个故事(Story),它需要更改A,B和C服务,其中A依赖于B,B依赖于C。在单体应用中,你可以简单的更新相关的模块,集成这些更改,并且一次部署应用就好。相反地,在微服务架构应用中,你需要小心翼翼计划并且协调每个服务的修改。举例来说,你需要更新服务C,然后服务B,最后服务A。幸运的是大多数的更改通常只会影响一个服务;并且多服务更改的需求相对来说比较少见。
部署一个基于微服务架构的应用也是很复杂的。单体架构应用借助于负载均衡器仅需要部署多个实例到相同配置的多个服务器上。用数据库和消息代理器地址配置每个应用实例。而一个微服务的应用通常包含很多的服务。比如,按照Adrian Cockcroft Hailo有160个不同的服务,Netflix有超过600个服务。
每个服务将会有多个运行实例。有很多的动态部分需要配置,部署,扩容和监控。另外,你也需要实现“服务发现机制”,它使一个服务能发现需要与其通信的其他服务的地址(主机地址和端口号)。传统的基于票据和手工操作的方法不能伸缩地解决这种级别的复杂性。因此,成功地部署微服务应用需要开发者更多的控制和高级别自动化机制。
一种自动化的方法是采用现成的PaaS平台比如Cloud Foundry。PaaS平台提供给开发者一种简便的方法部署和管理他们的微服务。它帮助开发者简化了获取和配置IT资源流程。同时,配置系统和网络的专家可以确保最佳实践和公司的政策一致。
另外一种自动化部署微服务的方法是开发你自己的PaaS系统。一种典型的开始点是采用集群解决方案,比如Kubernetes,结合容器技术如Docker协同解决问题。后面我们会深入看基于软件应用的交接方法,如Nginx,它能轻易地在微服务的级别处理缓存,访问控制,API测量和监控等问题。
总结
构建复杂应用本来就很困难。单体架构模式仅适用于简单,轻量级的应用。如果你应用这种架构在复杂应用中,你注定会步入到痛苦的世界中。尽管微服务架构有它自己的缺点和实现的挑战,它仍然是实现复杂,演进应用比较好选择。
后面的章节,我们深入微服务架构模型的细节中,讨论如服务发现,服务部署选项和重构单体应用到微服务的策略。
翻译自“Microservice -from design to deployment" by Chris Richardson with Floyd Smith.
(待续第二章....)