微服务系列(⼀):微服务架构的优势与不⾜
微服务在当下引起⼴泛关注,成为⽂章、博客、社交媒体讨论和⼤会演讲的热点;在 Gartner 的 “Hype Cycle” 上排名也⾮常靠前。与此同时,在软件社区也有⼈质疑微服务并⾮新事物。反对者认为微服务只是 SOA (Service Oriented Architecture)的⼆度包装。然⽽,⽆论是追捧还是质疑,微服务架构拥有巨⼤优势,尤其是它让敏捷开发和复杂的企业应⽤交付成为可能。
本系列包含 7 篇⽂章,介绍了微服务的设计、构建和部署,并与传统的单体架构进⾏了⽐较。本系列将分析微服务架构的各种因素,你也将了解微服务架构模型的优劣、是否适合你的项⽬,以及如何应⽤。
Chris Richardson 微服务系列全 7 篇:
1. 微服务架构的优势与不⾜
⾸先让我们了解为何要将微服务纳⼊考量。
构建单体应⽤
假设我们要开发⼀款全新的与 Uber 和 Hailo 竞争的打车软件。在前期的会议和需求整理后,你要么需要⼿动创建⼀个新项⽬,要么可以使⽤ Rails、Spring Boot、Play 或者 Maven 来⽣成。这个新应⽤可能采⽤了六边形架构模块,如下图所⽰:
应⽤的核⼼是商业逻辑,它由定义服务、域对象和事件各模块来完成。各种适配器围绕核⼼与外部交互。适配器包括数据库访问组件、⽣成和 consume 信息的消息组件,以及提供 API 或者 UI 访问⽀持的 web 模块。
尽管拥有逻辑缜密的模块化设计,整个应⽤仍然以整体打包和部署,实际格式依赖于应⽤的语⾔和框架。譬如,许多 Java 应⽤被打包为WAR ⽂件,部署在 Tomcat 或者 Jetty 这样的应⽤服务器。有些 Java 应⽤本⾝就是包涵 JARs 的软件包。与此类似,Rails 和 Node.js 应⽤也通过⽬录层级打包。
采⽤此种风格的应⽤⾮常普遍。由于 IDE 和其他⼯具擅长构建单⼀应⽤,这类应⽤也易于部署。这类应⽤也⾮常容易测试。你可以⾮常轻松地进⾏端到端测试,使⽤ Selenium 测试 UI 。整体应⽤也便于部署,只需将软件包复制到服务器。你也可以通过运⾏多个包和负载均衡实现扩展。在项⽬早期这么做⾮常有效。
踏⼊单体架构的地狱
很不幸,这⼀简单的⽅法有着巨⼤的局限。成功的应⽤最终会随着时间变得巨⼤。在每个 sprint 阶段,开发团队都会新加许多⾏代码。⼏年后,原本⼩⽽简单的应⽤会变得臃肿。举个极端的例⼦,我最近与⼀位开发者交流,他正在开发⼀款⼩⼯具,来分析他们应⽤(包括⼏百万⾏代码)中的⼏千个 JARs 的依赖。我相信每年都会有⼤量开发者不遗余⼒地对付这种⿇烦。
⼀旦你的应⽤变得庞⼤、复杂,你的开发团队将饱受折磨,苦苦挣扎于敏捷开发和交付。⼀⼤原因就是应⽤已经格外复杂,庞⼤到任何⼀个开发者都⽆法完全理解。最后,修复 bug 和实施新功能也就极其困难且耗时颇多。更可怕的是,这是⼀个向下的螺旋发展。代码库越难理
解,正确的修改就越难。最后你会深陷庞⼤的、⽆法估量的泥淖之中。
⽽这种应⽤的尺⼨也会拖慢开发进度。应⽤越⼤,启动时间越长。譬如在最近的调查中,不少开发者指出启动时间长达 12 分钟。我也听说有的应⽤启动时间居然得 40 分钟。如果开发者不得不频繁重启应⽤服务器,那⼤量时间就被浪费,⽣产效率也饱受其害。
庞⼤且复杂的单体应⽤的另⼀⼤问题就是难以进⾏持续部署。现在, SaaS 应⽤的发展⽔平⾜以在单⽇内多次将修改推送到⽣产环境。然⽽要让复杂的单个应⽤达到此⽔平却极为棘⼿。想更新应⽤的单个部分,必须重新部署整个应⽤,漫长的启动时间更是雪上加霜。另外,由于不能完全预见修改的影响,你不得不提前进⾏⼤量⼈⼯测试。结果就是,持续部署变得不可能。
如果单体应⽤的不同模块在资源需求⽅⾯有冲突的话,那应⽤的扩展也很难。⽐如,模块之⼀需要执⾏ CPU-intensive 图像处理逻辑,最好部署到 AWS 的 EC2 Compute Optimized instances;⽽另⼀模块需要内存数据库,最好适配 EC2 Memory-optimized instances。由于这两个模块需要共同部署,你不得不在在硬件选择⽅⾯做妥协。
单体应⽤的另⼀问题就是可靠性。由于所有模块都运⾏在同⼀进程中,任何模块中的⼀个 bug,⽐如内存泄漏都可能弄垮整个进程;此外,由于应⽤中的所有实例都是唯⼀,这个 bug 将影响整个应⽤的可⽤性。
最后,单体应⽤会让采⽤新框架和语⾔极其困难。举例来说,你有两百万⾏使⽤ XYZ 框架的代码,如果要使⽤ ABC 框架重写代码,⽆论时间还是成本都将⾮常⾼昂,即便新框架更好。这也就成为使⽤新技术的阻碍。
总结:这个⼀开始曾经成功关键业务应⽤,最终却变成⼀个臃肿的、⽆法理解的庞然⼤物。它使⽤⽼旧、陈腐、低效的技术,⼏乎吸引不到出⾊的开发者。这个应⽤⾮常难于扩展,也不稳定可靠。最终,敏捷开发和交付⼏乎成为不可能。
你该何去何从?
微服务——直击痛点
诸如亚马逊、eBay、Netflix 等公司已经通过采⽤微服务架构范式解决了上⽂(第⼀部分)提到的问题。不同于构建单⼀、庞⼤的应⽤,微服务架构将应⽤拆分为⼀套⼩且互相关联的服务。
⼀个微服务⼀般完成某个特定的功能,⽐如订单管理、客户管理等。每个微服务都是⼀个微型应⽤,
有着⾃⼰六边形架构,包括商业逻辑和各种接⼝。有的微服务通过暴露 API 被别的微服务或者应⽤客户端所⽤;有的微服务则通过⽹页 UI 实现。在运⾏时,每个实例通常是⼀个云虚拟机或者 Docker 容器。
对于前⽂所述的系统,⼀种可能的系统分解图如下:
应⽤的每个功能区都由其⾃⾝微服务实施。此外,整个⽹页应⽤被拆分为⼀套简单的⽹页应⽤(⽐如我们的打车软件拆分为乘客应⽤和司机应⽤),从⽽能够轻松地针对特定⽤户、设备或者⽤户案例进⾏单独部署。
每个后端服务包括⼀个 REST API 和由其它服务提供的服务消耗 API。例如,司机管理服务使⽤“通知”服务器来告知司机即将的⾏程。UI 服务唤醒其它服务,从⽽呈现⽹页。这些服务也可能⽤到基于信息的异步通信。内部服务通信会在本系列⽂章中详述。
有的 REST API 也对司机和乘客的移动应⽤开放。这些应⽤并不能直接访问后端服务器,相反,通信由名为 API Gateway 的中间⼈调解。AIP Gateway 负责负载均衡、缓存、访问控制、API 计费、监控等,通过 NGINX ⾼效实施。本系列的后续⽂章将会讲解 API Gateway。
上图是 Scale Cube 的 3D 模型,来⾃《The Art of Scalability》⼀书。微服务架构范式对应 Y 轴,X 轴由负载均衡器后端运⾏的多个应⽤副本组成,Z 轴(数据分割)将需求路由到相关服务。
应⽤通常同时使⽤这三种不同类型的扩展。Y 轴扩展将应⽤分解为如图⼀(inx/wp-content/uploads/2015/05/Graph-031-e1431992337817.png)所⽰的微服务。在运⾏时维度,X 轴扩展在输出和可⽤性的负载均衡后运⾏多个实例。部分应⽤会使⽤ Z 轴扩展来对服务进⾏数据分割。下图展⽰了⾏程管理服务(Trip Management)是如何使⽤ Docker 部署到 AWS EC2 上的。
在运⾏时,⾏程管理服务包括多个服务实例,每个服务实例都是⼀个 Docker 容器。为了实现⾼可⽤性,这些容器运⾏在多个云虚拟机上。在应⽤实例前⾯是 NGINX 这样的负载均衡,将请求分发给全部实例。负载均衡也可以处理缓存、访问控制、 API 测量和监控等。
微服务架构范式对应⽤和数据库的关系影响巨⼤。每个服务都有⾃⾝的数据库计划,⽽不与其它服务共享同⼀个数据库。⼀⽅⾯,这个⽅法类似企业级数据模型。同时,它也导致部分数据的重复。然⽽,要想从微服务中获益,为每个服务提供单个的数据库计划就⾮常必要,这能保证松散耦合。下⾯的图表展⽰了⽰例应⽤的数据库架构。
每个服务都有其⾃⼰的数据库。此外,单个服务可以使⽤符合⾃⼰需要的特定类型的数据库,即多语
⾔⼀致性架构。例如,为了发现附近乘客,驾驶员管理服务必须使⽤⾼效⽀持地理位置请求的数据库。
表⾯上看,微服务架构范式与 SOA ⾮常类似,这两种架构都包括⼀套服务。然⽽,微服务架构范式被看作不包含某些功能的 SOA 。这些功能包括⽹络服务说明( WS-* )和 Enterprise Service Bus (ESB) 的商品化和请求包。基于微服务的应⽤更青睐 REST 这样简单的、轻量级
的协议,⽽不是 WS-* 。他们也极⼒避免在微服务中使⽤ ESBs 及类似功能。微服务架构范式也拒绝 SOA 的其它部分,⽐如 canonical schema 的概念。
微服务架构的好处
微服务架构模式有很多好处。⾸先,通过分解巨⼤单体应⽤为多个服务⽅法解决了复杂性问题。在功能不变的情况下,应⽤被分解为多个可管理的分⽀或服务。每个服务都有⼀个⽤ RPC- 或者消息驱动 API 定义清楚的边界。微服务架构模式给采⽤单体式编码⽅式很难实现的功能提供了模块化的解决⽅案,由此,单个服务很容易开发、理解和维护。
第⼆,这种架构使得每个服务都可以有专门开发团队来开发。开发者可以⾃由选择开发技术,提供 API 服务。当然,许多公司试图避免混乱,只提供某些技术选择。然后,这种⾃由意味着开发者不需
要被迫使⽤某项⽬开始时采⽤的过时技术,他们可以选择现在的技术。甚⾄于,因为服务都是相对简单,即使⽤现在技术重写以前代码也不是很困难的事情。
第三,微服务架构模式使得每个微服务独⽴部署,开发者不再需要协调其它服务部署对本服务的影响。这种改变可以加快部署速度,譬如 UI 团队可以采⽤ AB 测试并快速部署变化。微服务架构模式使得持续化部署成为可能。
最后,微服务架构模式使得每个服务独⽴扩展。你可以根据每个服务的规模来部署满⾜需求的实利。甚⾄于,你可以使⽤更适合于服务资源需求的硬件。⽐如,你可以在 EC2 Compute Optimized instances 上部署 CPU 敏感的服务,⽽在 EC2 memory-optimized instances 上部署内存数据库。
微服务架构的不⾜
Fred Brooks 在 30 年前写道 “there are no silver bullets”,像任何其它科技⼀样,微服务架构也有不⾜。其中⼀个跟他的名字类似,“微服务”强调了服务⼤⼩,实际上,有⼀些开发者⿎吹建⽴稍微⼤⼀些的,10-100 LOC服务组。尽管⼩服务更乐于被采⽤,但是不要忘了微服务只是结果,⽽不是最终⽬的。微服务的⽬的是有效的拆分应⽤,实现敏捷开发和部署。
分布式和微服务的关系另外⼀个不⾜之处在于,微服务应⽤是分布式系统,由此会带来固有的复杂性。开发者需要在 RPC 或
者消息传递之间选择并完成进程间通讯机制。此外,他们必须写代码来处理消息传递中速度过慢或者不可⽤等局部失效问题。当然这并不是什么难事,但相对于单体式应⽤中通过语⾔层级的⽅法或者进程调⽤,微服务下这种技术显得更复杂⼀些。
另外⼀个关于微服务的挑战来⾃于分区的数据库架构。同时更新多个业务主体的事务很普遍。这种事务对于单体式应⽤来说很容易,因为只有⼀个数据库。在微服务架构应⽤中,需要更新不同服务所使⽤的不同的数据库。使⽤分布式事务并不⼀定是好的选择,不仅仅是因为CAP 理论,还因为当前⾼扩展性的 NoSQL 数据库和消息传递中间件并不⽀持这⼀需求。最终你不得不使⽤⼀个最终⼀致性的⽅法,从⽽对开发者提出了更⾼的要求和挑战。
测试⼀个基于微服务架构的应⽤也是很复杂的任务。⽐如,对于采⽤流⾏的 Spring Boot 架构的单体式 web 应⽤,测试它的 REST API,是很容易的事情。反过来,同样的服务测试需要启动与它有关的所有服务(⾄少需要这些服务的 stubs)。再重申⼀次,不能低估了采⽤微服务架构带来的复杂性。
另外⼀个挑战在于,微服务架构模式应⽤的改变将会波及多个服务。⽐如,假设你在完成⼀个案例,需要修改服务A、B、C,⽽ A 依赖B,B 依赖 C。在单体应⽤中,你只需要改变相关模块,整合变化,部署就好了。对⽐之下,微服务架构模式就需要考虑相关改变对不同服务的影响。⽐如,你需要更新服务 C,然后是 B,最后才是 A。幸运的是,许多改变⼀般只影响⼀个服务,⽽需要协调多服务
的改变很少。部署⼀个微服务应⽤也很复杂,⼀个单体应⽤只需要在复杂均衡器后⾯部署各⾃的服务器就好了。每个应⽤实例是需要配置诸如数据库和消息中间件等基础服务。相⽐之下,⼀个微服务应⽤⼀般由⼤批服务构成。根据 Adrian Cockcroft 的分享,Hailo 由 160 个不同服务构成,⽽NetFlix 则超过 600 个服务。每个服务都有多个实例,这就形成⼤量需要配置、部署、扩展和监控的部分。除此之外,你还需要完成⼀个服务发现机制(后续⽂章中发表),以⽤来发现与它通讯服务的地址(包括服务器地址和端⼝)。传统的解决问题办法并不能解决这么复杂的问题。最终,成功部署⼀个微服务应⽤需要开发者有⾜够的控制部署⽅法,并⾼度⾃动化。
⾃动化的⽅法之⼀是使⽤譬如 Cloud Foundry 这样的 PaaS 服务。PaaS 能让开发者轻松部署和管理微服务,让他们⽆需为获取并配置 IT 资源劳神。同时,配置 PaaS 的系统和⽹络专家可以采⽤最佳实践和策略来简化这些问题。另外⼀个⾃动部署微服务应⽤的⽅法是开发⾃⼰的基础 PaaS 系统。通常的起步⽅式是 Mesos 或 Kubernetes 这样的集管理⽅案,配合 Docker 使⽤。作为⼀种基于软件的应⽤交付⽅
法,NGINX 能够⽅便地在微服务层⾯提供缓冲、权限控制、API 统计、以及监控。我们会在后续的⽂章中分析它如何解决这些问题。
总结
构建复杂的应⽤的确⾮常困难。单体式的架构更适合轻量级的简单应⽤。如果你⽤它来开发复杂应⽤,那真的会很糟糕。微服务架构模式可以⽤来构建复杂应⽤,当然,这种架构模型也有⾃⼰的缺点和挑战。
在后续的博客中,我会深⼊探索微服务架构模式,并讨论多个话题,包括服务发现、服务部署选择、以及将单体应⽤拆分为多个服务的策略。
阅读英⽂原⽂:
本⽂转⾃:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。