探讨这一全新架构术语的确切定义
“微服务架构”概念的提出已经有很长一段时间了,但在最近几年却开始频繁地出现。微服务架构是一种特定的软件应用程序设计方式——将大型软件拆分为多个独立可部署服务组合而成的套件方案。虽然这种架构风格的确切定义还存在争议,但并不妨碍其在众多企业的实际应用中被实践,并体现出了具备通用特征的业务功能、自动化部署、端点智能化以及对语言与数据的离散化控制能力。
Docker 作为一种开源的应用容器引擎,帮助开发者将他们的应用以及依赖打包到一个可移植的容器中,便于应用的部署和扩展。而随之产生的微容器概念和微服务正好相辅相成,通过 Docker 封装的应用可以轻松运行在以扩容能力见长的云计算平台上。数人云作为专业的数据中心管理系统,提供了基于 Mesos 和 Docker 技术的企业级容器云生产环境,通过一键部署、横向扩展、持续集成等特性,助力微服务架构在企业应用环境的实践。
由于篇幅问题,本文将分上下两篇,下篇将于明天放送给大家。
“微服务”——目前可谓早已人满为患的软件架构领域的新兴名词。虽然我们对于这种新生事物往往带着一种先入为主的蔑视与忽略态度,但经过几年的历练,我们发现这种软件构建风格正变得越来越具有吸引力。过去几年中已经有诸多企业将其引入实际项目,而至今其结果仍然相当积极,这甚至促使很多同业人士开始将微服务架构作为企业级应用程序的默认开发途径。但遗憾的是,目前仍然缺乏一套系统的概念定义,告诉我们微服务到底是如何实现这些成效的。
简而言之,微服务架构风格[1]是一类将单一应用程序作为由众多小型服务构成之套件加以开发的方式,其中各项服务都拥有自己的进程并利用轻量化机制(通常为HTTP源API)实现通信。这些服务围绕业务功能建立而成,且凭借自动化部署机制实现独立部署。这些服务匹配一套最低限度的中央式管理机制,且各服务可通过不同编程语言编写而成并使用不同的数据存储技术。
要解释微服务风格,那么首先应当将其与整体风格进行比较:整体应用程序作为单一单元进行构建。企业级应用程序通常包含三个组成部分:一套客户端用户界面(由运行在用户设备上的浏览器中的HTML页面以及JavaScript代码构成)、一套后端数据库(将大量插入至数据库管理系统的大量表构成,通常采用关系数据库)以及一款服务器端应用程序。该服务器端应用程序将负责处理HTTP请求、执行域逻辑、对来自数据库的数据进行检索与更新,同时选定HTML视图并将其发送至浏览器端。此服务器端应用程序通常为单一的逻辑可执行文件[2]。任何针对该系统的变更都需要对该服务器端应用程序进行新版本构建与部署。
这样的整体服务器机制在构建此类系统中可谓不可或缺。我们用于处理请求的全部逻辑都运行在单一进程当中,允许大家使用语言中的基本功能以将该应用程序拆分为类、函数以及命名空间。通过这种方式,我们能够在开发人员的笔记本设备上运行并测试应用程序,同时利用一整套部署流程以确保全部变更都经过妥善测试而后被部署在生产环境当中。大家可以将大量实例运行在一套负载均衡方案之后,从而实现横向扩展能力。
这类整体应用程序当然能够切实起效,但人们却逐渐发现其中存在着诸多弊端——特别是在将大量应用程序部署在云环境当中的情况下。由于变更周期被大量集中于一处——即使仅仅指向应用程序中的一小部分,单一变更亦要求我们对应用程序整体进行重构与重新部署。随着时间推移,我们往往很难保证理想的模块化结构,这意味着本应只影响单一模块的变更往往会扩散至该模块之外。规模伸缩亦要求我们对整体应用程序进行规模调整,而非单纯为其中必要的部分进行资源扩容。
图一:整体型应用程序与微服务架构应用程序
正是这些弊端造就了如今的微服务架构风格:即以服务套件的形式构建应用程序。除了各服务能够单独进行部署与规模伸缩之外,每项服务还具备牢固的模块边界,甚至允许我们在不同的服务当中使用不同的编程语言进行代码编写。另外,各服务亦可由不同团队负责管理。
我们认为微服务风格并不算什么新鲜事物或者创新成果,其历史至少可以追溯至Unix设计时代。但我们同时亦坚信,微服务架构一直未能受到足够的重视,而其确实能够帮助大家更好地完成软件开发工作。
微服务架构之特性
我们无法给微服务架构风格出具一条确切的定义,但我们却可以根据该架构表现出的各类共同特性对其加以描述。正如各类根据共同特性做出的定义一样,并不是所有微服务架构都符合这些特性,但可以肯定的是具备这些特性的微服务架构占据大部分比例。尽管我们各部分内容的作者仅仅是相关技术社区中的活跃成员,但制作这份文档是为了对采用微服务架构的工作流程及成果做出总结,而且其中仍有相当一部分表述并非严格定义——只应作为常见情况考量。
通过服务实现组件化
长久以来,我们一直参与软件行业之内并意识到人们对利用组件整合方式构建系统的渴望——这种思路与我们在物理世界中采取的构建机制非常相似。而在过去几十年当中,我们发现已经有大量公共库渗透到多数语言平台当中并成为其坚实的组成部分。
在谈到我们所使用的组件时,大家可能会发现不同群体对组件的定义也有所区别。我们对组件做出的定义是,其属于软件中的一类单元,且具备可更替性与可升级性。
微服务架构会使用这些库,但其实现组件化的主要手段则是将软件拆分成多个服务。我们将“库”定义为与程序相对接且可通过内存内函数调用发挥作用的组件,而“服务”则为进程之外的组件,其可通过Web服务请求或者远程程序调用等方式实现通信。(这里的服务概念与多数OO程序中的服务对象概念有所区别[3])。
将服务作为组件加以使用(而非库)的一大原因在于,服务具备独立可部署能力。如果大家的应用程序[4]由单一进程中的多个库构成,那么指向任何单一组件的变更都会致使该应用程序必须进行重新部署。但如果该应用程序被拆分成多项服务,那么单一服务变更将只会致使该服务进行重新部署。虽然这并非绝对,例如某些变更会导致服务接口受到影响,但一套优秀的微服务架构旨在尽可能少地对服务协议中的服务边界及演进机制产生干扰。
将服务作为组件的另一个理由在于实现更为明确的组件接口。大多数编程语言并不具备用于定义明确发布接口的良好机制。一般来讲,其只会提供说明文档及规则以防止用户打破组件封装,但这同时亦会导致不同组件之间的耦合程度过高。利用明确的远程调用机制,服务能够轻松避免此类难题。
但以这种方式使用服务亦存在一定弊端。远程调用在资源需求方面往往远高于进程内调用,因此远程API需要采取粗粒度设计,但这亦会增加API的使用难度。如果大家需要更改不同组件间的职能分配,那么这类需求在跨越进程边界时往往不易实现。
通过粗略观察,我们往往会发现这些服务会与各运行时进程相映射——但这仅仅只是第一印象。一项服务可能由多个进程构成,且各进程始终共同进行开发与部署——这方面实例包括只由单一服务所使用的应用程序进程以及数据库。
围绕业务功能构建组织
当着眼于将单一大型应用程序拆分成多个组成部分时,管理人员通常更重视技术层,其中具体包括UI团队、服务器端逻辑团队以及数据库团队。当这些团队据此进行拆分时,即使是最简单的变更也将给项目造成跨团队协作负担,并因此导致时间与预算的双重支出。睿智的团队会对此进行优化,同时采取两害相权取其轻的办法——即强制要求逻辑存在于一切与之相对接的应用程序当中。换言之,也就是实现逻辑的普遍存在性。这正是所谓康威法则[5]的一种实际表现形式。
任何组织在设计一套系统(广义层面的系统)时,其设计成果都会直接体现该组织所使用的沟通结构。
--梅尔文·康威,1967年
图二:康威定律的实际体现
微服务方案对于各部门而言是一种不同于以往,且以业务功能为核心的服务拆分及组织途径。此类服务采用软件方案在业务层面中的广泛实现堆栈,具体包括用户界面、持久性存储以及任何外部协作机制。因此,各团队将拥有跨职能特性,包括开发过程当中要求的全部技能组合:用户体验、数据库以及项目管理等等。
图三:由团队边界决定的服务边界
微服务架构有多“微”?
尽管“微服务”早已成为一种极具人气的架构类型,但这一名称却并不能准确反映服务的实际规模——换言之,“微”服务并不一定微。在与众多微服务从业者的交流当中,我们发现服务的具体规模可谓多种多样。其中规模最大的成果源自Amazon公司旗下的“两块披萨”团队(即整个团队只需两块披萨即可填饱肚子),这意味着其总人数在十位左右。而规模较小的团队则由六人组成,负责支持六项服务。
那么这就带来了新的问题:这种十二人对单项服务的机制同一人对单项服务之间存在着怎样的差别?二者也许不可一概而论。就目前而言,我们姑且认为双方属于同类团队结构,但随着对微服务认识的持续深入,也许我们未来将抱持新的观点。
采取此类组织方式的企业实例可参见www.comparethemarket.com,其各职能团队共同负责构建并运营每款产品,而每款产品则被拆分为一系列独立的服务——且各服务间通过一套消息收发总线实现通信。
大型整体应用程序亦可以始终围绕业务功能实际模块化,不过这种状况并不常见。诚然,我们都听说过由大型团队构建的单一整体应用程序根据自身业务线进行设计与划分。然而在这类情况下,最大的问题在于整体应用程序在组织当中需要考虑太多背景信息。如果其整体范畴当中包含太多模块边界,那么团队中的单一成员将很难通过短期记忆对其进行管理。除此之外,我们发现这种模块化业务线的维护工作还要求相关人员具备极高的专业技能水平。相比之下,服务组件能够令拆分方式更为明确,从而大大简化团队边界的设定与认知。
产品而非项目
大部分应用程序开发工作都会遵循项目模式:其目标在于交付软件方案中的特定部分,并拥有直观的完成指标。在软件开发工作完成后,其会被传递至运维部门,这时负责构建该软件的团队也将即刻解散。
微服务的支持者们则认为这种模式并不可取——他们的主张是相关团队应该伴随产品走过整个生命周期。这方面最典型的例子应该是Amazon公司提出的“谁构建,谁运行”原则,其中开发团队需要对生产环境下的软件成果承担全部责任。这就要求开发人员在日常工作中全程关注其软件的生产运行情况,同时掌握来自用户的反馈意见,意味着他们需要在一定程度上为用户提供技术支持服务。
产品的定位应始终与业务功能相协调。相较于以往将软件视为一整套已经完成的功能集的心态,微服务架构要求我们全程与之保持关联,并思考该软件能够如何协助用户加强业务功能。
当然,我们完全可以将同样的思路引入整体应用程序当中,不过大量小型服务集合能够显著简化服务开发人员与及用户之间的个人联系。
智能化端点与傻瓜式流程
在跨越不同进程构建通信结构时,我们发现很多产品及方案会直接把智能化机制塞进通信机制本体当中。这方面的典型实例就是企业服务总线(简称ESB),ESB产品当中通常包含复杂度极高的消息跌幅、编排、转换以及业务规则应用等机制。
微服务社区则倾向于使用另一种实现方式:智能化端点与傻瓜式流程。采用微服务架构的应用程序旨在尽可能实现解耦化与关联性——它们各自拥有自己的域逻辑,而且在经典Unix场景下的运作方式更像是过滤器机制——接收请求、应用合适的逻辑并生成响应。这一切都通过简单的REST类协议实现编排,而非经由WS-Choreography或者BPEL等复杂协议以及中央编排工具实现。
目前最常用的两类协议为配合源API的HTTP请求-响应与轻量化消息收发协议[6]。对于前者,最简练而准确的说明是:
立足于Web,而非居于Web背后。
-- Ian Robinson
微服务团队采用的正是万维网(在很大程度上亦包括Unix在内)所遵循的原则与协议。一般来讲,其使用的资源能够为开发人员或者运维人员轻松实现缓存处理。
第二类作法则是立足于轻量化消息总线实现消息收发。这类基础设施选项通常具备傻瓜式特性(这种傻瓜特性体现在实现操作上,即只需匹配消息路由机制,再无其它)——以RabbitMQ或者ZeroMQ为代表的简单实现方案仅仅需要提供一套可靠的异步结构,而服务的全部智能化元素仍然存在于端点当中并负责消息的生成与消费。
在整体应用程序当中,各组件在进程内执行并通过方法调用或者函数调用的方式实现彼此通信。将整体应用程序转化为微服务形式的最大难题在于改变这种通信模式。由内存内方法调用指向PC通信机制的简单转换往往无法良好起效。相反,大家需要利用粗粒度方式取代原本的细粒度通信机制。
脚注
1: “微服务”一词最早被威尼斯附近的一个软件架构师小组于2011年5月首次提及,当时他们用这个词汇来描述自己近期研究项目当中所涉及的通用性架构机制。2012年5月,该小组作出最终决议,认为“微服务”是最适合的架构名称。2012年3月,James在《微服务-Java以及Unix方式》当中就此发表了一篇案例研究报告,而Fred George也几乎在同一时间进行了相同的工作。Netflix公司的Adrian Cockcroft将微服务架构称为“细化SOA”,并认为这是一套在Web规模下具备开创意义的架构类型。Joe Walnes、Dan North、Evan Botcher以及Graham Tackley也分别在这篇文章中对此作出了评论。
2: 文章中所使用的“整体”一词长久以来一直被Unix业界所使用。其首次出现在《Unix编程艺术》一书中,用于描述那些过于庞大的系统方案。
3: 很多面向对象设计人员,也包括我们自己,都会在域驱动设计当中使用“服务对象”这一表述,专指那些并不具备实质性联系但却拥有重要作用的对象。这与我们在本文中所使用的“服务”一词在表意上完全不同。遗憾的是,服务这个词汇同时具备两种含义,而我们对这种多义词也没有更好的处理办法。
4: 我们将一款应用程序视为一套社会性体系,其中融合了代码库、函数组以及供应主体。
5: 大家可以查看梅尔文 康韦网站上的原文论述。
6: 对于规模极为庞大的应用体系,企业通常会采用二进制协议——例如protobufs。使用二进制协议的系统仍然符合智能化端点与傻瓜式通道的特性——并为了规模化而在透明度方面作出妥协。不过大多数Web方案与绝大多数企业不需要在这方面考虑太多——一般来讲,透明度越高、效果就越好。