微服务架构(Microservices)

  • 内容
  • 评论
  • 相关

说在前面

    好久没写博文了,心里痒痒(也许是换工作后,有点时间了吧)。最近好像谈论微服务的人比较多,也开始学习一下,但是都有E文,看起来半懂不懂的。
    Martinfowler的《微服务》,也算是入门必读了。有人翻译过,但是只有一半。还是自己练练手吧。

微服务

    “微服务架构”一词在过去几年里广泛的传播,它用于描述一种独立部署的软件应用设计方式。这种架构方式并没有非常准确的定义,但是在业务能力、自动部署、端对端的整合、对语言及数据的分散控制上,却有着显著特征。
    “微服务”----只不过在满大街充斥的软件架构中的一新名词而已。尽管我们非常鄙视这样的东西,但是这玩意所描述的软件风格,越来越引起我们的注意。在过去几年里,我们发现越来越多的项目开始使用这种风格,以至于我们身边的同事在构建企业应用时,把它理所当然的认为这是一种默认开发形式。然而,很不幸,微服务风格是什么,应该怎么开发,关于这样的理论描述却很难找到。
    简而言之,微服务架构风格,就像是把小的服务开发成单一应用的形式,每个应用运行在单一的进程中,并使用如HTTP这样子的轻量级的API。这些服务满足某需求,并使用自动化部署工具进行独立发布。这些服务可以使用不同的开发语言以及不同数据存储技术,并保持最低限制的集中式管理。
    开始介微服务风格前,先介绍整体风格:即把一个完整的应用当成一开发单元。企业应用通常包含三个部分:客户端界面(由HTML、Javascript组成,使用浏览器进行访问)、数据库(由许多的表组件构成一个通用的、相互关联的数据管理系统)、服务端应用。服务端应用处理HTTP请求、执行领域逻辑、检索并更新数据库中的数据、使用适当的HTML视图发送给客户端。服务端应用是完整的 ---- 由单一的逻辑层次执行。系统中任务变更都会导到服务端的应用重新编辑并发布一个新的版本。
    这样的整体服务是这样的构建系统的很自然的方式。虽然利用开发语基础特性会把应用封装成类、函数、命名空间,但是业务中所有逻辑都要在单一的进程中处理完成。在某些场景中,开发者可能在的笔计本中开发、测试应用,然后利用部署通道来保证经过正常测试、发布的修改内容正确的发布的产品中。也可以使用横向扩展,通过负载均横系统将事个应用部署到多台服务器上。
    整体风格的应用也是相当成功的,但是越来越多的人感觉到有点不妥,特别是在云中进行应用的发布时。变更发布周期被绑定了 ---- 原来可以划分成小的应用、小的需要的变更,需要统一的进行编译和发布。随着时间的推移,人们通常难于维护一种优美的模块化的结构,使得一个模块的变更很难不会影响到其它的模块。进行扩展,也需要进行整体的扩展,而不能根据进行部分的扩展。
整体风格与微服务风格
图1:整理架构与微服务架构
   这些原因导致了微服务架构风格的出现:以服务构建应用。因为服务可以独立部署、独立扩展,服务也可以提供模块化的边界,并且不同的使用也可以使用不同的开发语言。服还可以以不同的周期进行管理。
    微服务风格关不是我们发明的,也不是一个新的东西,它至少起源于Unix时代的设计原则。之所以这样,我们认为只是当时很少人考虑过这种风格,并认识到把软件使用这种的风格可以带来更多的好处。

微服务风格的特性

    我们没有办法对微服务风格进行准确的定义,但是我们可以偿试着描述一下微服务风格所应该具有的觉特性,这样就可以对它打上相应的标签了。正如其它定义中对特性的描述一新,并不是所有的微服务风格都要所有的特性,但是我们认为常见的微服务都应该有这些特性。尽管我们是相当松散的社区核心成员,但是我们也计划偿试描述我们工作中或者在其它我们了解的组件中所理解的微服务。当然,我们并不依赖于那些已经明确过的定义。

组件与服务

    自从我们从事软件行业以来,一直希望能够构建由组件组成的系统,就像我们所看到的实现世界由物件构成的一样。在过去的几十年里,我们已经看到了大部分语言平台的公共库的进行了精简,并取得可观的进展。
    当我们谈论组件的时候,有可能会因为组件的不同定义引起混乱。因此我们申明,这里谈到的组件是指软件中独立的单元,它能独立替代和独立更新。
    微服务架构也使用组件库,但是它把软件拆分成服务,并认为这是主要的组织形式。我们把组件库定义为程序中相互关系、并使用内存中调用的组件,把服务定义为进程间使用如Web请求服务或者远程调用来相互通信的组件。(这种定义的方式与其它面向对象程序中服务对象的概念是不一样的。)
    把服务当成组件(而不是组件库)一个主要的原因是服务可以独立的部署。如果你的应用由多个组件库组成并跑在一个进程中,那么任何组件的变更都将导致整体应用的重新发布。但是如果由许多服务构成的应用,你可以想像的到每个服务的变更仅需要发布相应的服务。当然,这也不是绝对的,比如导致服务接口的变更的更新就需要相应服务的变化,但优秀微服务构架,会尽量避免这种服务间的耦合并完善服务的交互接口。
    把服务当成组件的另一个考虑是这将拥有更新清晰的接口。许多开发语言并没有良好的定义公共接口的机制。通常只有文档和规范说明,让用户避免组件间会导致组件耦合的过度的依赖。不过服务由是是通过明确的远程接口调用,这个问题就很容易解决了。
    使用服务也有它的不足之处。远程调用比进制内部调用更消耗性能,而且远程的API比较粗糙,难以使用。如果由于对组件的职责进行变更,影响到跨进程间的交互,那么这操作起来也比较困难。
    第一个可能的特性,我们看到每个服务是运行在独立的进程上的。注意,这只是第一个可能的特性。服务也可以由多个进程组成,它们是同时开发和部署的,例如服务可能用到一个应用进制和一种数据禀。

围绕业务功能进行组织

    当寻找把一个大的应用拆分成小的部分时,主管通常注意在技术层面,拆分成UI组、服务逻辑组和数据库组。当使用这种标准对团队进行划分时,甚至小小的更变都将导致跨团队间项目协作,从而消耗时间和预算审批。一个高效的团队会针对这种情况进行改善,关注它们所涉及的应用逻辑,并从中做出较好的选择。换句话说,逻辑无处不在。Conway's Law的实践就是一个例子。
[plain] view plain copy

  1. 任何组织设计一个系统(广义上的系统)都会产生一种设计,其结构为其组织通信结构的复本。
  2. -- Melvyn Conway, 1967
    
图2:Conway's Law的实践
    微服务更倾向于围绕业务功能对服务结构进行划分、拆解。这样的服务,是针对业务领域有着关完整实现的软件,它包含使用接口、持久存储、以及对旬的交互。因此团队应该是跨职能的,包含完整的开发技术:用户体验、数据库、以及项目管理。
图3:通过团队边界强调服务边界
    www.comparethemarket.com就采用这种组织形式。不同职能的团队同时为各自的产品构建和运营负责,同时每个产品又拆分成多个通过消息引擎通信的单独服务。
    大型的整体型应用也可以按照业务功能进行模块化的,尽管这种例子不常见。当然,我们敦促一个大型的团队将一个构建成整体型的应用依照业务功能进行拆分。我们能看到主要问题在于,这种组件形式会导致很多的上下文依赖。如果在大量的模块边界上都存在这种大量的调用,对于团队的每个成员来说,短期内是很难记住的。此外,我们发现模块化方式需要大量的规范去强制执行,当然,大量明确的拆分也让服务组件在团队的边界中更加清晰。

产品不是项目

    大部分的软件开发者都使用这样的开发模式:至力于提供一些被认为是完整的软件。一旦开发完成,软件将移交给维护部门,然后,开发组就可以解散掉了。
    微服务的支持者认为,这种做法是不可取的,并提议开发组应该负责产品的整个生命周期。一个常见的证明是:Amazon的“你编译,你运维(you build, you run it)”的理念,它要求开发团队对软件产品的整个生命周期负责。这要求开发者每天都关注软件产品的运行情况,并与用户联系的更紧密,同时承担一些售后支持。
    成熟的产品会与业务功能进行绑定。除了把软件看成既定功能的集合外,会进一步关心“软件如何帮助用户实现业务功能”这样的问题。
    采用整体型的架构并不是没有原因的,但是越小的服务粒度越容易促进用户与服务提供商之前的关系。

强化终端及弱化通道

    当构建不同的进程间通信机制的时候,我们发现有许多的产品和方法能够把更加有效方法强加入的通信机制中。比如企业服务总线(ESB),这样的产品提供更有效的方式改进通信过程中的路由、编码、传输、以及业务处理规则。
    微服务倾向于做如下的选择:强化终端及弱化通道。微服务的应用致力松耦合和高内聚:采用单独的业务逻辑,表现的更像经典Unix意义上的过滤器一样,接受请求、处理业务逻辑、返回响应。它们更喜欢简单的REST风格,而不是复杂的协议,如WS或者BPEL或者集中式框架。
   有两种协议最经常被使用到:包含资源API的HTTP的请求-响应和轻量级消息通信协议。最为重要的建议为:

[plain] view plain copy

  1. Be of the web, not behind the web(善于利用网络,而不是限制使用网络。)
  2. ---- Ian Robinson

微服务团队采用这样的原则和规范:基于互联网(广义上,包含Unix系统)构建系统。这样经常使用的资源几乎不用什么的代价就可以被开发者或者运行商缓存。

    第二种做法是通过轻量级消息总线来发布消息。这种的通信协议非常的单一(单一到只负责消息路由),像RabbitMQ或者ZeroMQ这样的简单的实现甚至像可靠的异步机制都没提供,以至于需要依赖产生或者消费消息的终端或者服务来处理这类问题。
    在整体工风格中,组件在进程内执行,进程间的消息通信通常通过调用方法或者回调函数。从整体式风格到微服务框架最大的问题在于通信方式的变更。从内存内部原始的调用变成远程调用,产生的大量的不可靠通信。因此,你需要把粗粒度的方法成更加细粒度的通信。

分散治理

    集中治理的一种好处是在单一平台上进行标准化。经验表明这种趋势的好处在缩小,因为并不是所有的问题都相同,而且解决方案并不是万能的。我们更加倾向于采用适当的工具解决适当的问题,整体式的应用在一定程度上比多语言环境更有优势,但也适合所有的情况。
    把整体式框架中的组件,拆分成不同的服务,我们在构建它们时有更多的选择。你想用Node.js去开发报表页面吗?做吧。用C++来构建时时性要求高的组件?很好。你想以在不同类型的数据库中切换,来提高组件的读取性能?我们现在有技术手段来实现它了。
    当然,你是可以做更多的选择,但也不意味的你就可以这样做,因为你的系统使用这种方式进行侵害意味着你已经有的决定。
    采用微服务的团队更喜欢不同的标准。他们不会把这些标准写在纸上,而是喜欢这样的思想:开发有用的工具来解决开发者遇到的相似的问题。这些工具通常从实现中成长起来,并进行的广泛范围内分享,当然,它们有时,并不一定,会采用开源模式。现在开源的做法也变得越来越普遍,git或者github成为了它们事实上的版本控制系统。
    Netfix就是这样的一个组织,它是非常好的一个例子。分享有用的、尤其是经过实践的代码库激励着其它的开发着也使用相似的方式来解决相似的问题,当然,也保留着根据需要使用不同的方法的权力。共享库更关注于数据存储、进程内通信以及我们接下来做讨论到的自动化等这些问题上。
    微服务社区中,开销问题特别引人注意。这并不是说,社区不认为服务交互的价值。相反,正是因为发现到它的价值。这使得他们在寻找各种方法来解决它们。如Tolearant Reader和Consumer-Driven Contracts这样的设计模式就经常被微服务使用。这些模式解决了独立服务在交互过程中的消耗问题。使用Consumer-Driven Contracts增加了你的信心,并实现了快速的反馈机制。事实上,我们知道澳大利亚的一个团队致力使用Consumer-Drvien Contracts开发新的服务。他们使用简单的工程,帮助他们定义服务的接口。使得在新服务的代码开始编写之前,这些接口就成为自动化构建的一个部分。构建出来的服务,只需要指出这些接口适用的范围,一个优雅的方法避免了新软件中的'YAGNI '困境。这些技术和工具在使用过程中完善,通过减少服务间的耦合,限制了集中式管理的需求。
    也许分散治理普及于亚马逊“编译它,运维它”的理念。团队为他们开发的软件负全部责任,也包含7*24小时的运行。全责任的方式并不常见,但是我们确实发现越来越多的公司在他们的团队中所推广。Netfix是另外一个接受这种理念的组件。每天凌晨3点被闹钟吵醒,因为你非常的关注写的代码质量。这在传统的集中式治理中这是一样多么不思议的事情呀。

分散数据管理

    对数据的分散管理有多种不同的表现形式。最为抽象层次,它意味着不同系统中的通用概念是不同的。这带来的觉问题是大型的跨系统整合时,用户使用不同的售后支持将得到不同的促销信息。这种情况叫做并没有给用户显示所有的促销手段。不同的语法确实存在相同的词义或者(更差)相同的词义。
    应用之间这个问题很普遍,但应用内部这个问题也存在,特别是当应用拆分成不同的组件时。对待这个问题非常有用的方式为Bounded Context的领域驱动设计。DDD把复杂的领域拆分成不同上下文边界以及它们之间的关系。这样的过程对于整体架构和微服务框架都很有用,但是服务间存在着明显的关系,帮助我们对上下文边界进行区分,同时也像我们在业务功能中谈到的,强行拆分。
    当对概念模式下决心进行分散管理时,微服务也决定着分散数据管理。当整体式的应用使用单一逻辑数据库对数据持久化时,企业通常选择在应用的范围内使用一个数据库,这些决定也受厂商的商业权限模式驱动。微服务让每个服务管理自己的数据库:无论是相同数据库的不同实例,或者是不同的数据库系统。这种方法叫Polyglot Persistence。你可以把这种方法用在整体架构中,但是它更常见于微服务架构中。
Polyglot Persistence
图4:Polyglot Persistence
   微服务音分散数据现任意味着管理数据更新。处理数据更新的常用方法是使用事务来保证不同的资源修改数据库的一致性。这种方法通常在整体架构中使用。
   使用事务是因为它能够帮助处理一至性问题,但对时间的消耗是严重的,这给跨服务操作带来难题。分布式事务非常难以实施,因此微服务架构强调服务间事务的协调,并清楚的认识一致性只能是最终一致性以及通过补偿运算处理问题。
    选择处理不一致问题对于开发团队来说是新的挑战,但是也是一个常见的业务实践模式。通常业务上允许一定的不一致以满足快速响应的需求,但同时也采用一些恢复的进程来处理这种错误。当业务上处理强一致性消耗比处理错误的消耗少时,这种付出是值的的。

基础设施自动化

    基础设施自动化技术在过去几年中得到了长足的发展:云计算,特别是AWS的发展,减少了构建、发布、运维微服务的复杂性。
    许多使用微服务架构的产品或者系统,它们的团队拥有丰富的持集部署以及它的前任持续集成的经验。团队使用这种方式构建软件致使更广泛的依赖基础设施自动化技术。下图说明这种构建的流程:
基础构建流程
图5:基本的构建流程
    尽管这不是介绍自动部署的文章,但我们也打算介绍一下它的主要特征。我们希望我们的软件应该这样方便的工作,因此我们需要更多的自动化测试。流程中工作的软件改进意味着我们能自动的部署到各种新的环境中。
    整体风格的应用相当开心的在各种环境中构建、测试、发布。事实证明,一旦你打算投资一条整体架构应用自动化的的生产线,那么你会发现发布更多的应用似乎非不那么的可怕。记住,CD(持续部署)的一个目标在于让发布变得无趣,因此无论是一个还是三个应用,它都一样的无趣。
    另一个方面,我们发现使用微服务的团队更加依赖于基础设施的自动化。相比之下,在整体架构也微服务架构中,尽管发布的场景不同,但发布工作的无趣并没有多大的区别。
不同的模块化发布
图6:模块化部署的区别

容错性设计

    使用服务作为组件的一个结果在于应用需要有能容忍服务的故障的设计。任务服务可能因为供应商的不可靠而故障,客户端需要尽可能的优化这种场景的响应。跟整体构架相比,这是一个缺点,因为它带来的额外的复杂性。这将让微服务团队时刻的想到服务故障的情况下用户的体验。Netflix的Simian Army可以为每个应用的服务及数据中心提供日常故障检测和恢复。
    这种产品中的自动化测试可以让大部分的运维团队正常的上下班。这并不意味着整体构架的应用没有这么精巧的监控配置,只是在我们的经验中它并不常见。
    由于服务可以随时故障,快速故障检测,乃至,自动恢复变更非常重要。微服务应用把实时的监控放在应用的各个阶段中,检测构架元素(每秒数据库的接收的请求数)和业务相关的指标(把分钟接收的定单数)。监控系统可以提供一种早期故障告警系统,让开发团队跟进并调查。
    对于微服务框架来说,这相当重要,因为微服务相互的通信可能导致紧急意外行为。许多专家车称赞这种紧急事件的价值,但事实是这种紧急行为有时是灾难。监控是至关重要的,它能快速发现这种紧急不良行为,让我们迅速修复它。
    整体架构,跟微服务一样,在构建时是通明的,实情上,它们就是这样子的。它们不同之处在于,你需要清楚的认识到不同进程间运行的服务是不相关的。库对于同一进程是透明的,也因此不那么重要了。
    微服务团队期望清楚的监控和记录每个服务的配置,比如使用仪表盘显示上/下线状态、各种运维和业务相关的指标。对断路器(circuit breaker)状态、目前的吞吐量和时延细节,我们也会经常遇到。

设计改进

    微服务实践者,通常有不断改进设计的背景,他们把服务分解成进一步的工具。这些工具可以让应用开发者在不改变速度情况下,控制都他们的应用的需求变更。变更控制不意味首减少变更,而是使用适当的方式和工具,让它更加频繁,至少,很好让它变得可控。
    不论如何,当你试图软件系统拆分成组件时,你将面临着如何拆分的问题。那么我们的决定拆分我们应用的原则是什么呢?首要的因素,组件可以被独立替换和更新的,这意味着我们寻找的关键在于,我们要想象着重写一个组件而不影响它们之前的协作关系。事实上,许多的微服务小组给它进一步的预期:服务应该能够报废的,而不是要长久的发展的。
    Guardian网站就是这方面的一个优秀的例子,它初期被设计和构建成一个整体架构,但它已经向微服务的发展了。整体构架仍然是它网站的核心,但是他们使用微服务来增加那些使用整体架构API的新特性。这种方法增加这些临时的特性非常方便,比如运动新闻的特稿。这样站点的一个部分可以使用快速的开发语言迅速整合起来,当它过时后可以一次性移除。我们发现一家金融机构用相似的方法增加新的市场营销活动,数周或者几个月后把它撤销。
    可代替是模块化开发中的一个特例,它是用模块来应对需要变更的。你希望让变更是相同模块,相同周期中进行变化而已。系统的某些很小做变更部分,也应该放在不同的服务中,这样它们更容易让它们消亡。如果你发现两个服务一直重复的变更时,这就是一个要合并它们的信号了。
    把组件改成服务,增加了细化发布计划的一个机会。整体构架的任务变更需要整个应用的完整的构建和发布。然而,使用微服务,你只需要发布你要修改的服务就可以了。这将简化和加速你的发布周期。缺点是你需要为一个变更服务发布可能中断用户的体验而担心。传统的集成方法是使用版本来处理这些问题,但是微服务版本仅是最后的通告手段。我们需要在设计服务时尽可能的容忍供应商的变更,以避免提供多个版本。

其它

微服务系统多大?

    尽管“微服务”一词在架构风格中越来越流行,它的名字很不辛让人关注它的服务大小,以及对“微”这个组成的争议。在我们与微服务实践者的谈话中,我们发现了服务的大小范围。被报道的最大团队遵循亚马逊Tow Pizaa团队理念(比如,一个团队吃两个比萨就可以了。),这意味着不超过20号(一打)人。我们发现最小配置是半打的团队支撑起一打的服务。
    这也引发这样的考虑:规模为一个服务一打人到一个服务一个人的团队打上微服务的标签。此刻我们认为,它们是一样的,但是随着对这种风格的深入研究,也存在我们改变我们的想法的可能。

微服务与SOA

    当前我们谈到微服务时,通常会问,这是不是我们20年前讨论的面向服务架构(SOA)。这是一个很好的观点,因为微服务风格也SOA所提倡的一些优势非常相似。尽管如此,问题在于SOA意味的太多不同的东西了,因此通常时候我们谈的所谓“SOA”时,它与我们谈论的风格不一至,因为它通常是指在整体风格应用中的ESB。
    此外,我们发现面向服务的风格是这么的拙劣:从试图使用ESB隐藏复杂性, 到使用多年才认识到发费数百美元却没产生任务价值这样的失败,到集中治理模式抑制变更。而且这些问题往往很难发现。
    可以肯定的时,微服务社区中使用的许多的技术都开发者是从大型机构的整合服务经验中发展来的。Tolerant Reader模式就是这样的一个例子。由于互联网的发展,利用简单的协议这种方法,让它从这些经验传达的出来。这是从已经很复杂的集中式标准中的一种反模式,坦白的说,真让人惊叹。(无论何时,当你需要用一个服务来管理你的所有的服务,你就知道这很麻烦。)
    SOA的这种常见行为让微服务的提倡者拒绝打上SOA的标签,尽管有人认为微服务是从SOA中发展而来的,或许面向服务是对的。无论如何,事实上SOA表达这么多的含义,它给一个团队清醒的认识到这种构架风格就已经值的了。

多语言,多选择

    JVM做为一个平台,它的增长就是一个平台中运行多语言的最大的例子。过去二十年中,它通常做为更高层次语言的壳,以达到更高层次的抽象。比如,研究它的内部结构,、使用低级的语言写更高效的代码。尽管如此,许多整体风格并不需要这种层次的性能优化或者在语法及高层次上的抽象,这很常见(让我们很失望)。此外整体构架通常意味着使用单一的语言,这也限制着使用技术的数量。

实践标准和强制标准

    它有点尴尬,微服务团队倾向于避免这种通常由企业架构队伍定制的僵硬的强制标准,但是它们却非常乐于甚至推广这些开放的标准,如HTTP、ATOM、其它微规范。
    关键的不同在这些标准是怎么开发出来的,以及它们是怎么被推广的。标准被一些组件管理,如IETF认证标准,仅当它们在互联网上有几个在用的实现,通常源自于开源工程的成功应用。
    这些标准单独分离出来,与那种在企业中通常有没有什么编码经验的或者没有什么影响力的厂商标准进行区别。

让做对事更容易

    一方面,我们发现在持续发布、部署越来越多的使用自动化,是很多有用的工具开发出来帮助开发者和运营商的努力结果。为打包、代码管理、支撑服务的工具,或者增加标准监控的记录的工具,现在都非常常见了。网络中最好的,可能就是Netflix's的开源工具,但是包含Dropwizard在内的其它工具也被广泛的使用着。

断路器(circuit breaker)和产品中现有的代码

    断路器(circuit breaker)出现在《Realease It!》一书中,与Bulkhead和Timeout这样的模式放在一起。实施起来,这些模式用于构建通信应用时相当的重要。Netflix的博客在解释它们的应用时,做了大量的工作。

同步是有害的

    任务时候,你在服务间的调用使用同步的方法,都会遇到宕机时间的乘积效应。简单的说,你的系统宕机时间是你系统的单独组件的宕机时间的乘积。你面临的选择使用异步或者管理宕机时间。在www.guardian.co.uk中,它们在新平台中使用一种简单的规则来实现它:在Netflix中每次用户请求的同步调用,他们重新设计的平台API都会把它构建成异步的API来执行。

微服务是未来吗?

    我们写这篇文章的主要目的在于解释微服务的主要思想和原则。但是发时间做这事的时候,我们清醒的认识到微服务构架风格是一个非常重要的想法:一个值得企业应用中认真考虑的东西。我们最近使用这种风格构建了几个系统,认识那些也使用和喜欢这种方法的爱好者。
    我们认识的使用这种方式的先行者,包含亚马逊、Netflix、The Guardian、The UK Government Digital Service、realestate.com.au、Forward和comparethemarket.com。2013看的巡回会议充满了向正在想成为微服务一分子的公司,包含Travis CI。此外,大量的组件正在从事我们认为是微服务的事,只是没有使用微服务的名字而已。(通常,它们被打上SOA的标签,尽管,我们认为SOA有许多不同的地方。)
    尽管有这些积极的经验,然后,我们也不急于确认微服务是未来软件架构方向。至今为止,我们的经验与整体风格的应该中相比出来的是有优势的,但是我们意识知这样的事实,我们并没有足够的时间来证明我们的论证。
    你所使用的架构通常是你开发出来后,使用的几年的实际成果。我们看到这些工程是在一个优秀的团队,带着对模块化的强烈追求,使用在过去几年中已经衰退的整体架构构建出来的。许多人相信,这种衰退不太可能与微服务有关,因为服务边界是清晰的并且很难再完善的。然而,当我们还没看到足够多的系统运行足够长时间时,我们不能肯定微服务构架是成熟的。
    当然,还有原因就是,有人期望微服务构架不够成熟。在组件化方面的任何努力,其成功都依赖于软件如何拆分成适合的组件。指出组件化的准确边界应该在那,这是非常困难的。改良设计要承认边界的权益困境和因此带来的易于重构的重要性。但是当你的组件是被远程通信的服务时,重构比进程内的库又要困难的多。服务边界上的代码迁移是困难的,任务接口的变更需要参与者的共同协作,向后兼容的层次需要被增加,测试也变更更加复杂。
    另一个问题在于,如果组件并没有清晰的划分,你的工作的复杂性将从组件内部转向组件间的关系。做这事不仅要围绕着复杂,它也要面对着不清晰和更难控制的地方。很容易想到,当你在一个小的、简单的组件内找东西,总比在没有关系的混乱的服务间要容易。
    最后,团队技能也是重要的因素。新的技术倾向于被掌握更多的技能的团队使用。但是掌握多技能的团队中使用的技巧在较少技能的团队中并不是必需的。我们发现大量的少技能的团队构建混乱的整合构架,但是它要发时间去证明使用微服务在这种情况下会发生什么。一个糟糕的团队通常开发糟糕的系统:很难说,微服务在这种情况下是否能帮助它们,还是破坏它们。
    一个理性的争议在于,我们听说,你不应该从微服务构架开始做。最好从整体构架开发,做模块化开发,然后当整体构架出现问题是再把模块化拆分成服务。(尽管这种建议不是好主意,因为一个好的进程内接口并不是一个好的服务接口。)
    因此我们持这种谨慎的乐观。到目前为止,我们还没有足够认识,关于微构架能否被大范围的推广。我们不能肯定的说,我们要终结什么,但是软件开发的挑战在于你只能在不完整的信息中决定你目前要处理的问题。

【案例】互联网保险O2O平台微服务架构设计

       关于架构,笔者认为并不是越复杂越好,而是相反,简单就是硬道理也提现在这里。这也是微服务能够流行的原因,看看市场上曾经出现的服务架构:EJB、SCA、Dubbo等等,都比微服务先进,都比微服务功能完善,但它们都没有微服务这么深入民心,就是因为他们过于复杂。简单就是高科技,苹果手机据说专门有个团队研究如何能让用户更加简单的操作。大公司都是由小公司发展起来的,如果小公司在开始技术选型时感觉某个框架费时费力就不会选择,而小公司发展到大公司的过程,一般也伴随着系统不断优化的过程,而不断优化往往不会重新选择开发技术和框架,而是在原来基础改进,这也许就是简单框架流行的本质。

假设我们需要为超高业务量的保险代理企业设计一个“互联网+”保险平台。假设这家保险代理企业网上保险注册用户规模为2千万,门店及加盟商销售人员2万,年保单量2亿单(中国平安总用户规模达1.67亿,拥有超过79.8万名寿险销售人员和约24.6万名正式雇员。截至2015年6月30日,集团总资产达4.63万亿元,归属母公司股东权益为3,311.90亿元。而目前互联网保险领头羊众安保险,经营以小额贷款为主,由于背靠阿里巴巴,日保单销售量可达1亿,不过别人很难复制众安保险的模式)。因此我们取大型互联网企业和众安保险的折衷来考虑这个保险O2O平台。

l 需求分析

参考保险业务相关文档(文档不全),获得如下核心需求矩阵(因为涉及功能太多,只取大的功能点)。

 

分类 功能 质量 约束
电子商务(B2C) 产品展示(搜索、详情展示等) 及时响应、安全性、健壮性、易用性 多种险种,处理方式可能不同
  产品购买(提交订单、支付) 及时响应、安全性、健壮性、易用性 多种险种,处理方式可能不同
  用户中心(我的保单、我的理赔等) 及时响应、安全性、健壮性、易用性 多种险种,处理方式可能不同
代理人管理(加盟商管理) 车险投保(询价、录单、缴费) 及时响应、健壮性、可扩展性 多种险种,处理方式可能不同
  非车险投保(询价、录单、缴费) 及时响应、健壮性、可扩展性 多种险种,处理方式可能不同
  保单查询 及时响应、健壮性、可扩展性  
  单证管理 及时响应、健壮性、可扩展性  
  我的账户(我的保单、佣金结算等) 及时响应、安全性、可靠性、易用性 多种险种,处理方式可能不同
案卷管理 案卷录入 及时响应、健壮性、可扩展性 多种险种,处理方式可能不同
  索赔资料收取 及时响应、健壮性、可扩展性  
  案卷交接 及时响应、健壮性、可扩展性  
  案卷跟踪 及时响应、健壮性、可扩展性  
客户管理 客户信息维护 及时响应、健壮性、可扩展性 上传大量文件
  客户活动管理 及时响应、健壮性、可扩展性  
  商机管理 及时响应、健壮性、可扩展性  
  我的工作台(消息、活动、商机) 及时响应、健壮性、可扩展性  
保险公估 车险定损过程跟踪协助 及时响应、健壮性、可扩展性 多种险种,处理方式可能不同
  人伤出险协助 及时响应、健壮性、可扩展性 多种险种,处理方式可能不同
  法律援助服务 及时响应、健壮性、可扩展性 多种险种,处理方式可能不同

 

从O2O的概念来看:“O2O即Online To Offline,也即将线下商务的机会与互联网结合在了一起,让互联网成为线下交易的前台。这样线下服务就可以用线上来揽客,消费者可以用线上来筛选服务,还有成交可以在线结算,很快达到规模。该模式最重要的特点是:推广效果可查,每笔交易可跟踪(百度百科)”,不是每一种服务都适合O2O。商品类的销售不适合O2O,因为你直接从网店就可以购买根本不需要线下店。但保险类服务的确需要线下和线上结合,如果是纯线上将不能满足客户的服务需求。O2O的线下服务可以是加盟商、代理人,也可以是直营店。

上面的需求是按照用户角度提出的,虽然使用“系统”一词,但这里的系统是一个抽象概念,可能包括软件系统以及人事、制度等在内。上面的需求可以分为三大类,一类是针对终端用户纯线上服务:电子商务网站(B2C);一类是专门针对代理人服务的:代理人系统;剩下的是一些公共服务,有可能电子商务网站和代理人系统都会用到的一些服务。另外保险业务最大的一个问题是多个险种,不同险种处理方式有很大不同,这跟普通电子商务网站区别很大,比如天猫,所有商品都是同样的下单方式,同样售后服务(主要就是快递这块),而保险产品即使是线上B2C网站下单操作,短险、寿险、车险等也是不同的,更何况保险有一大堆售后服务,这些售后服务更加不相同。传统保险公司在处理这方面时,一般会做多个系统,比如寿险系统,车险系统等等。

l 系统分析

安装之前提到的业务规模我们分析(假设)出一些比较重要的性能需求:

a)产品方面:继续上线当前没有的保险产品

b)B2C网站日访问量:5000万PV

c)B2C产品购买并发高峰:2000 TPS

d)运维系统同时在线:1万(共有2万销售员或代理人)

e)运维系统并发高峰:2000 TPS

f)短险订单:每年1.85亿单

g)长险订单:每年500万

h)车险订单:每年1000万

i)案卷信息:每年新增100万单

日访问量5000万和产品并发2000 TPS是我们假设的,客户信息和案卷信息是随订单数据量变化而变化,在前面我们虽然假设了总共每年产生2亿个订单,但是根据保险种类,短险(旅游险、伤残险)明显产生了90%的订单量,这一点需要特殊处理。除此之外车险和长险(主要指寿险等)无论是投保还是售后服务都有明显不同,所以也需要特殊处理。

那么我们按照上面的需求,进行系统分析,首先按大的职责将职责相同的划分为一个服务。并且有了上面这个性能需求,所有功能需求都需要增加一项“质量”特性,那就是“高并发”,高并发会影响到所有设计。另外如果要将互联网保险平台质量特性排个序,最重要的是可扩展性、安全性,因为保险的种类多而且处理方式不同,除此之外,高并发和可靠性也会直接影响功能的实现,但并没有可扩展性影响大。深入分析职责后把每一种功能的实现关键技术列出,如下:

需求分类 实现需求 实现子系统及服务 软硬件实现技术
客户端 B2C电子商务网站 B2C Web客户端 集群部署、高速缓存、分布式缓存、搜索引擎技术、静态化
B2C电子商务网站手机客户端 B2C App客户端  
代理人管理 代理人Web客户端 集群部署、高速缓存、分布式缓存、搜索引擎技术、静态化
代理人管理手机客户端 代理人App客户端  
案卷处理管理 案卷处理Web客户端 集群部署、分布式缓存
客户管理管理 客户管理Web客户端 集群部署、分布式缓存
保险公估管理 保险公估Web客户端 集群部署、分布式缓存
运维产品管理 产品管理Web客户端 集群部署、分布式缓存
报表及财务统计 报表及财务统计Web客户端 集群部署、分布式缓存
公共服务 运维产品管理、Web前端产品访问 产品服务 集群部署、分布式缓存
电子商务或代理人订单管理 订单服务 集群部署、分布式缓存
电子商务或代理人等涉及财务操作 总账服务 集群部署、分布式缓存
报表及财务统计 报表服务 集群部署、分布式缓存
业务服务 B2C电子商务网站及手机客户端个人账户 B2C个人账户服务 集群部署、分布式缓存
代理人管理 代理人管理服务 集群部署、分布式缓存
案卷处理管理 案卷处理管理服务 集群部署、分布式缓存
客户管理 客户管理服务 集群部署、分布式缓存
保险公估管理 保险公估管理服务 集群部署、分布式缓存
短险开放式接入 开放式接入平台服务 集群部署、分布式缓存
工具性服务 保险公司产品对接 产品对接服务 集群部署、消息队列
在线支付 第三方支付服务 集群部署
短信邮件通知 通知服务 集群部署、消息队列
性能监控 日志采集服务 集群部署、消息队列
文件服务器 文件服务 集群部署、消息队列
服务授权与审计 服务授权与审计服务 集群部署
分布式事务管理 分布式事务管理服务 集群部署
定时任务管理 定时任务服务 集群部署

 

其中订单服务、产品服务、财务服务、工具服务为基础服务,其它各个业务模块的服务会调用这些基础服务。各个业务模块的服务都是根据业务领域进行划分的,同一业务领域下实现技术不同会被划分为两个服务,比如产品展示和订单原本属于同一个大的领域,但其因为实现技术和质量要求不同需要划分为两个服务。因为短险接入量大,而且大部分是跟第三方合作接入,因此设计短险接入公共接口服务平台处理大量短险订单。

 

l 存储及缓存架构

对于大型的高并发系统来讲,最重要的当属数据的架构。我们在前面也提到过,web系统业务处理模块本身就可以集群部署,当用户出现高并发时最先遇到的瓶颈就是数据库访问的瓶颈。这也是我们说数据架构最为重要的原因。其实web系统是典型的“计算机信息系统”,也就是说一切以数据(信息)为基础,所有的功能都是围绕着数据来的。这也是我们在这里说数据是web系统最重要的,很多公司在做少用户量web系统时直接设计好数据库就可以开发了。

按照上面划分的业务领域我们设计多个数据库,技术选项包括“是否读写分离”、“是否水平切分”及“路由键”。其中路由键是指在进行水平切分后,我们使用那个“标识”去查询数据库,一般来说会使用该业务领域聚合根对象的主键作为路由键。

数据库 是否读写分离 是否水平切分 水平切分路由键
产品数据库    
订单数据库   客户ID
公共数据库(元数据、公共数据)    
客户管理数据库   客户ID
案卷管理数据库   客户ID
代理人服务数据库   代理人ID
B2C电子商务个人账户数据库   客户ID
保险公估数据库   客户ID
工具数据库(短信、支付、文件)   客户ID
报表数据库    
日志采集服务数据库   日志ID

 

除工具数据库外,其它的数据库的划分很容易理解。工具数据库的数据也大都跟客户或说用户有关,比如“产品对接服务”,产品对接服务是指用户在购买了保单后,系统会自动对接到具体的保险公司接口去上传保单信息和下载保单,所以水平切分数据库时可以采用用户ID作为路由键。“在线支付”和“通知服务”也是类似,都是保存用户相关的数据,在线支付服务保存的是用户在线支付的流水,通知服务保存的是发送给某用户的短信或邮件。

另外在数据架构中我们也可以看到一个规律,就是使用了水平分库的存储结构就不能再使用读写分离,原因是防止存储单元过度泛滥,因为使用了水平分库之后,本身也要为数据库建立多个备份库,这个时候如果再用读写分离需要建立一套只读库,数据库的数量将增加一倍。使用了分库后我们可以把热点数据存储在分布式缓存中以起到读写分离类似的作用。

另外缓存也是存储技术的一种,而且非常重要。从日常生活中我们也可以看到这一点。比如你要去购买一台电脑,你会发现二级缓存和内存大的价格高出很多,如果你的显卡显存巨大,那将是顶级配置,发烧级配置。手机也是一样,内存大的手机速度明显快,价格也高,如果内存和持久化存储都高,那也是顶级配置。对于web系统也是一样,有些大型web系统只要缓存处理的好,数据库不需要分库就可以承载亿级的用户,比如维基百科、新浪微博等。也因此,不管是电子商务网站还是互联网+大型应用,都会在它们的架构中看到缓存大量使用的情况。

从整个web系统的架构来看,缓存在两个层面大量使用,分别是展示层和逻辑层,展示层通常使用高速的页面缓存,逻辑层通常使用高并发的分布式缓存。当然有些分布式缓存工具既可以在逻辑层使用也可以在显示层使用。

系统 是否使用CDN 是否使用高速缓存服务器(varnish) 是否使用分布式缓存(redis)
B2C电子商务网站  
报表及财务统计Web端    
B2C个人账户服务    
代理人管理服务    
案卷处理管理服务    
客户管理服务    
保险公估管理服务    
B2C个人账户服务    
短险公共平台服务    

 

l 逻辑架构

在给出系统总体的逻辑架构前,我们先看看系统前端和服务层直接的关系。前端是客户端,可以有多个客户端,也可以有多种客户端,比如手机端(APP、WAP、微信)等。客户端和服务之间的架构是典型的MVC架构,V是客户端,C就是spring mvc或servlet开发的控制层,对于Web应用V和C在开发角度是一个工程,剩下的M就是服务层。这是对于web系统来说的,对于app或者使用html直接实现的客户端,或者是.net实现的桌面客户端,由于这些客户端是单独开发的,没有控制层,因此需要增加一层“API 网关”作为这些客户端的控制层。

图中的服务组件就是指各个业务服务,这些服务可以看成是组件的概念。通常前端界面一个界面中需要的数据通常不仅仅来自于同一个服务,即使是来自于同一个服务,那也来自于很多不同的接口,servlet或api 网关作为控制层,所起到的作用就是组合接口、组合数据,并处理接口间的事务性。另外服务组件也是分层的,图中并没有展现,一般可以分为3层,从低到高依次是工具性服务组件、基础业务层服务组件、业务层服务组件。前端界面的请求按照从高到底向下传递和处理请求。

按照职责、通用性、技术特性综合考虑和计量,逻辑架构设计如下图:

 

  十几个子系统分别分布在服务层、控制层、表现层(典型的三层架构)。实体层和接口访问层虽然属于“层”,但它们并不单独发布,而是使用Jar包类库的方式提供给其它服务调用,是逻辑上的层。服务组件的构成大都是按照业务领域划分的,只有一个除外,就是“B2C网站”,B2C网站由于是面向终端用户的高并发电子商务网站,为了处理高并发,我们将其拆分为两个业务领域(也可以拆分成多个业务领域,看实际并发量),分别是用户账户和产品。用户浏览产品并购买,这是电子商务网站最基本的两个领域。其中产品浏览等功能由更底层的产品服务提供,用户账户功能由会员服务提供。还有一些功能,比如推荐商品可能是由其它服务提供,这些可以在控制层直接组合这些服务。

 

l 服务架构

服务框架采用典型的“服务注册表”模式,注册表使用redis。服务组件在启动时将自己注册进服务注册表,web服务器或api 网关在访问服务时查询服务注册表得到服务的uri,然后调用某服务接口。

 

淘宝的dubbo使用的是zookeeper作为服务注册表,之所以使用zookeeper,主要是使用它的负载均衡的功能。笔者认为restful接口没有必要使用zookeeper做负载均衡,可以使用nginx(负载均衡服务器都可以),所以没必要选用动态的zookeeper作为注册表,而是使用“redis+心跳监测”机制(redis也可以换为LDAP等)来完成服务注册和监控失效服务的功能。这个方案至少比dubbo简单几个数量级,简单就是硬道理。

在注册服务时只需要注册nginx服务器的IP以及服务描述信息即可。反观dubbo还要注册接口进注册表,笔者认为这个没必要,因为调用一个服务接口的充分必要条件就是知道服务器的IP即可。至于调用什么服务接口,肯定在代码里已经写死,目标服务器必定存在这个服务接口。将服务接口注册进服务注册表如果是为了监控审计服务的使用情况,那这个功能使用访问日志来实现能做的更好。

总体的分布式拓补结构如下图:

对于服务集群来讲,看上去像是一个大哥(nginx)带有众多小弟的结构。访问某服务群组只需要访问其nginx服务器即可,这里nginx均采用高可用方案,防止单台nginx出现问题。至于高层服务调用底层服务也是直接访问其服务器组的nginx。这个执行的流程其实和日常生活中的概念很像,如公司内部的执行流也是如此:老板分配任务给部门经理,部门经理分配任务给主管,主管分配任务给具体的个人(某单台服务器)。领导们起到的作用也是负载均衡和监控,负载均衡就是把任务可以分配给多个人同时执行(并且某个执行失败自动切换),监控就不必说了就是监控任务完成情况。在我们的方案里,会专门有个服务去监控所有服务器的执行情况,很简单的服务就可以做到。

l 关于分布式事务

如果研究一下EJB就会发现,EJB和微服务的架构基本相同,甚至所有面向服务(SCA、SOA等等)的架构都相差不大。很多人反对EJB,并不是EJB不够强大,而是它不够简单,它给项目带来的复杂性甚至超过了项目本身(这也是笔者不建议使用dubbo框架的原因)。曾有人说过:你要么把事情做的尽可能简单,让人挑不出毛病;要么把事情做的尽可能复杂,让人找不出毛病,EJB就是后者。EJB分布式事务机制实现的很好,可惜的是这种“一刀切”的事务机制,大大降低了web系统的性能,所以几乎所有面向互联网的应用都极少使用分布式事务,也就不会采用EJB。互联网应用本身事务性操作并不多,一些新闻、博客之类的网站甚至都不需要事务。另外一个方面,即使出现一个或两个分布式事务应用场景,也可以通过其它手段解决,比如事件机制等等。

在之前的文章中我们也提到过对于分布式事务最佳的策略是尽量避免。如果避免不了将按以下方式实现。

1)  将分布式事务性操作封装在一个服务中,这个服务使用XA或链式分布式事务管理。

2)  可以使用事件机制协调事务管理(具体做法就是事务失败后发失败事件到可持久化的消息队列,然后需回滚操作的接口监控此事件并执行回滚)。

3)  使用自定义分布式事务管理器管理分布式事务。

笔者设想的自定义分布式事务管理器主要是封装了流程及分布式事务相关功能,笔者将在其它文章专门讨论。如图所示,假设有一个事务需要依次调用ABCDE五个接口,我们首先调用分布式事务管理接口创建这条“流程”的实例,实例中五个接口分别对应五个状态,调用成功后将该接口对应的状态设置为“成功”,反之就是“失败”,流程处理结束后,分布式事务检查状态,然后按照一定的策略调用失败接口的反向操作接口去回滚数据(前提条件也是参与分布式事务操作的接口要开发反向操作接口)。这既是一个简单的流程引擎,也是一个分布式事务协调处理装置,具体是否有必要做的复杂(比如处理并行流程),还要看实际环境下分布式事务的情况,但笔者认为互联网前端应用使用简单流程应该足以应付。

 

l 开发架构

系统所需的工程,“[ ]”里面表示工程的名称。

从图中不难看出,我们将运维相关前端界面合并为一个前端系统,总体来讲前端只有3种,B2C前端、APP前端、运维前端。把运维前端合并为一个项目有利于加快前期的开发、部署的效率,在后期如果某子系统功能界面太多,可以将前端系统独立,比如公估系统、客户管理系统等,独立后的系统需要使用单点登录,这样就可以在各个系统之间免登陆切换。

开发环境:

编码:UTF-8

工具:Myeclipse 10

SVN:Site-1.8.22

Web服务器:Tomcat7

JDK: JDK1.7、 Java EE 5

开发环境:Maven 3

 

开发技术选型:

表现层:Bootstrap+Html+Jquery

MVC框架:Spring MVC 3.2

安全框架:Spring security 3.2

Rest接口实现:Spring MVC Rest

持久层:Mybatis3.2

分布式缓存:Redis

数据库:MySql 5.6

【拓展】使用容器技术来建立一个微服务架构

之前的博文中,我讲解了Linux容器技术的相关实现,比如如何使用Docker来建立流线型的开发和测试体验。因为可以实现跨不同类型基础设施的兼容(比如,在AWS上,容器也可以如实体服务器上一样轻松的运行),容器让代码的部署异常便捷。在实际工作中,测试和开发环境的细微不同很可能会导致应用程序的部署失败;因此在这种情况下,对于开发和测试工作,容器技术可以让开发者豁免很多预想之外的工作和相互推脱。

在本篇文章中,我们将讨论是什么特性让容器技术如此适应开发和测试工作,同样适用于在AWS平台上构建一个基于微服务的架构。对于Web应用程序来说,微服务架构可以让应用程序的代码库更加敏捷,并且容易管理。下面,我们将介绍这个架构为何可以大幅提升开发者生产效率的原因,并了解它能够快速迭代和扩充一个代码库的原理。对于快速发展中的创业公司来说,微服务架构可以让开发团队在研发过程中更加的敏捷和灵活。

Web开发简史

首先,我们先简洁地回顾下20年内基于Web开发的历史,它可以让我们知悉微服务架构为什么可以在Web开发领域如此的盛行,同时也顺便了解这个架构可以解决的问题。

在Web应用程序开发的早期,应用程序通常使用Common Gateway Interface(CGI)建立,这个接口为网络服务器提供了处理浏览器发来的HTTP请求时执行脚本(通常情况下用Perl编写)的能力。CGI的扩展性非常很好,因为它需要为每个输入请求都建立一个Perl进程。为了解决这个问题,那个时代的网络服务器通常都会添加模块化的支持。Apache,现下最为流行的网络服务器之一,增加了“mod_perl”让Perl代码可以在内部运行,这样一来,CGI脚本就可以在更少的时间内执行。

即使对比传统的CGI类似mod_perl这些技术有了很大的提升,但仍然存在问题。也就是说,负责视图层(比如,在HTML页面上执行一个动态模块)的代码通常会被混入应用程序逻辑代码中。这就意味着,完成一个简单的任务,比如在HTML列表中增加一列,或者在form中增加一个元素,通常需要修改一个低等级的应用程序代码。因此,Web程序开发技术下一个阶段中衍生了“server pages”,它允许在HTML嵌入执行代码。这样一来,应用程序逻辑代码与视图代码被很好的分离。在Java开发领域,一个被称为“Model 2”的设计模式得以快速演变,在这里,应用程序代码放到Java servlets中,数据则通过Java Beans进行,视图层逻辑则使用了Java server pages,详见下图:

图1:Model 2设计模型

随后,在Java领域,“Model 2”模式在很短的时间就演化成了“Model-View-Controller(MVC)”框架,比如Apache Struts。而在其他领域,Ruby on Rails则非常盛行。在MVC模式中,控制器类会定义方法,通过“route”类映射成URL模式被调用。 控制器方法会利用“model”类封核心应用程序实体的业务逻辑和数据。最后,每个控制方法都会渲染一个“view”用于显示,并修改相应模式类的方法。在这种模式下,业务、应用程序、视图逻辑被很好的分离,如图2:

图2:MVC设计模型

REST协议的盛行

就在MVC被广泛接受并成为网络开发途径的同时,进程间通信(IPC)也开始利用上了基于文本的序列化格式,比如XML和JSON。而在类似SOAP这些协议实现跨HTTP IPC的不久后,网络开发已不再限制于给浏览器建立交付内容的应用程序,为其他程序执行操作和交付数据的网络服务也逐渐走上历史的舞台。这种基于服务的架构拥有非常强大的功能,因为它消除了代码库共享的依赖性,从而开发者可以更进一步的解耦应用程序组件。而SOAP协议和相关的WS-*标准也变得越来越复杂,并更加依赖于应用程序服务器的实现,至此开发者开始投身更为轻量级的REST协议。同时,随着移动设备的剧增,Web UX development逐渐走向AJAX和JavaScript框架,应用程序开发者开始广泛使用REST在客户端设备和网络服务器之间做数据传输。

后来人们发现,MVC框架同样非常适合开发REST端点。REST面向资源的特性被很好的映射成了控制器和模型理念,如图3所示:

图3:MVC的REST端点

Monolithic架构

因此,曾今由模型、视图层、控制器组成,主要用于给应用程序交付HTML内容的MVC应用程序发生了本质上的变化——它们不仅可以支撑传统的HTML,也可以通过REST端点来支撑JSON。应用程序被部署为一个单一的文件(比如Java)或者同一个目录下的文件合集(比如Rails)。然而不容忽视的是,所有应用程序代码都运行在相同的进程中。因此在缩放过程中,开发者需要将应用程序代码的多个副本部署到多个所需的服务器上。下图解析了Monolithic架构:

图片4:Monolithic架构

在Monolithic架构中存在着很多的问题。首先,随着应用程序的功能和服务越来越多,代码将变得越来越复杂。对于新的开发者来说,这一点非常头疼。新型集成开发环境在加载、编译整个应用程序代码时也可能存在问题,同时这个过程的耗时也可能非常长。此外,因为所有程序代码都运行在服务器上的相同进程中,导致应用程序某个组成的扩展也非常难。如果某个服务是内存密集型的,而另一个是CPU密集型的,那么服务器必须有足够的内存和CPU来满足每个服务的需求。因此,鉴于每个服务器都使用非常高的CPU和内存,基础设施的整体花费可能会非常高,特别是在纵向扩展策略下。最后非常微妙的是,应用程序的组成通常也会映射到研发团队的结构上。UX工程师负责UI组件的建立,中间层开发者通常负责建立服务器端点,而数据库工程师和DBA们则负责数据访问组件和数据库。如果某个UX工程师期望给增加一些显示,他往往需要来自中间层和数据库工程师的配合。就像水一样,人们通常期望以最少的阻力执行,每个工程师也都期望为其负责的应用程序嵌入尽可能多的逻辑。鉴于这些问题,随着时间的推移,代码将越来越难以管理。

微服务架构

微服务架构的发明就是用来解决这些问题。定义在Monolithic架构应用程序中的服务将拆分成独立的服务,它们在不同的主机上进行独立的部署。

图片5:微服务架构

每个微服务都对应了一个独立的业务功能,也只定义了该功必须的一些操作。这听起来比较类似面向服务架构(SOA),事实上,微服务架构和面向服务的架构确实有很多共同的特性。两个架构都使用服务的模式组织代码,两种架构在不同的服务间都建立了非常明确的边界。然而,面向服务的架构起源于Monolithic应用程序交互的需求,通常彼此都会提供一个API(基于SOAP)。在面向服务架构中,集成重度依赖于中间件,特别在企业服务总线(EBS)中。微服务架构通常会利用一个消息总线,但是无论任何情况在消息层都不会存在逻辑——它纯粹的被用于服务之间的交互。这与ESB有着非常显著的差别,ESB包含了大量逻辑——用于消息路由、模式验证、消息翻译和业务规则。因此,对比传统的面向服务架构,微服务架构往往更为简单,不会包含用于定义服务间接口的同级别控制和规范化数据建模。通过使用微服务,开发将非常快速,服务的衍变也只需匹配业务的需求。

微服务架构的另一个核心优势就是服务可以基于资源的需求进行独立扩展。取代运行包含大量CPU和内存的大服务器,微服务可以被部署在更小的主机上,这些主机只需要满足其部署服务的需求。同时,开发者可以根据业务的需求选择开发语言,比如:一个图像处理服务可以使用类似C++这样的高性能语言实现,一个执行数学或者静态操作的服务可以使用Python实现,对资源进行增删查改的基础操作则往往通过Ruby进行。在微服务中,开发者并不需要考虑Monolithic架构中使用的“一刀切”模型——比如只使用MVC框架和单一的编程语言。

然而,不容忽视的是,微服务同样存在一些劣势。因为服务通常部署在多个主机上,很难持续跟踪指定服务究竟运行在某台主机上。同时,因为微服务架构使用的主机容量往往小于Monolithic架构,随着微服务架构不停的横向扩展,主机数量将以一个非常恐怖的速度增长。在AWS环境中,微服务架构中独立服务需要的资源往往会小于最小的EC2实例类型。从而造成了超量配置并浪费开销。此外,如果服务使用不同的编程语言将开发,这就意味着每个服务的部署都需要完全不同的库和框架,从而服务的部署非常复杂。


容器的用武之地

Linux容器技术的使用可以很大程度上缓解微服务架构所带来的问题。Linux容器技术使用了类似cnames和namespaces这样的内核接口,它允许不同容器共享相同的内核,同时容器之间还进行了完全的隔离。Docker执行环境使用了一个被称为libcontainer的模块,它标准化了这些接口。Docker同样为容器镜像提供了一个类GitHub的资源库DockerHub,让容器的共享和发布非常简单,也正是这种相同主机上的容器隔离简易了不同语言开发的微服务代码部署。使用Docker,我们可以创建一个DockerFile来描述所有用到的语言、框架和服务间库的依赖性。举个例子,下面代码中的DockerFile可以用来定义一个微服务的Docker镜像,它使用了Ruby和Sinatra框架:

FROM ubuntu:14.04
MAINTAINER John Doe <jdoe@example.com>
RUN apt-get update && apt-get install -y curl wget default-jre git
RUN adduser --home /home/sinatra --disabled-password --gecos '' 
sinatra
RUN adduser sinatra sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER sinatra
RUN curl -sSL https://get.rvm.io | bash -s stable
RUN /bin/bash -l -c "source /home/sinatra/.rvm/scripts/rvm"
RUN /bin/bash -l -c "rvm install 2.1.2"
RUN /bin/bash -l -c "gem install sinatra"
RUN /bin/bash -l -c "gem install thin"

使用这个镜像建立的容器可以便捷地被部署到一个主机上,这个主机同时还运行了另一个使用Java和DropWizard 定义的Docker镜像所建立的容器。容器执行缓解隔离了主机上运行的不同容器,因此不存在使用不同语言、库和框架容器所造成的冲突问题。

同时值得高兴的是,近期发布的Amazon EC2 Container Service(Amazon ECS)可以帮你搞定所有这些工作。使用Amazon ECS,你可以定义一个被称为“cluster”的计算资源池,一个cluster由一个或以上的EC2实例组成。Amazon ECS负责管理集群中所有基于容器的应用程序,提供 telemetry和logging,并管理集群的容量优化,进行高效的任务调度。Amazon ECS提供了一个“任务内容(task definition)”的理念,它可以定义组成一个应用程序的一组容器。task definition中的每个容器都指定了该容器所需的资源,而Amazon ECS将基于集群中的可用资源来调度这个任务的执行。

微服务可以非常便捷地被定义为一个任务,它可以由两个容器组成——一个负责运行服务终端代码,另一个负责运行数据库。Amazon ECS可以管理这些容器之间的依赖性,同时也可以跨集群进行资源平衡。同时,Amazon ECS还可以无缝的访问多个AWS重点服务,比如Elastic Load Balancing、Amazon EBS、Elastic Network Interface和Auto Scaling。通过Amazon ECS,使用 Amazon EC2部署应用程序的所有基本特征都对基于容器的应用程序可用。

此外,类似Amazon ECS 这样的容器解决方案还可以简化“service discovery(服务搜寻)”这样的实现。因为微服务往往会跨多个主机部署,并根据负载进行缩放,service discovery更有利于服务之间的定位。在最简单的情况下,可以使用负载均衡器来进行,但是在更为复杂的环境中,一个真正的分布式配置服务非常有必要,比如Apache Zookeeper。使用Amazon ECS API,与类似Zookeeper这样的第三方工具整合将非常容易。配置了Zookeeper的容器可以被添加到一个task definition中,并可以通过Amazon ECS在集群中的Amazon EC2调度执行。

从许多方面来看,使用容器技术实施微服务架构转变都与过去20年Web开发的衍变非常类似。许多这些衍变都是为了更好的利用计算资源,以及更方便的维护越来越复杂的Web应用程序。如我们所见,使用Linux容器技术来实现微服务架构完全匹配了这两个需求。在本文中,我们简单地接触了使用Amazon ECS来定义一个微服务架构,但是容器在分布式系统中的使用已经远超过了微服务。在分布式系统中,越来越多的容器成为了first class citizens,而在未来的报告中,我将讨论为什么 Amazon ECS对管理给予容器的计算是至关重要的。


产品经理都很寂寞!
扫一扫来撩!!!
  微信公众号码:kelebl
  QQ群:497315816