随笔 - 6  文章 - 0 评论 - 2 trackbacks - 0

当了10年码农,却还没有写过Web后端。最近可能要上Web项目,先从架构入手,赶紧先充充电,觉得一篇文章写得还不错,分享一下。

http://www.infoq.com/articles/microservices-intro

本文描述了当前正快速流行的微服务架构。微服务背后的主要思想是将大型的、复杂的、且需要长期演进的应用分解为一个个相互作用且不断演进的服务。顾名思义“微服务”这个词的重点在于服务必须“微”小。

对于微服务的规模,有些社区建议一个服务应该在10到100逻辑行之间,这不是关键,因为我们做微服务的目的在于想要解决一些实际问题,这些开发、部署相关的问题我们会在下面讨论到。所以有些服务可能非常小型,而有些可能有点大。

微服务并不是什么很新的理念,类似的理念如分布式系统和SOA已经出现很久,甚至微服务也可以叫做轻量级SOA。事实上你可以把微服务理解为没有ESB和WS的SOA。

使用微服务架构的主要动机是什么?他和传统的整体集成架构(monolithic)相比怎样?微服务架构有什么优缺点?在微服务架构中,如何解决服务间通信和分布式数据管理等关键技术问题?尽管微架构不是什么新颖的想法,但因为它和传统的SOA不尽相同,还是值得对其进行探讨。当然探讨的最终目的是想要解决当下很多Team面临的很多问题。

1.整体集成架构

早期的WEB企业应用大多将所有的服务端组件都放到一块。对于Java企业应用来说可能就是一个WAR或EAR文件,其他语言(如Ruby或C++)开发的应用也类似。

设想一下我们要做一个图1的网店应用,包括订单、库存、信用卡和物流等管理功能。

这个应用会包含前端和后端。前端包括用户界面和操作,后端包括商品管理、订单管理、账户管理等服务,所有后端服务会共享商品、订单、用户等业务数据

尽管我们可以将应用划分为多个功能,但整个应用在部署来讲可能就是一个运行在Tomcat上的WAR文件。

这种所谓的整体集成架构的优势在于:IDE友好,因为IDE擅长管理单个工程;方便启动测试,因为仅仅需要启动一个应用就可以开始测试了;方便部署,只要拷贝一个文件就够了。

这种架构对于相对较小的应用是没有问题的,但对于复杂应用就不能适用了。首先一个很大的应用不利于开发人员理解和维护源代码;另外对于很大的应用要出个一版本需要很多开发人员配合,测试周期也很长,这不利于快速迭代上线。

这种架构还有一个大问题是不利于尝试并引入新技术。比如你发现一个很好用的新框架,但是除了重写整个应用外你根本无法引入这个新框架,而重写整个应用是风险极高的,基本上不现实。所以总的来说这种架构不适合大型的、长期演进的系统。

2.将应用分解为多个服务

幸运的是我们有其他能更好支持伸缩的架构。“伸缩艺术”中提到了一种三维伸缩模型:图2中的伸缩立方

这个模型的X轴方向上的扩展,是运行在负载均衡器背后的多个一模一样的应用,这是提升服务容量和可靠性的好方法。

Z轴上有点像X轴,同样运行多个一模一样的应用,区别在于这些应用不是运行在负载均衡器背后,而是运行在路由背后。一种常用的路由是根据服务数据的Primary Key分区路由;另一种常用的路由是根据客户的等级分体验等级路由(为VIP用户提供更快的响应速度,更大的容量等)。

Y轴也可以叫做功能分解轴。X轴和Z轴所有应用拷贝都是一样的功能,而Y轴不是,Y轴上的每个部件的功能都不一样。对应到应用,就是将整体集成架构分解为一组功能不同的服务。每个服务负责一组相关业务,如订单管理、客户管理等。

将一个应用分解为一组服务有点像艺术创作,不容易说清楚,但有一些常用的策略。比如从动词或User Case的角度分解。还是在线商店的例子,我们马上会看到分解过后的情况,其中就有一个订单管理UI服务对应于订单操作的User Case的UI部分。再比如从名词或者资源的角度分解。按照这种角度分解出来的服务,会负责对应某个资源的处理。后面我们也会看到在线商店中的商品管理服务,负责处理所有的商品资源。

理想情况下,一个服务负责一件事情,或几件小事情。Bob Martin有一个关于一个类仅负责一件事情理论(single-responsibility principle-SRP)的PDF。SRP将一个类定义为仅处理一种变化的责任主体,这个理论同样可以运用于服务的设计。另外也可以参考Unix命令,Unix系统提供了大量的命令,比如grep、cat,和find,每一个命令能出色得完成单一功能,同时又能和其他命令组成Shell Script完成复杂的功能。所以也参照Unix命令的思想,按照单一功能思路来创建服务。

需要强调的一点是,分解应用的目的并不是为了得到精确地“小”(比如10-100逻辑行)服务,而是要发现并解决整体集成架构的问题和不足。一些服务可以很小,而另一些也可能比较大。

回到在线商店的例子,按照Y轴分解的话,我们会得到图3的架构。

经过分解后的应用变成了由一些服务组成,这些服务实现了不同的前端或后端功能。前端服务包括商品管理的UI(商品查找和浏览功能)、订单管理UI(购物车和订单提提交功能)等,相应的后端也有服务完成对应的功能。我们已经把应用的各个模块都变成了独立的服务,让我们看看这样会带来哪些改变。

3. 微服务架构的优劣

任何架构都是有优缺点的。

  • 优点:

 

    • 每个微服务相对规模比较小

 

由此,开发人员更容易理解代码;IDE工具能跑得更欢,变相得提升了开发效率;启动更加快捷,这样针对具体某个模块的测试、Debug工作的效率也得到提高。

    • 每个微服务可单独部署

当服务需要调整时,可由负责该服务的开发人员单独完成发布和部署,不需要其他人陪同参与(被动加班等);微服务架构使得持续部署成为可能(大型工程如果不拆分,又必须按明确的时间节奏提交各个阶段的版本,那么80%的可能都被20%的卡住)。

    • 每个微服务可以单独在X轴和Z轴伸缩

可以为每个服务单独进行负载均衡或硬件配置上的伸缩。这在整体集成架构下是做不到的,因为整体集成架构下的每个模块对资源有着不同的需求(如有些模块是IO密集型,有些模块是CPU密集型),而这些模块是在一个应用程序中部署的。

    • 开发过程可伸缩

团队的存在意义就在于能够产生1+1>2的合作价值。对于开发团队而言,抛开主观因素,其存在意义主要在于成员之间方便交流。而这种交流的根本驱动在于程序模块之间需要相互交流。当采用微服务架构后,模块之间的交流讨论需求更少(模块间接口其实不见得会变少,只是对于测试、Debug、部署、线上问题定位等,就不需要太多交流了),从而能带来更松散和人性化的团队架构。

    • 问题责任界定更快捷

比如一个服务的内存泄漏问题只会影响这个服务,其他没有内存泄漏问题的服务一般还是能正常工作,这样就很容易界定出哪个服务出了问题。对应的,在像整体集成架构下,一个模块的内存泄漏可能导致整个应用出问题,这常常使得服务宕机。微服务之间的界限最少应该在进程等级,除非有一些更高档的沙盒可以隔离微服务。

    • 可以更灵活的选择开发技术

理论上来讲,当需要开发或重构一个微服务的时候,你可以选择任何语言和框架,当然很多组织中这种选择是有范围的。不过重点是,开发人员不用为他觉得很愚蠢的技术决策买单了。另外因为微服务都比较小,所以如果一个技术尝试失败了,也不会太影响这个工程的进度。在整体集成架构下,往往一旦决定了采用的语言和框架,后面所有人都必须用这个才行。

这一点对于很多技术人员干活时的心情的影响,是非常大的。就像你本有机会娶得女神,你爹非要给你按一个如花,那在后面婚姻不如意时,更容易埋怨当初你爹的坑爹决策。而其实过日子总是会遇到不如意,关键在于是自己的自由选择就会心甘情愿承担责任。有自由才有责任嘛。

 

  • 缺点

    • 增加了开发复杂度

开发人员需要实现进程间通信的机制,这是分布式系统的必备要素,而微服务必然使用分布式系统;IDE等开发工具不会为多个应用交互的分布式系统做很多支持,它们一般都设计为开发单个应用。这些都是放弃整体集成架构后带来的新问题。进程是非业务层面的支撑,可以理解为一种沙盒(部署时的执行镜像隔离和运行时的内存隔离),所以理论上其他沙盒如果能完成更好的隔离,也可以代替进程隔离。

    • 增加了运维复杂度

增加了很多可独立部署的服务(Y轴伸缩),而很多服务还有多个实例(X轴和Z轴伸缩),这些都需要在生产环境下管理起来。这种复杂的管理需要高度自动化。这种自动化可以自己开发实现,也可以采用PaaS技术,比如Netflix 的Asgard及相关组件搭建一个平台,或Pivotal Cloud Foundry这样现成的提供商

    • 增加了部署复杂度

多个服务之间存在相互依赖,这种依赖只有开发团队清楚,所以需要根据服务间依赖确定服务的部署顺序图。而在整体集成构架下,所有的依赖都是应用在开发阶段搞定,无需延伸到部署环节。或者由开发人员完成部署:)

  • 架构选择

上面提到的大部分微服务架构优势,在应用开发初期都不存在,因为一开始应用总是比较简单的,不会面临那些需要微服务架构来解决的问题。并且一开始就采用微服务架构会增加了开发成本(需要很多业务无关的支撑)。所以在开发过程中是否采用或何时开始切换到微服务架构就是个问题了。

项目一开始都是希望快速实现业务需求,完成开发任务。从Y轴上去分解会使初期的迭代交付更慢。之后,当项目的主要目标慢慢从实现业务变成如何让业务做到可伸缩时,错综复杂的模块间依赖或许已经不允许你将应用分解为微服务了。

由此可见,何时采用或切换到微架构是需要慎重考虑的。不过对于那些显然需要具备很强伸缩能力的应用,比如2C的Web应用或SaaS应用,采用微服务架构一般是不会错的。大名鼎鼎的eBay(PDF),Amazon.com,Groupon,和Gilt已经都从整体集成架构切换到了微服务架构。

到目前我们已经讨论了微服务架构的优劣,下面看看微服务架构设计过程中的几个关键问题,先从通信机制说起。

 

4.微服务架构中的通信

在微服务架构中,客户端和应用之间的通信机制,以及应用内部模块之间的通信机制,都和整体集成架构中不一样。我们先看客户端和微服务之间的通信,再看应用内部之间的通信。

  • 客户端和应用之间的通信——API网关模式

在整体集成架构中,浏览器(B/S)或客户端(C/S)发起HTTP请求到负载均衡路由,然后请求被路由到运行着的N个完全相同的应用中的一个去处理。那么微服务架构下,这些HTTP请求发给谁呢?

像智能手机App这样的客户端,会发送很多分别发送RESTful HTTP请求到各个服务,如图4。

 

初看这种模式挺好的,但是显然没有考虑到不同的客户端对于API和数据需求的粒度差异。比如,Amazon.com上要显示一个页面可能会调用到100多个服务(详见)。如此庞大的网络调用,且不说移动客户端,就是桌面客户端下也显得性能低下,用户体验可想而知。

更好的方式应该是客户端在渲染一个页面时减少服务调用次数,也许是将多个调用合并为以后,然后发送给一个独立的API网关服务器。如图5。

 

API网关处在客户端和微服务之间,他既为移动客户端提供粗粒度的API,也为桌面客户端提供细粒度的API。如上图中移动客户端仅需要一个请求即可获得桌面客户端需要3个请求才能获取的信息。API网关将粗粒度的API请求分解后,通过高性能的局域网络向微服务发起请求。比如在Netflix上,一个请求对应微服务的扇出为6左右(详情)。

API网关不仅能更好地为低质量网络下的客户端服务,也对微服务提供了一层封装。这使得只要业务接口不变,实现业务的服务能很方便地灵活改变,如拆分、合并等。仅需要API网关稍微修改对粗粒度API的处理即可,而客户端完全不受影响。

API网关是于客户端和微服务之间通信的关键一环,下面我们看看微服务和微服务之间如何通信的。

  • 微服务间的通信机制

在集成架构下,应用模块之间的通信通常直接由编程语言的调用实现。但在微服务架构下,每个微服务都运行在独立的进程,必须借助进程间通信(IPC)来交互。

    • HTTP同步调用

REST和SOAP都是基于HTTP的同步调用,这是两种友好的网络技术,同时容易实现请求-响应设计模式。HTTP技术的一个局限是它不支持像发布-订阅这样的设计模式。

另一个问题是HTTP访问需要知道服务器地址和端口号,在一些大系统这个问题会凸显出来。比如云平台上部署的服务,这些服务的运行环境可动态伸缩,而且服务本身也是有需要时才实例化,不需要时销毁,这种情况下,想要人为管理各个服务的HTTP地址也是一个复杂的事情。所以微服务架构的应用内部需要一个服务发现机制,比如建立一个服务注册机制(如Apache ZooKeeperNetflix Eureka)。一些应用里甚至规定微服务注册时必须内置负载均衡器(进而又负载均衡器接管HTTP路由?),例如在Amazon VPC中的ELB

这里的同步不是编程语言层面的同步,而是指HTTP请求的响应有一点的时限要求。

    • 异步消息

比如基于AMQP的Message Broker就是一种异步消息机制。这个机制里,将消息生产者和消费者分离开来,Message Broker会缓存消费者还没有处理的消息,生产者仅需和Message Broker交互而完全不用关心消费者,由此也不再需要服务发现了。同时HTTP不能实现的像发布-订阅这种双向通信模式,该机制也能实现。不过请求-响应这种模式在消息机制下就不那么自然了(所有消息都是对等的,不像HTTP里那样有天然区分

缺点这种机制需要一个Message Broker,这无疑使得微服务下复杂的部署更加复杂。

两者各有特点,可以两者兼用。

5.去中心化数据的管理

为了让微服务之间解耦,数据库的分解是理所当然的,每个微服务应该有自己专有的数据库(至少不能共享表结构)。甚至理论上来讲每个微服务可以根据自己的需要选用不同的数据库实现——这种设计被称为多样持久化(polyglot-persistence)。因此,应用被分解为一个个微服务,对应的数据库也跟着被分解了。

比如,一个微服务如果需要ACID交互,那么可以用关系型数据库;而另外一个微服务如果需要管理社交网络的话,反而会选择图数据库。

暂且先不讨论数据库分解如何重要,数据库分解带来了一个新的问题:如何处理那些需要多个服务和多个数据库的请求。我们先来看对数据的读请求,再看写。

  • 处理读请求

在在线应用商店的例子中,假设每个用户有一个信用额度(所有未付款订单交易金额总和不能超过信用额度)。那么当用户提交一个订单时,系统必须检查所有未付款订单的总额是否超过了信用额度。在微服务架构下,这个用例中涉及到用户服务和订单服务两个微服务,其中订单服务需要向用户服务查询信用额度信息。

一种方法是订单服务每次需要信用额度信息时向用户服务查询,这种实现最简单。缺点是订单服务的可用性依赖于了用户服务,另外每次都做跨进成查询比较耗时。

另一种方法是订单服务中缓存一个信用额度信息。这样订单服务对用户服务的依赖就弱很多,而且也不会产生额外的查询耗时。缺点是必须引入另外的机制保证这个缓存的信用额度能得到及时更新。

  • 处理写请求

类似像保证订单服务和用户服务中的信用额度一致性的问题,在涉及到多服务写请求处理的时候,是很普遍的。

    • 分布式事务

一个方案是利用数据库的分布式事务,在用户服务处理信用额度更新时,除了更新自己维护的数据外,提交一个分布式事务到订单服务的数据库。这种机制能保证两边的信用额度始终相同。缺点是降低了系统可用性,因为必须处理分布式事务的服务或数据库都可用,这个更新才能完成,而且分布式事务不支持自恢复,也不被新很多技术框架支持(REST,NoSQL等)。

    • 事件驱动更新

另一种方法是异步事件驱动回复。也就是如果一个服务关心一个数据更新,那么就对这个数据更新注册一个监听,当数据发生更新时,由负责更新的服务发出一个更新事件,之前注册的监听者就都能收到这个更新通知了。在这里,用户服务更新了信用额度后,发布一个CustomerCreditLimitUpdatedEvent,里面有用户ID和新的信用额度两个参数。而订单服务注册过这个事件所以可以收到新的信用额度。这个流程图6。

 

这个方法主要的优点是将更新数据的生产者和接受数据更新的消费者解耦,这样两个微服务相互不依赖,可独立运行。即使订单服务在信用额度更新时没有运行,也可以在运行后收到最新的信用额度。其实这是用数据一致性换取了服务可用性(事件发送和处理之间有时间差),这就导致系统必须设计为容忍一定的数据不一致,对应到程序开发时就需要增加另外的机制来检查并纠正这种数据不一致。尽管如此,这种方法是现在很多程序采用的。

 

6.摘要

整体集成架构在企业应用中很常见。这种架构下的小应用,不论是开发、测试还是部署,都能较好完成。但是对于复杂的大型程序,整体集成架构则成为了开发和部署的绊脚石。继续发布基本已经不可能了,开发也被牢牢限制在之前选择的技术框架中。因此对于大型应用,采用微服务架构将其分解为一组服务值得一试。

微服务架构优点多多。一个微服务的源代码很容易被理解,开发部署也不需要对其他模块有什么依赖。另外在一个微服务中应用新技术框架更简单容易。

微服务架构缺点也不是没有。东西一分解以后,一个应用会变成很多零碎,你或许需要一个像PaaS那样高度自动化的平台来管理这些零碎。在开发阶段,你还要考虑如何处理数据碎片化。总的来说,对于需要快速迭代的大型程序,特别是SaaS风格的应用来讲,值得一试。

 

posted on 2014-10-10 18:26 jan4984 阅读(...) 评论(...) 编辑 收藏