写在前面的话,这些文章是在NGINX的官方博客中发现的。是关于微服务的一系列的文章,本着好东西共享一下,同时也丰富一下自己,把这些翻译成中文,但是后来发现国内已经有很多人翻译了,我只能说我的品位还不差,和各位大牛步调还算一致,虽然已经有人翻译了,但是本着“一千个读者就有一千个哈姆雷特”的想法继续了下去。
本文出自Introduction to Microservices,作者 Chris Richardson, 写于2015年5月19日
微服务目前得到越来越多的关注:文章、博客、社交媒体上的讨论、会议上的演讲。这种趋势使得微服务快速地进入到Gartner Hype cycle中提到的peak of inflated expectations 阶段。但同时在软件社区也有一些质疑者,认为微服务不是什么新东西。反对者认为微服务只是SOA
(service-oriented architecture)架构的别名。即使质疑和大肆地宣传仍旧存在,但微服务架构模式已经显示出它的巨大优势——特别是当谈到敏捷开发和复杂企业应用的交付时。
这篇文章是讨论、构建和部署微服务系列文章的第一篇,你会了解到微服务架构的方式以及它和传统的单体架构模式的异同。这一系列文章将会阐述微服务架构中的各个方面。你将会学习到微服务架构的优点和缺点,以及它是否适合你的工程,如何应用它。
首先让我们来看一下你为什么应该考虑使用微服务。
设想一下,你正在构建一个旨在与Uber和Hailo竞争的全新的打车应用。在初步的讨论和需求分析之后,你会手动或使用特定平台提供的生成器,比如Rails、Spring Boot、Play或者Maven,创建一个新的工程,,这个新的应用将会有一个模块化的六角形架构,如图1-1所示:
图 1-1 打车应用例图
这个应用的核心是商业逻辑,它通过定义服务、领域对象和事件的模块来实现,围绕着这个核心是一些适配器来和外部的世界连接,这些是适配器包括数据库访问组件、生产和消费消息的消息组件、暴露API或者实现UI的Web组件。
即使有逻辑模块架构,这个应用也会被打包和部署成单体应用,真正的打包格式依赖于应用使用的语言和框架。例如,许多的Java
框架被打包成WAR
文件并且被部署到应用服务器,例如Tomcat或者Jetty。有的其他Java
应用被打包成独立的可执行的JAR
文件。类似的,Rails
和Node.js
应用被打包成层次分明的目录结构。
以这种方式开发的应用是极其普遍的,它们开发起来非常简单,因为我们的IDE和其他的工具关注于构建简单的应用。这种应用也非常容易测试,你可以通过简单的启动应用并且使用类似于Selenium
的测试包测试UI以实现端对端的测试。单体应用也是非常容易进行部署的,你仅仅需要做的就是复制已经打包好的应用到服务器目录下。你也可以通过在负载均衡器后面运行多个拷贝来扩展应用。在早期的时候,这种工程工作的很好。
不幸的是,这个简单的方法有巨大的限制。成功的应用会随着时间推进,变得越来越大。在每次需求更改或者功能增加时,你的开发团队会实现更多的业务功能,这意味要增加很多行代码。在几年后,你的简单的应用将会成长为一个巨大的单体应用。为了提供一个极其简单的例子,我(指作者)最近和一个开发者进行了交流,这个开发者正在写一个工具来分析他们的应用使用的JAR彼此之间的依赖,但是这个应用竟然有数以百万行的代码,我确信一定很多的开发者经过很多年的辛苦努力才创造出来了这样的巨无霸。
一旦你的应用成为一个巨大的、复杂的单体应用,你的开发团队一定会陷入痛苦的泥淖不能自拔,任何对于敏捷开发和交付的尝试最终都会陷入挣扎之中。一个主要的问题是这个应用过于复杂了。它太大以至于对于任何一个开发者都无法完全理解。结果就是,修复bug和正确地实现新功能变得非常困难,并且会消耗大量的时间。更重要的是,这将会趋向于一个向下的螺旋。如果代码库理解起来很困难,那么也就不会给出恰当的改善方案。你最终会得到一个巨大的、难以理解的big ball of mud.
这种应用程序的巨大的规模也会使得开发工作放缓,应用越大,启动的时间越长。在调查中,一些开发者说他们的应用的启动时间长达12分钟。我也听到了一些应用竟然需要40分钟来启动整个应用。如果开发者经常需要重启应用服务器,那么他们一天当中的绝大部分时间都会花费在等待当中,这样生产力将会急剧下降。
另外一个伴随着单体应用的问题是它对于持续部署是一个巨大的障碍。如今,SaaS
应用的发展使得一天当中要提交多次更改到生产环境中。单体应用处理起这种情况来是十分困难的,为了更新它的任何一部分,都必须要重新部署整个应用。上文提到的极长的启动时间也会造成负面的影响。因为巨型应用的变化造成的影响通常不是很容易觉察到,所以你可能需要进行广泛的手工测试。结果就是,持续交付几乎是不可能的。
当不同的模块对资源的需求出现冲突时,单体应用也是很难进行扩展的。例如,一个模块可能实现了CPU密集型图像处理逻辑,将会被部署到Amazon的EC2 Compute Optimized instances。另外一个模块可能是一个内存数据库,更适合于EC2 Memory-optimized instances。然而这些模块被部署到一起,你必须对硬件的选择进行妥协。
另外一个问题关于单体应用的是可靠性,因为所有的模块运行在相同的进程中,任何模块中的一个bug,例如内存泄漏,都可能使整个应用进程垮掉。此外,因为应用的所有实例都是相同的,这个bug将会影响整个应用的可靠性。
最后的但是也不能轻视的问题,单体应用使得采用不同的语言和框架变得异常困难。例如,假设你使用XYZ框架写了200万行代码,如果使用新的ABC框架来重写整个应用,会是十分昂贵的(包括时间上和金钱上),即使这个框架相对于之前的更好。结果,采用新技术就成为了一个巨大的障碍。你只能受限于这个工程开始时选择的技术。
总结起来,你有一个成功的商业应用,已经成长为巨型的单体应用,它将很难被开发者完全理解。过时的、没有生产力的开发技术也使得雇佣优秀的开发者变得十分困难。进行扩展也将变得十分艰难,同时也是不可靠的。结果,敏捷开发和应用交付变得几乎不可能。
那么对于这种情况,你能做些什么?
许多公司,像Amazon、eBay和Netflix,已经通过采用微服务架构模式解决了这个问题。微服务模式将你的应用划分成一系列较小的并且内部互联的服务,而不是构建巨大的单体应用。
一个典型的服务要实现一系列不同的特点或者功能,比如订单管理、客户管理等等。每个微服务都是一个小型的应用,有它自己的包括各种业务逻辑和多种适配器的六角形结构。一些微服务会暴露出API以供其他的微服务或者应用客户端消费。其他的微服务可能实现了Web UI。在运行时,通常情况下每个实例都是一个云端的虚拟机或者Docker容器。
一种如上文描述的可能的系统分解方式如图1-2 所示:
图1-2 一个单体应用分解成微服务
现在这个应用的每个功能都被它自己的微服务实现。另外,这个Web 应用被分成了一系列更简单的Web应用——例如在打车应用中,一个为了乘客,一个为了司机。这个使得它更容易为特定的用户、设备或者特定的用例提供不同的部署方式。
每个后端的服务暴露出REST API
,并且大多数服务消费其他服务提供的API。例如,司机管理模块(Driver Management)使用通知服务器(Notification server)告诉没有订单的司机潜在的订单。UI服务(UI Services)调用其他的服务来渲染页面。服务可能也使用异步的、基于消息的通信。服务内的通信内容在后面的文章会加以详述。
一些REST API
也被提供给司机和乘客使用的移动应用。然而,这些APP
并不会直接访问后端的服务。而是通过API网关来作为中介提供通信。API网关负责类似于负载均衡、缓存、访问控制、API衡量统计以及监控,可以通过使用NGINX有效的实现。
图1-3 扩展立方体,应用在开发和交付上
微服务架构模式对应于扩展立方体(Scale Cube)的Y轴,扩展立方体是一个来自于《可扩展的艺术》(The Art of Scalability)一书中提出的3D模型。其他两个扩展轴是X轴扩展,这种扩展包括在一个负载均衡器后面运行多个相同的应用;Z轴扩展(或者说数据复制),在这种方式中,通过使用请求的属性(例如,一行数据的主键或者顾客的唯一标识)来路由该请求到特定的服务器上。
应用通常情况下将三种方式结合使用进行扩展,Y轴扩展方式将应用分解成如图1-2所示的微服务。在运行时,为了吞吐量和可用性,X轴扩展方式在负载均衡器后面运行每个服务的多个实例。一些应用可能通过使用Z轴的扩展方式来划分这些服务。图1-4 表明出行管理服务是如何被部署到运行在Amazon EC2的Docker
容器中的。
图1-4 使用Docker部署出行管理服务
在运行时,出行管理服务包括多个服务实例。每个服务实例是一个单独的Docker容器,为了高度可用,这些容器都运行在多个云虚拟机上。在服务之前是一个负载均衡器比如Nginx,它分布请求到不同实例。这个负载均衡器也可以处理其他的事情,比如缓存、访问控制、API衡量和监控。
微服务架构模式也极大地影响了应用和数据库之间的关系。每个服务都有自己的数据库模式,而不是和其他的服务共享一个数据库模式。在另一方面,这种方法显然与构建企业级的数据模型的想法相悖。并且这种方式也会造成一些数据的重复。然而,如果想要从微服务架构中受益,每个服务有自己的数据库模式是十分必要的,因为它确保了彼此之间的松耦合。图1-5 显示对于这个示例应用的数据库架构。
图1-5 打车应用的数据库架构
每个服务有自己的数据库,然而,一个服务可以使用一种最能满足它的需求的数据库类型,这就是所谓的多边形持久化架构。例如,司机管理会发现靠近潜在乘客的最近的司机,此时必须使用支持高效地理查询的数据库。
从表面上来看,微服务架构模式和SOA
架构是十分相似的。然而,一种想法认为微服务架构就是没有商业化的SOA架构,也没有web service specifications(WS-*
)和企业服务总线(Enterprise Service Bus, ESB) 的拖累。
基于微服务的应用偏爱简洁的方式、轻量级的协议比如REST
,而不是WS-*
。它们也避免使用ESB
,而是通过在内部实现类似于ESB
功能,微服务架构也拒绝了SOA
的其他部分,比如用于数据访问的 Canonical Schema概念。
微服务架构有很多重要的优点。首先,它解决了复杂性的问题。它将一个本会成为巨大的单体应用分解成一系列服务。在保证整体的功能没有改变的前提下,应用被分成很多的模块或者服务。每个服务都有明确的边界,这种边界是通过远程调用(RPC
)或者消息驱动的API
来确定的。微服务架构模式强制实行模块化,这种在单体应用中是很难去实现的。结果就是,单个服务开发起来更快、也更容易理解和维护。
第二,这个架构使得每个服务可以被独立地开发。开发者可以自由地选择合适的技术,只要这个服务满足API
交互规范。当然,大多数组织想通过限制技术选项来避免过于无拘无束。然而,这种自由意味着开发者没必要在开始新工程时继续使用过时的技术。当开发一个新的服务时,他们可以选择使用当前的技术。另外,因为服务相对来说很小,那么使用当前的技术重写旧的服务会更加可行。
第三,微服务架构模式使得每个微服务可以被独立部署。当本地的服务发生变化时,开发者不再需要协调这种变化引起的部署问题。只要被测试通过,这些变化就可以被部署。UI
团队可以执行AB测试,并且可以快速地迭代UI
的变化。微服务架构模式使得持续部署成为可能。
最后,微服务架构模式使得每个服务可以被独立地扩展。为了满足吞吐量和可用性,你可以对每个服务部署任意的实例数量。另外,你可以使用最能满足服务资源要求的硬件。例如,你可以部署CPU密集型图像处理服务到EC2 Compute Optimized instances,部署内存数据库到EC2 Memory-optimized instances。
正如Fred Brooks 30年前在《人月神话》一书中提到的,“不存在银色的子弹”。像许多其他的技术一样,微服务架构也有缺点。一个缺点就是名称本身。术语“微服务”过于强调服务的规模。结果,有一些开发商提倡构建极其细粒度的服务,例如只包括10行到100行的代码。虽然小型服务可能更合适,但是要记住:服务小型化是一种手段,并不是主要的目标。微服务的目标是充分地分解应用以便促进敏捷开发和部署。
微服务的另外一个重要的缺点是微服务应用本质是分布式系统而引起的复杂性。开发者需要选择和实现基于RPC
或者消息的进程间通信机制。另外,开发者也需要写代码来处理局部故障,因为请求的目的可能是缓慢或者无法使用的。虽然这不是火箭科学,但是远比单体应用复杂的多。模块之间通过语言级别的方法或者过程调用进行通信。
微服务面临的另外一个挑战是分区数据库架构。商业事务更新多个业务实体是十分普遍的。这种事务在单体应用中是极其简单的,因为只有一个数据库。但是在基于微服务的应用中,你需要更新属于不同服务的多个数据库。使用分布式事务通常并不是一个选择,不仅因为CAP理论。如今高度扩展的NoSQL数据库和消息代理根本不支持分布式事务。你将不得不使用一个基于最终一致性的方法,这对于开发者而言更有挑战性的。
针对微服务的测试也是十分复杂的。例如,对于现代化的框架,比如Spring Boot
,编写一个测试类来启动单体的Web
应用并且测试它的REST API
是十分简单的。比较起来,针对一个微服务的相同的测试类可能需要启动它所依赖的所有服务,或者至少为这些服务配置一些“临时工”。老调重弹,这不是火箭科学,但是却不能低估这样做而引起的复杂性。
另一个微服务架构模式要面临的主要挑战是实现跨越多个服务的功能变化 。例如,设想一下,你正在实现一个功能,这个功能要求对服务A、B和C进行更改,其中A依赖于B,B依赖于C。在单体应用中,你可以简单地更改对应的模块,集成这些变化,并且通过一次部署即可完成。比较而言,在微服务架构模式中,你需要小心地计划和协调针对每个服务的变化。例如,你需要首先更新C,然后B,最终是A。幸运的是,大多数变化通常只影响一个服务;协调多个服务的变化的要求相对而言是较少的。
部署基于微服务的应用也是十分复杂的。一个单体应用可以在一个传统的负载均衡器后部署在一系列相同的服务器上。每个应用实例都需要配置基础服务,比如数据库和消息代理的位置(比如主机和端口)。反过来,一个微服务应用一般包含大量的服务。例如,Hailo has 160 different services,Netflix有超过600个服务,根据Adrian Cockcroft 。每个服务有多个运行时实例,这就导致还有很多其他的组件需要配置、部署、扩展和监控。另外,你也需要实现服务发现机制(service discovery mechanism ),使得一个服务可以发现要与之通信的其他服务的位置(主机和端口)。传统的基于清单的方式和人工操作无法扩展到这种复杂程度。成功部署一个微服务要求开发者更好地掌握部署的方法和高度的自动化。
一个自动化的方法是使用现成的平台即服务(platform-as-a-service,PaaS),比如Cloud Foundry 。PaaS
提供给开发者一个简单的方法来部署和管理他们的微服务。这使他们免于采购和配置IT
资源。同时,配置PaaS
的系统和网络专业人员也可以保证最优的方案来和公司的策略保持一致。如果必要的话,另外一种微服务自动化部署的方式是开发你自己的PaaS
。一个典型的起点就是使用集群解决方案,比如Kubernetes ,同时结合容器技术,比如Docker
。
构建复杂的应用实际上是十分困难的。单体应用架构模式仅仅适用于简单、轻量级的应用。如果你使用它构建复杂的应用,最终你会陷入痛苦之中不能自拔。微服务架构模式对于复杂、不断发展的应用是更好的选择,虽然它仍然有缺点并且实现起来充满挑战。
在后面的内容中,我(指作者)将深入微服务架构模式的不同部分,并且讨论如服务发现、服务部署选项,将单体应用重构成微服务应用的策略。