微服务
微服务现在受到了大量的关注︰ 文章、 博客、 社交媒体和学术会议上的讨论都能看到该词汇的身影。微服务正迅速走向 Gartner Hype cycle 所指的快速发展期。同时,软件社区的一些怀疑论者指出微服务并不是什么新鲜玩意儿。这些唱反调的人说微服务和SOA概念并没有什么不同,旧瓶装新酒而已,顺势炒炒新概念。然而,不管说是夸大也好,怀疑也罢,微服务架构模式应用在敏捷开发和交付复杂的企业应用程序的时候还是有巨大优势的 。
本博客是设计、构建和部署微服务七篇博客系列的开篇。通过该文章将学到一些微服务的方法,还有微服务架构与传统单体式架构模式的比较。本系列将介绍微服务架构的各种元素,并揭示该架构模式的各种优点与缺点,以此来指导微服务是否适合您的项目,以及如何运用该模式。
让我们首先看看为什么你应该考虑使用微服务。
构建单体式应用
假设现在我们为了与Uber和Hailo竞争来构建一个全新在线打车软件:经过一系列的预备会议和需求收集,我们决定无论是人工撸还是用Rails,Spring Boot,Play,或Maven之类工具生成也好,最终要创建一个全新的应用!应用应该有如下图六边形一样的模块结构
![](http://upload-images.jianshu.io/upload_images/3912920-d319c7769ff1e518.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/767/format/webp)
应用程序的核心是业务逻辑,它了定义服务、 领域对象和事件模块。围绕核心是接口与外部世界的适配器。适配器的例子包括数据库访问组件、 生产消息和消费消息的消息传递组件以及暴露API或实现用户界面的 web 组件。
尽管在逻辑上模块化,整个应用还是做为一个巨大的整体进行打包和部署的。而且模块的结构往往与选择的编程语言和框架紧密相关。 举个栗子: 多数java程序打成war包部署到Tomcat 、Jetty等应用服务器中;还有一部分被打成自包含的jar包(Spring Boot支持把jetty或者tomcat包进去,以main为入口启动应用); 同样的,Rails 和 Node.js 应用直接以目录结构的方式部署.
这种风格开发的应用是非常常见的。我们的IDE和工具致力于构建这样的单一应用,所以我们开发起来也感觉到简单;此类应用测试起来也很方便,你可以很简单的启动这个应用,采用selenium测试UI去完成端到端的测试;单体应用部署起来也不难,把包一拷贝扔服务器里就妥了, 如果要扩展应用,只需要在负载均衡器后面跑多个相同的应用就可以了。工程的初期呢,这个方法工作的还是不错的!
迈向单体的地狱
很不幸的是,这种方式还是有巨大的局限性的.。一个成功的应用总会随着时间逐步成长并变得巨大起来。每个敏捷Sprint周期,开发者会实现更多的功能,当然这就意味着又添加了很多行代码。几年之后你会发现,当年你写的那个简单的小小的应用居然变成一个巨大的单体怪物。举一个比较极端的例子吧:最近我和一个开发者聊天,他正在写一个工具来分析数百万行代码的巨大应用中的上千个jar的依赖关系。别的不说,写出这么巨大的怪物肯定花费了很多程序员几年的时间。
一旦应用变成了巨大复杂的单体,那你的开发团队将痛苦不堪!敏捷开发和交付的一些愿景在这个庞然大物面前都会受挫。最主要的问题就是这玩意儿现在太复杂了!复杂到很难有某个程序员能完全理解它!导致结果就是,正确的修个bug、做个新功能变的费时又费力。更可恨的是,这是一个恶性循环的过程,应用正在逐步的‘死去’ 。如果你的代码库别人都理解不了,那么对代码做点正确的改动是很难的,最终这个怪物更加的可怕更加的难以理解,纠结!
应用程序的规模大了也会拖慢程序的开发: 程序越大,启动越慢!调查显示一些程序员说他们启动程序要12分钟,我个人还听过有的应用启动要40分钟!程序员开发过程需要周期性的重启应用,这样就浪费了很多时间,效率自然也很低下,不能忍!
巨大、复杂的单体应用还是持续交付的巨大障碍。现在SaaS应用的宗旨是如果有改动能够每天部署多次。单体应用的中某部分的改动需要需要重新部署整个应用,这样的话持续交付是相当困难的。前面也讲了,启动一次就要那么久 !另外,改动造成的影响也不是很好被理解,需要大量的手工测试去保证,这样的话持续交付更是难上加难了!
单体应用在多个模块对资源需求上有冲突时很难进行扩展。比如:一个新模块可能实现了CPU密集型的图片处理逻辑,更适合部署到Amazon EC2 Compute Optimized instances。另一个模块可能需要一个内存数据库,更适合使用EC2 Memory-optimized instances。然而,这些模块必须被一起部署的话,选择硬件的时候就要好好折中一下喽。
单体应用的另一个问题是稳定性:因为所有的模块都跑在同一进程之中,某模块中的bug,比如内存泄漏是可能拖垮整个应用的!更重要的是,因为负载均衡后面的所有的应用是一样的,bug还可能影响整个应用的可用性!
最后重要的一点:单体应用很难拥抱新的框架和编程语言。假设你有2百万行用XYZ框架写的代码,如果用ABC框架重写它将会耗费巨大的时间和金钱!哪怕大家都知道用新框架更适合一些(摊手)。这样导致的结果是,在尝试转换新框架新技术的时候存在巨大的障碍,我们不得不继续在选型之初定好的技术架构上前行(哭脸)。
最后总结下吧,你从拥有一个业务清晰,几个程序员都明白的小程序,逐步的变成了一个可怕的单体应用。回首看这个应用是采用了过时的、效率低下的技术来写的(毕竟技术在进步,抱着老东西闷头陶醉不是什么好事情)!别的不说,招聘都费劲呐!这个单体应用变的难以扩展、变得极不稳定,想敏捷开发?想持续交付? 恐怕只能呵呵了。
面对这些,你能做点什么呢?
微服务 – 处理这些复杂问题!
很多机构,比如Amazon、eBay、Netflix,已经开始通过拥抱微服务架构去解决上述的问题了。她们不再去构建一个可怕的单体应用,而是拆分成多个更小的、相互连接的微服务!
服务通常实现了一系列相互独立的功能或特性,比如订单管理、客户管理等等。每一个微服务都具有自身业务逻辑以及各种适配器构成的六角形架构模式,都是一个迷你的小应用。有的微服务会暴露API供其他微服务和客户消费,其他微服务则可能实现Web UI。运行时,每一个实例通常是云平台的一个VM或者一个Docker容器。
下面是对上述老架构的一个拆分:
![](http://upload-images.jianshu.io/upload_images/3912920-9762019e56935c8a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/815/format/webp)
应用的每个功能区域现在都以微服务的方式来实现了,此外整个应用被拆分成一系列更小的web应用(比如乘客管理、司机管理)。拆分之后更加方便的为特殊用户、设备或者特殊用例进行部署。
每一个后端服务都暴露REST API,绝大部分服务也会消费其他服务提供的API。比如,司机管理服务会使用通知服务告知空闲的司机潜在的行程(来生意了,接单!);还有UI 服务会调用其他服务后渲染web页面。服务之间也会使用异步的,基于消息的通信模式,微服务进程间的通信将会在后面的文章详述。
一些REST API 也会暴露给移动端设备方便司机和乘客的使用。当然,这些应用不会直接访问后端各个微服务 ,而是通过一个协调者API网关来访问的。 API网关的职责有负载均衡、缓存、访问控制、API计费、监控···,这些事情使用NGINX就OK啦。后面的文章将会讲述API网关的细节。
![](http://upload-images.jianshu.io/upload_images/3912920-edd018002c577cff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/713/format/webp)
微服务架构模式很契合上图的Y轴扩展方式,上图X轴的扩展表示横向扩展应用,一般就是负载均衡器后面部署几个相同的应用。Z轴扩展表示数据分区,将请求路由到特定的服务器上(比如数据库分库分表,根据主键访问到用户记录对应的数据库)。
应用一般都会三个维度去扩展。Y轴正是按照我们第一节讲到的,把单体应用拆分为微服务,运行时呢,为了某个服务达到高吞吐和高可用性,可以采用负载均衡,也就是X轴扩展了,特殊应用也会使用Z轴扩展来切分服务。下面的图展示了行程管理采用Docker镜像部署到Amazon EC2的情况:
![](http://upload-images.jianshu.io/upload_images/3912920-2d3f5e1b1c954c35.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
运行时,行程管理由多个服务实例组成。每个应用实例实际是一个Docker容器。为了达到高可用,容器会跑在多个云平台VM上,服务实例之前是NGINX这样的负载均衡器来分发请求,负载均衡器也会关注比如缓存、权限访问、服务计费、监控等事宜。
微服务架构模式极大的影响应用和数据库之间的关系。每个微服务都会有自己的数据库Schema,而不是和其他服务共享一套数据库Shcema。另一个角度看,这种方式对于以往企业级范围的数据模型来讲是很奇怪的,另外,微服务这么搞会造成数据冗余。然而,如果你想从微服务架构获益,那么每个微服务一个数据Schema是很有必要的,因为微服务提倡的就是松耦合啊,下面的图展示了微服务架构下应用的数据架构:
![](http://upload-images.jianshu.io/upload_images/3912920-609da44e77622fea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/852/format/webp)
每个微服务都有自己的数据库,而且每个服务还能选择适合自己需求的数据库,这就是所谓的多态型持久化架构。比如,司机如果需要查找附近的潜在乘客,那就必须要使用支持高效地理位置查询的数据库。
表面上看微服务和SOA架构很相似,两种架构都倡导由一系列的服务组成。我们可以把微服务架构模式当成是没有商业web service规范(WS规范咯)以及ESB等打包套件约束的SOA。基于微服务架构的应用更倾向于简单的、轻量级的REST协议而不是老旧的WS,当然,微服务也会避免去使用笨重的ESB套件而更喜欢去实现一些ESB部分功能的轻量级工具(不要捆绑,不要全家桶!),微服务架构模式也避免SOA中诸如规范化Schema的定义。
微服务的优势
微服务架构模式有很多重要的优势。首先,通过把可怕巨大的单体拆分为一系列的服务解决了单体复杂度问题, 拆分后整体功能数量并没有变化,但是应用现在变成了多个方便管理的小应用,每个服务都有一个定义良好的服务边界,通过RPC方式或消息驱动暴露API。微服务架构模式解决了单体应用代码库在实践中极难模块化的难题,拆分后的微服务能够更快的部署,更易理解和维护!
第二,拆分后的服务可以被专注于某部分服务的团队独立开发。程序员可以基于API约定前提下自由选择合适的技术,当然,很多机构为了避免技术选择混乱也会限制技术的选项。因此,自由意味着程序员可以不再局限于项目初期选择的技术了(撒花)!开始写一个新的微服务的时候,开发者可以使用一些当下流行的技术,更重要的是,因为每个服务现在拆分的很小,使用现有技术重写老的服务成为可能!
第三,微服务架构模式使得独立部署成为可能,开发者不用再忙碌于协调其他模块的变化再去部署(单体应用,改一个部分可能对其他部分影响,某个更改可能涉及多个模块的协调),微服务的更改可以在测试后尽快的被部署。比如,UI组可以进行A/B测试并且快速迭代而不用等待整个应用部署。微服务架构模式使得持续部署成为可能!
最后,微服务架构模式使得每个服务可以独立的扩展。我们可以针对某些有容量和可用性要求的微服务进行扩展,为需要的服务部署多个实例而不是复制多个单体去折中获得某项提升!更重要的是,我们可以针对服务需求使用最合适的硬件资源。比如,我们可以把CPU密集型的图片处理服务部署到EC2 Compute Optimized instances,部署内存数据库相关的服务到EC2 Memory-optimized instances。
微服务的劣势
正如Fred 30年前的书中所说,“没有银弹!”。和其他技术一样,微服务架构也有其劣势 。劣势之一就是它的名字(捂脸),微服务这个词过分强调了服务的尺寸,实际上还真有几个开发者号召大家写10-100行代码的‘微服务’(这应该不太可能吧)!然而微服务更想表达的是一种工具和途径,微并不是最终的目标(为微服务而微服务,敲响警钟),微服务是为了便利敏捷开发和部署而去有效的拆分应用。
微服务另一个劣势是因为从单体应用拆分为分布式系统带来的复杂。开发者需要选择或者实现基于消息或者RPC模式的进程间通讯机制,另外开发者也要写额外的代码去处理对于目的服务请求可能存在的请求缓慢或者请求不可用导致的局部故障问题。分布式系统可不是什么火箭科学,它可比单体应用中模块间通过语言层面或者进程内调用复杂的多了!
微服务另一个挑战就是拆分数据库架构。一个逻辑事务更新多个业务记录是很常见的事情,单体应用实现该事务是比较简单的,毕竟大家使用一个数据库。然而在微服务架构中,你必须要更新多个服务的多个数据库。一般不会使用分布式事务,不仅仅是因为CAP理论,还因为一些流行的NoSQL数据库和Message Queue系统压根也不支持(摊手)。最后还得绕回最终一致性方案,这个方案对开发者来讲也是略有挑战的(实际上我看这个系列的文章并且开始翻译也是因为实际中遇到了这个问题,下面的文章将会提到)。
测试微服务架构的应用也是更加复杂的。比如,使用Spring Boot这种框架,开发者启动一个单体应用去写一些测试用例、测试其REST API是很平常事情。相反的是,相类似的测试在微服务中的话,你要启动或者至少去mock其依赖的其他的服务才能完成。再次强调,微服务不是火箭科学,很重要的一点是大家不要低估了这样做的复杂度!
微服务架构模式的另一巨大挑战是实现跨服务的改动。比如,让我们假设你要实现一个需要更改服务A、B、C的功能,而此时A依赖于B,B依赖于C。在传统单体应用中,你可以很简单的去更改对应的模块,集成这些变动然后一起部署它们。然而在微服务架构模式下,你需要小心翼翼的计划和协调每个服务的改动和发布:你需要更新服务C,再更新服务B,然后还要更新服务A。幸运的是,绝大部分时候一个服务的变化仅会影响一个服务,多个服务间需要协调的情况是很少出现的。
部署一个基于微服务架构的应用是更加复杂的。一个传统单体应用可以简单的部署到负载均衡器后面,因为应用都是相同的,拷贝部署就可以了。每个应用配置好数据库和message broker的地址(主机和端口)就好了。相对应的微服务一般是大量的服务组成的,比如,Hailo存在160个不同的服务,Netflix则有多达600多个不同服务,每个服务还有多个运行实例!将会有更多的变化的部分需要去配置、部署、扩展、监控。此外还需要实现一个服务发现机制让其他服务找到它需要通信的服务的地址。传统的基于Ticket和手动运维的方式是不可能处理好这个级别的复杂扩展的。因此,成功部署一个微服务需要开发者对部署方法有更多控制还要对服务执行高水平的自动化。
自动化的其中一种方式是使用现有的Paas平台(比如Cloud Foundry)。PaaS平台为开发者提供了便捷的方式去部署他们的微服务,避免了是他们纠缠于购买和配置IT资源的泥潭。同时,配置PaaS系统和网络的专业人员可以确保遵守了最佳实践和公司策略。另一种自动化方式就是开发自己的PaaS平台。一个典型的开端就是先使用集群方案(比如Kubernetes)配合上Docker容器技术,后续的文章将会介绍基于软件的应用交付方案,比如NGINX是如何在微服务层面方便的处理缓存、访问控制、API计费、监控等问题的。
总结
构建复杂的应用本身就是困难的事情。单体应用架构在针对简单、轻量级的应用的时候是很好的。但是一旦你把那套方案运用在复杂的应用的时候你将会痛苦不堪。尽管微服务架构模式有诸多缺点和实现上的挑战,但对于复杂的、演进的应用来讲是一个更好的选择。
在后续的文章中我将会详细介绍微服务的几个方面,讨论一些诸如服务发现、服务部署选择以及重构单体应用到微服务的的话题。
后记
第一篇文章翻译完了,说实话胆颤心惊,第一次作大死,厚着脸皮上吧!!!
作者:nonumber1989
链接:https://www.jianshu.com/p/8d2cfa1fa633
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。