微服务概述

“微服务”这个术语在过去几年如雨后春笋般涌现,它是一种构建可独立部署服务套件的软件设计方式。虽然这样的架构风格没有明确的定义,但它们在组织方式、业务能力、自动化部署、智能化终端以及对语言与数据的去中心化等方面具备共同的特征。

以下内容摘自Martin Fowler的网站

“微服务”,又一个出现在拥挤的软件架构街道的新名词。虽然我们的第一反应是不屑一顾,但它的确是一个出镜率越来越高的软件设计风格。在过去的几年中,我们已经看到很多的项目使用了微服务,目前来看效果不错,我们很多同事已经将它作为构建企业应用的默认方式。但很遗憾,并没有很多资料解释微服务是什么以及如何实现微服务。

简而言之,微服务是一种将单个应用以许多微小的服务所组成的服务套件的形式来构建软件的方法,每个微服务拥有自己的轻量级数据处理模块以及通信机制(通常是HTTP API的形式)。微服务围绕业务能力和各自独立的自动化部署机制构建而来。由于微服务需要极少的集中管理,因此各个服务可以使用不同的编程语言以及不同的存储技术。

为了解释微服务的设计风格,我们先来把它和单块架构风格做一个比较。单块架构的应用只有一个单元。企业应用常常包含三个主要部分:客户端用户界面(包括运行在用户计算机浏览器中的 HTML 页面和 JavaScript)、数据库(包括保存在常见的关系型数据库中的各种表)和服务器端应用程序。服务器端应用程序处理 HTTP 请求,执行业务逻辑,在数据库中检索和更新数据并选择和渲染 HTML 视图发送到浏览器。此服务器端应用程序是一个完整的、 单一的逻辑可执行单元。任何对系统的更改都需要构建和部署完整的服务器端应用程序的新版本。

这样的单块服务器是构建系统最自然的方式。所有处理请求的业务逻辑都在同一个进程中,它允许你使用编程语言的特性来将整个应用划分为类、函数及命名空间。利用某些方法,你可以在笔记本电脑中运行和测试应用程序,并使用部署流水线确保新的更改通过了测试,并部署到了生产环境中。最后你可以通过增加运行实例并进行负载均衡,对应用进行横向扩展。

单块应用是可行的,但越来越多的人在使用的过程中受挫,尤其是随着越来越多的应用被部署到云中。整个系统的更新周期是被绑在一起的——对应用的一小部分进行了更改,就需要整个系统重新构建和部署。随着时间的推移它往往很难保持一个良好的模块化结构,继而难以保证新的更改只影响其所在的模块。需要对应用进行扩展时,只能将整个应用一起进行扩展,而不是扩展应用中的某个部分,这也消耗了更多的资源。

MicroservicesFowler

这些挫折导致了微服务的架构方式:以服务套件的形式构建软件。微服务是独立部署的和可扩展的,每个服务都有明确的模块边界,甚至允许不同的服务使用不同的编程语言,它们甚至可以由不同的团队管理。

我们不认为微服务架构是什么创新,它的历史可以追溯到 Unix 年代的设计思想。但还没有足够多的人考虑过采用微服务架构,如果他们使用微服务,很多软件开发过程会变得更好。

了解更多信息:

詹姆斯和马丁的文章接着罗列了9个微服务的特点来定义什么是微服务架构,并探讨了其与面向服务的架构(SOA)的关系,最后论述了这种风格是否是企业软件的未来。

请到这里继续阅读:http://martinfowler.com/articles/microservices.html

James Lewis是 ThoughtWorks 的资深咨询师,也是技术顾问委员会的成员。James 的兴趣点在整合企业级系统时采用小型的相互协作的服务构建应用。他已经成功得构建了许多采用微服务的系统,并且在最近的几年里一直是社区的活跃分子。

Martin Fowler 是一位作家,演讲家和大名鼎鼎的软件开发者。如何组件化软件系统的问题一直困扰着他,一些模棱两可的答案并没有让他满意。他希望微服务不要辜负倡导者们所提出的承诺。

Share

微服务即演进式架构

作者:Neal Ford & Rebecca Parsons
译者:禚娴静

微服务架构风格正在席卷全球。去年三月,O’Reilly举办了他们的第一届软件架构大会,提交的话题中与微服务相关的占绝大部分。那么为什么这种架构风格会突然如此流行了呢?

微服务是后DevOps革命时代出现的第一种全新架构风格。它第一个全面拥抱持续交付的工程实践,也是演进式架构家族的一员。演进式架构以支持增量的、非破坏的变更作为第一原则,同时支持在应用程序结构层面的多维度变化。不过,微服务仅仅是支持某些演进行为的众多架构之一。本文分享了演进式架构风格的一些特点和原则。

演进式架构

软件行业曾经有这样一个共识,架构一旦确定,“日后很难改变”。演进式架构将支持增量式变更作为第一原则。由于历来变更都是很难预测的,改造的成本也极其昂贵,所以演进式架构听上去很吸引人。如果真的可以在架构层次做到演进式地变更,那么变更就会更容易、成本更低,也能发生在开发实践、发布实践和整体敏捷度等多个地方。

微服务满足这一定义,因为它遵循强边界上下文的原则,从而使得Evan的领域驱动设计中描述的逻辑划分变成物理上的隔离。微服务能够通过采用自动化基础设施建构、测试、自动化部署等先进的DevOps实践,获得这种分离。因为每个服务在结构层面与其他服务都是解耦的,替换服务就像替换乐高积木一般。

演进式架构的特点

不同的演进式架构展示了一些共同的特点,我们已经识别了很多特点,并收录进即将出版的《演进式架构》一书里。本文将分享其中的几个特点。

模块化和耦合

边界划分明确的组件,显然可以给希望做出非破坏性变更的开发人员以更大的便利。而毫无架构元素的大泥球架构就无法做到演进式变更,因为它缺少模块化。

0602-微服务即演进式架构-1

[在一个大泥球项目中类(图中圆边上的点)与类之间的耦合度 – 来自一个不愿透漏名字的项目]

不适当的耦合将变更导向难以预料的方向,从而阻碍演化。而演进式架构都支持一定程度的模块化,这种模块化通常体现在技术架构层面(例如经典的分层架构)。

围绕业务能力组织

受领域驱动设计的启发,越来越多的成功架构都以在领域架构层的模块化为特色。基于服务的架构与传统的SOA主要区别在于模块划分的策略,SOA是严格按照技术层进行模块划分,而基于服务的架构则倾向于按业务领域划分。

试验

试验(experimentation)是演进式架构给商业交付带来的最大价值之一。从操作角度来讲,可以采用A/B测试、金丝雀发布等常见的持续交付实践对应用进行低成本的、微小的变更。微服务架构常常是围绕服务之间的路由来定义应用程序的。通常,微服务架构围绕服务之间的路由来定义应用程序,允许同一个服务的多个版本同时运行。这反过来也使得试验和现有功能的逐步替换成为可能。最终,这使得你的业务可以花更少的时间去猜测待办故事项,从而投入到假设驱动开发中。

演进式架构的原则

思考演进式架构的另一个方法就是通过考察它的原则。原则描述了架构本身的不同特点或者设计架构的方法,其中一些原则专注在特定的架构决策的过程。

适应度函数

我们将浮现式和演进式架构区别对待,并且认为这种区分是很重要的。就像在进化计算技术中的遗传算法一样,一个架构级别的适应度函数指明了我们的目标架构是什么样子,其中一些系统关注高运行时间,而其他一些系统则会更关注吞吐量或者安全。

fitness-function

[这张雷达图展示了适用于软件系统的几个重要的适应度函数]

在项目前期思考一个特定系统的适应度函数,可以对决策的制定和决策的时机提供指导。适应度函数是对架构决策的衡量,通过适应度函数,我们可以预见架构是否在向正确的方向演进。

痛苦的事提前做

受极限编程社区的启发,许多持续交付和演进式架构的实践都体现了“痛苦的事提前做”的原则。在做一个项目的时候,如果一些事情可能会很痛苦,那么你需要强迫自己更早更频繁地去做这些事情。这反过来会鼓励你用自动化的手段消除这些痛苦并能提前识别问题。那些基础的持续交付实践,如部署流水线、自动化基础设施建构、数据库迁移,就是这一原则的应用,它们会提早解决变更带来的常规问题,从而使架构的演进更容易。

最后责任时刻

决策的时机是传统架构和演进式架构的最主要区别。这些决策可以是围绕应用程序的结构、技术栈、特定的工具或通信模式。在传统的架构中,这些决策发生在早期写代码之前。而在演进式的架构中,它们发生在最后责任时刻。延迟决策的好处是有更多可用的信息来辅助决策。其成本是在做出决定后任何可能出现的重复工作,这些工作可以通过合适的抽象得到减轻,但成本仍然是实打实存在的。相比而言,决定太早的成本也是很明显的。比如一个通讯工具的选择,不同的工具有不同的特性。如果我们选择一个可能最终也不会用到的重量级工具,那就是在项目中引入了技术债。这个技术债是由于使用了错误的工具导致开发进度延缓而出现的。你不能拿事先“抽象一切”作为借口。我们仍然支持敏捷YAGNI(You Ain’t Gonna Need It,你不会需要它)的原则,但会更倾向于在合适的时候做出决策。

当然,我们在考虑最后责任时刻的一个最直接的问题就是,什么时刻是最后责任时刻。适应度函数对这个问题提供了指导建议。那些会对架构、设计选型或者项目的关键成功有重大影响的决策,都必须尽早做出,延缓这些决策往往得不偿失。

总结

软件架构师常常用画图的方式来解释系统之间的适配,然而太多的架构师并没有意识到,一个静态的二维架构图的生命是很短暂的。软件世界是不断变化的,它是动态的而不是静态的存在。而架构也并不是一个等式,它是持续过程的快照。

持续交付和DevOps运动都诠释了一个问题,即由于忽略实现架构并使之保持常新所要付出的精力。其实架构建模和付出这些精力本没有什么问题,但是架构的实现仅仅是第一步。架构只是抽象,直到真正投入运维。换句话说,如果你没有实现并升级过一个架构,甚至帮它度过难关,那么你就无法真正判断架构的长期生命力。

架构师的运维意识是演进式架构的关键。演进会影响具体的实现,因此这些实现细节是不能忽略的。持续交付在架构层面的要求,使得实现更可视化,演进更简单化。因此,持续交付是所有演进式架构重要的助力。

其他资源:

在这期ThoughtWorks技术领袖播客中,Rebecca和Neal讨论了演进式架构的含义,以及组织如何将它作为商业优势加以使用。

原文链接:Microservices as an Evolutionary Architecture

Share

微服务和演进式架构

文章译自YouTube – Rebecca Parsons:Evolutionary Architecture & Micro-Services

译者:占红来

在我们的软件开发流程中,经常需要面临改动,有来自用户需求的改动,来自市场的,以及为了一些潜在机会而产生的改动等。当这些改动来临的时候,我们需要能够快速做出调整。但不幸的是,事情并不总是如我们所愿。

那么我们之前是怎么做的呢?

对于资历较老的程序员来说,应该都还记得有一种基于可重用组件的设计方法。在这种方法中,专门有一个架构团队,他们来识别出整个组织中 各个部门应该怎么使用某一组件。某些时候架构团队的业绩甚至还会与其所设计的组件被使用的次数进行挂钩。但是不幸的是,由于架构团队和组件使用团队缺少连接,因此架构团队很难设计出一个有口皆碑,受使用者欢迎的组件。

还记得另外一种通过数据库做集成的架构么?是的,我们之前都是这么做的,在这种架构模式中,我们有一个巨型数据库,所有的数据都囊括其中。围绕着这个数据库的周围,有一圈各种各样的应用,它们支配着这些数据。

此外还有一种曾经颇为流行的面向服务的架构,这种架构理念声称它们可以达到我们想达到的目标。这种架构确实有其做的不错的一些地方,首当其冲的是它让组织者知道并不是所有的东西都必须是模块化的。与此同时,它也激发了大家开始思考,将多种功能更好的集成到一起,而不是紧耦合到一块,比如你可以想想使用中间件,消息队列或者其他一些更松耦合的方法。

SOA(面向服务型架构)让最终一致性得到更加广泛的认同。一直以来,传统的(DBA)数据库管理员这个角色起着至关重要的作用,因为最有价值的数据资源均由他们保护。在访问数据库的时候,一切都是关系型的,都是事务处理。因此每当你说我需要做持久化的时候,就会有人告诉你,放在一个关系型数据库里就好了,大家对此都达成默契了,没有任何问题。然后所有的操作都是完整的事务处理,我们可以使用关系型数据库所能提供的所有功能。以前大家都是这么干的。而SOA的出现最少启发了大家开始讨论我们是不是可以用另外的方式来取代之前的老路子,从这个方面来讲,SOA确实有其正面意义。

但不幸的是,SOA也犯了一些错误。最主要的一个问题是其庞大的服务。如果你看一看使用SOA模型做出来的这些服务,这些海量服务作为一个全局组件,还是太大了,太难做出改动,也太难部署了。其次,SOA倾向于提供方驱动的服务,我指的意思是,当你开始想我要写一个用户服务,我要给用户提供这些信息,因为我是这些信息的提供方。我假设我知道用户将会如何使用这些信息。这种思路其实和设计可重用组件的架构师差不多,我们统称为信息提供方驱动。一般而言是由组织中的IT部门做完之后移交给业务部门使用,这种设计中的想法大部分是从系统的角度在想问题,而不是从应用的角度。

SOA的另外一个问题是其充斥着大量的编配(orchestration),注意这里我们说的是编配而不是编排(choreography)。在管弦乐(也是orchestration这个词)中,有一个统一的指挥,由他来指挥整个乐团,他来设置节奏,音量等各种信息。整个乐团里的个体都无一例外的听命于这个指挥。我们设想一下,如果某个时候同时发生两件事,那么可能就需要两个指挥。总而言之,在这种模型里面,有一个类似指挥的角色来对整个系统全盘负责。而编排却不是这样,在编排的时候,每个个体只需要关注自己周围的其他成员就行,而并没有一个统一的总指挥。当出现问题的时候,每个个体只要知道自己应该怎么调整就行,同样也不需要一个所谓的总指挥来指挥它进行调整。SOA这种类似管弦乐团的组织方式,过分集中化了,从而导致其本身通常难以测试。

SOA还有一个问题是工艺受阻。SOA需要我们有这样的供应商,能满足业务部门的所有想法,然后业务部门只需要坐在自己的办公室里面做一些自定义的配置就可以了。因此最后的工具通常是我们将一些之前有人用过的系统稍作修改来供应另外一群用户。

事实上,SOA搞出来的东西甚至比没有他们之前还要更难以修改。讽刺的是SOA恰恰是为了让改动变得容易而诞生的。与此同时,SOA不可避免的也很难进行测试。

好,现在我们来聊聊微服务(micro-services)

简单来讲,你可以将微服务理解成正确的SOA模式。我们的关注点还是服务,在微服务的架构里面,服务是作为一个基础单元存在的。SOA本身即是面向服务的架构,所以我们这个其实也可以叫SOA2,但是我们还是用了微服务这个名字,目的就是提醒大家不要搞海量服务。这是一段James Lewis和Martin Fowler的文章中摘抄的一段关于微服务的定义:

一种将一组小型服务做成一个单独应用的开发方式,每一个小型服务在其独自的进程中运行,使用诸如HTTP资源API等其他轻量级方式进行通信。重点是要轻量级。

微服务有些什么特点呢?

第一个显而易见的特点是,微服务中每个个体服务单元很小。这里所谓的小其实并不是绝对的,其思想的要点是,与其搞一个大而全的服务体,我们更倾向于小一些的单一服务。微服务的另外一个特点是,这些服务是按照业务能力构建的。之前我们经常掉入一个陷阱,就是从系统的角度思考问题,而不是从业务角度。现在我们不仅要思考系统能力,更要思考业务能力。并且我们需要按照业务能力来组织工作。对于业务部门而言,如何实现的并不重要,他们所关心的是系统长什么样子,怎么样能方便用户使用。业务部门是这么思考的,我们也对应的需要围绕业务能力进行设计。

微服务中的服务个体都是可以单独部署的。也就是说,只要没有动接口协议,在修改某一个服务个体的时候,完全不用理会其他服务个体。在微服务中,只有很少一部分的集中管理,而由于各个服务个体之间是相对独立的,因此我们甚至可以在不同的服务单元采用不同的技术栈。比如某一部分的计算逻辑比较复杂,我们选用clojure,另外一部分则对应着很强的对象模型,我们可以用一些面向对象的编程语言等等。

在微服务中,我们主张“智能端点”和“傻瓜管道”。基本上如果管道两边都按照一定的假设强制执行的话,需要重新配置时就会简单许多,而并不需要时刻监控管道中的各种中间状态。当我们尝试把现在的这种模块拆分的时候,需要考虑的另一个问题是,数据库的结构应该是什么样的?具体的问题还包括数据如何进行维护,其他单元如何缓存等等。毫无疑问在微服务中,数据结构的形态会有很大不同。

使用微服务意味着什么呢?

首先粒度的问题是至关重要的,当你在思考微服务的边界问题时,这应该是使用微服务架构需要做出的一个最重要的决定。在这个过程中需要平衡好内聚和耦合的关系,一方面为了加强内聚你可能需要将某一些服务合并到一起,另外一方面为了解耦,需要对某些服务进行拆分。事实上在使用微服务架构的整个过程中都充斥着来回往复的拆分和合并,直到你真的理解了服务的边界应该是什么为止。我们后面会继续回到这个问题上。

微服务中的各个服务个体是可以独立扩展的,也就是说如果某个服务上的需求量猛增,我们只需要扩展该服务即可,于其他服务无关。相应的,对于各个服务的状态监控也非常重要。如果你手头是一个单块,从定义来看,理论上你需要监控的只有一个东西。而到了微服务里面,因为我们有大量的小型服务,因此需要对所有这些服务进行监控才行,你需要清楚地知道每一个服务是不是在工作,是不是在正常工作,为此光监控就会需要更多的机器。

在微服务中,我们还需要考虑服务挂掉的情况,而这些情况在传统单块架构里面一般不用怎么考虑。比如我们有一块业务需要依赖数个独立的微服务,那么我们需要考虑的失败场景就相当多了,基本上是把所有这些可能的失败进行排列组合,而不得不说的一点是,在传统单块架构里面这些可能根本就不成之为一个问题。

在微服务架构中,因为我们有很多独立的单元,它们需要独自进行部署,因此我们真心离不开基础设施的自动构建,自动化部署和持续交付这些基础。在微服务中,虽然我们讲在不同的服务中可以使用不同的技术栈,但是这种灵活性如果任其发展最终也会完全不可控,因此我们需要管理好这种灵活性。我之前呆过的一个项目里面,光是XML解析就有11种不同的方法,是的你没听错,11种之多。一般来讲我觉得两种XML解析器我尚能接受,但是11种实在是难以理解。因此在微服务架构中,我们必须要避免引入所有类型的编程语言,引入所有不同种类的数据库,没什么原因,明眼人都知道,我们不能这么做。在系统的技术栈方面,我们必须在某个层面进行控制,否则一发不可收拾了。

当然最终一致性也必须管理好。一旦我们开始将数据分散到各个不同的服务单元里面去,我们必须考虑到这对于信息传播会有什么影响。其中的一个结果就是,我们需要给业务人员讲清楚,什么是最终一致性。一般而言我觉得业务人员会理解成“可能会一致”,而不是最终会达到一致。我们必须时刻想到由于最终一致性的存在所可能产生的各种后果。同样,如果我们用的是一个大而全的关系型数据库的话,压根就不会存在这个问题。

好了,大家看看我们列出来的这些个问题,他们一个比一个复杂,而在传统的单块模型中,这些问题我们根本不用想。当我们回顾这些问题的时候,不由得会想到我们的初衷,如果我们的初衷能够得以实现,最少能弥补解决这些问题时所带来的痛处。我们的初衷是什么呢?让系统能尽可能快的响应变化!另外一个方面,这些问题也在一定程度上警醒着你,微服务这个东西不是你想象的那么简单,不是照着教程做一遍就可以大功告成,然后出门跟人炫耀:哥在搞微服务哦,牛逼吧!

最后有一点差点忘了说了,接口的改动也必须有个限度。在定义清楚服务边界的时候,你少不了要修改一些接口协议,在修改的时候务必想清楚这个修改意味着什么。这个也深受团队成员结构的影响,比如如果写另外一个服务的人就在你旁边,改起来三两句话就搞定了,但是如果团队成员咫尺天涯或者天各一方,这个沟通过程本身就是一个巨大的成本。

好了,以上这些就是我认为的在准备使用微服务的时候,需要想清楚的一些问题。

好了,我们现在有两个选项。第一个是一片绿地,就好比我们拿到一个空白的项目,没有任何的遗留代码。这种情况也有,但是很少。另外还有一点,在马丁即将发表的一篇文章里提到,我们并不建议在一个崭新的项目中一开始就使用微服务进行开发。因为你很可能并不真的理解业务领域,从而也很难理解各个服务的边界。因此在这种项目中,可以先做成单块的,进行适当的模块化,加深理解之后再考虑演进成微服务。总而言之,你迟早会走到这个点,就是我们怎么将一个单块系统拆解成微服务架构的。

首先,想清楚边界上下文。这其实是从领域驱动设计一书中拿过来的概念。所谓的边界,指的是你可以将某些部分用一个圈圈起来,圈里的内容代表了一定的业务面。这个所谓的边界其实就是我们设计服务边界时的一个重要指示。再回到我之前说的那个问题,你需要对你的系统有一个全面透彻的认知之后,才能做出这些决定。作为划分服务边界的第一个提示便是:尝试按照领域驱动设计的方式来思考服务的边界应该在哪里。

其次还是这一点:从业务能力的角度来思考服务的边界。想想系统所提供的产品、服务是什么,从这个角度想想怎么样才算一个合理的服务边界。

想想调用者需要什么。与提供方驱动的方式不同的是,你需要想想这个服务的使用者会需要些什么信息。也就是说需要从真实的需求开始想问题,而不是一些作为提供方揣测出来的需求。

想想服务之间的通信模式。想想哪些服务,哪些系统可能会用到同一组数据,不同的服务之间怎么合作来实现一个特殊的业务产出。特别是当我们在犹豫是要将两个服务分开还是合并到一块的时候,想想拆开之后可能产生的各种通信,以及通信失败的情形等等。

想想数据结构。一般而言,大家不愿意花太多精力想怎么保护数据的问题,也不会纠结太多最终一致性产生的影响。你怎么设计数据,合适的数据结构应该是什么样的也可以作为一个指示你如何设计服务边界的提示。因为服务是持有数据的。我们不再想的是一个数据库周围围绕着一堆服务,而是每一个服务有一个自己的持久化存储。

想想联动变化模式。比如有两个业务部分需要一起改,其实某种程度上在提示你,是不是把这两块放到一个服务里面更加合适。因此多想想未来可能产生的各种改动,对你做出决定也会有所帮助。

做好出错的准备,你可能经常需要将一些服务进行合并,而后搞不好又要重新再拆开。由于服务还牵扯到数据,如果两个服务的关系是一个服务主要负责一部分数据,而另外一个服务中对这部分数据只是进行缓存的话,你或许还要考虑一些需要数据迁移的情况。一般而言你不会想经常修改服务的边界,因为这会带来一系列的应用程序,数据的改动。但是还是那句话,做最坏的打算。

最后的一点是:做一名有耐心的读者。这其实扯到演进架构的内容了。首先是我们不能把build搞挂了。我们能做的是只有在没有别的选择的时候,我们才开始改。而做耐心的读者就是为了确保他们改的确实是你所需要的。也就是说在正式确定修改我们实现之前,需求本身可能改的还要多一些。

OK,那么微服务与演进式架构还有什么联系呢?其实当我们谈论演进式架构的时候,其实是有很多不同的原则的。这些原则都很严格,而微服务某种程度上展现出了一些演进式架构的原则。我们在谈微服务的时候,主要是想用它来快速应对频繁的需求变更,因此微服务所承载的期望便是让我们能尽可能快的适应变化。而演进式架构中所倡导的进化性与此不谋而合。可进化型并不同于可维护性,但也不是说要怎么预测未来,而是一种随时准备响应变化的状态,而不管你是否提前就设想好了这些变化。以前关于可进化性的一个理解误区是,我要非常聪明的想到所有可能出现的变化,并且为所有的这些可能的变化写好代码,哪怕到头来根本就没变。因此微服务将可进化性作为其首要目标。

耐心的读者,是的前面已经提到过一遍了。我们可以做出任何改变,但这种改变必须是我们经过大量的沟通讨论之后得出的统一结论。

遵从康威法则。我们需要关注怎么组织团队结构,以及不同的团队结构可能影响到最终的系统形态。只有团队对服务有很好的所有权意识,团队做出来的微服务才是这种松耦合的独立服务。某些采用了微服务的组织中,但是并没有一个专门的支持维护团队,因此每个团队成员会不自觉地将他的那部分代码写好,因为谁也不想大周末的还要跑到公司来修BUG。

适当耦合。我们在谈演进式架构的时候,其实总是在平衡耦合与性能、复杂度等其他因素之间的关系。

协议。在微服务架构中,各个服务可以独立演进。那么必不可少的一环就是相互之间的接口协议。通常而言我们可以借助于一些协议测试工具或者文档来确保相互之间正常工作。作为服务提供方同时还应该提供对应的接口测试给调用方,而一旦调用方发现这个测试失败了,马上需要找到提供方确认问题。测试的另外一重含义是,只要这个全面的接口测试是正常通过的,那么无论提供方把代码改成什么样了,调用一方都不用关心,因为大家都是独立的,链接这彼此的就是这些接口协议,这也是为什么微服务中的接口协议显得如此重要的一个重要原因。

最后,作为一名来自ThoughtWorks的员工,我不得不谈谈测试相关的事情。我们发现如果你始终沿着方便测试的方向设计架构,最后的架构会更加简洁。而且易于测试的系统一定是有一个清楚的定义的,你甚至不需要颗粒度太细的测试,哪怕只有一个非常顶层的端到端测试,但是只要这个测试能简单明了的描述了其行为就已足够。当你的架构设计的很好,边界划分的很清楚的时候,系统本身就应该是很好测试的。我们发现,一般而言,不管是从代码层面还是系统层面,关注测试始终会让你的架构更加完善。而且一般而言,基于测试定义的边界,也让整个架构更容易做出改变。

持续交付在这里扮演什么角色呢?

微服务相比于传统架构,增加了大量的运维负担。有更多的东西需要部署,有更多的地方需要监控,出错的地方自然也成倍增加,有更多的数据库,对应的需要更多的备份。在微服务架构中,出现问题时如何快速恢复也是件很复杂的事,因为服务数量众多,对应的排列组合情况更是数不胜数。

第二点是微服务需要很强的运维文化,这里我用了“不明智”这个词,主要是强调运维文化对于微服务架构的重要性。如果你的开发团队和运维团队之间有堵高墙,那么用微服务实施下去必然是个灾难。这里面有大量的琐事需要做好做对,需要理解清楚。如果没有一种强运维环境,你那个单独的运维团队一定会被你搞疯掉的。

如果不能严格执行持续交付,而选择了微服务也不是一种明智之举。在程序出错的时候,我们需要赶紧进行调试,但是如果你连现在的代码是哪个版本都没法准确知道,你告诉我怎么他喵的调试?所以严格执行持续交付是微服务架构赖以成功之根本。关于持续交付的内容,比如基础设置自动化,自动化部署,自动化测试等等都缺一不可。拿自动化测试举例,与传统架构相比,你的测试场景会更加复杂,甚至就连程序主路径这一个方面都要比原来负责很多,因此测试就显得至关重要了。

我应该从哪儿开始呢?

跟前面讲的一样,我们始终建议在完全透彻的理解业务背景的前提下再尝试使用微服务。在一个崭新的项目上,始终建议从一个单块架构开始,因为你不可避免的会犯一些边界划分的错误,除非你真的是对于这个领域烂熟于胸,倒背如流。在开始的单块架构中,你依然可以让代码保持整洁清晰,有完整的测试覆盖。实施微服务的时候所面临的一个最大问题是,大家容易把它用错了,导致大量没有必要的反复更改,从这个意义上来讲,从一个单件开始不失为一种较为安全的选择。

微服务是一把尚方宝剑吗?

我们说它并不是。我们确实见证了不少使用微服务架构的成功案例,但是就微服务本身还是有很多问题需要考虑。比如我之前提到的最终一致性的问题,错误的排列组合问题等。这些问题也并不是我们就完全不知道怎么解决,我们知道怎么管理开发流程,我们知道该怎么沟通,只是在微服务中要稍微复杂一些而已。但是面对这些问题的时候,不要害怕做出修改。你完全可以重新设计某些部分。我们有严格的接口协议,全面的接口测试覆盖,来保证你在修改业务流程、引入新功能、新的校验方式时更加从容。事实上这些东西也确实容易朝秦暮楚,现在只要谁在社交媒体上秀了个新功能,马上大家一窝蜂的都想要这个新功能了。用户的期待就是改变的这么快,业务生命周期也越来越短,安全相关的问题也是日新月异,所有这些变更都驱使着我们必须快速的适应变化,而微服务便是在这样的大环境下应运而生的,但是微服务并不是免费的蛋糕,也有其自身的成本消耗。

最后给大家推荐一本关于微服务的书,是的你没看错,就是这本《持续集成》。你可以将微服务看成我们开将持续交付的里面引入软件开发行业之后的第一个主要架构。在这本书里面你会看到,如果没有持续交付,微服务中所提到的很多东西你根本就不会朝这个方向想,也更不会想这么做。持续交付和微服务只有组合在一起才能展现出相互的价值,就像我前面所说的,我们需要喊上运维的同事一起来做这个事情,因为在微服务中他们的部署方式,监控方式都会与之前大不相同。

Share

微服务——大企业是如何在实践微服务中成长的

我足够“高”了么?

Martin Fowler描述过当组织在考虑实现微服务架构时,必须要有的一组“能力基线”,但大企业通常有各种理由不太愿意去尝试和成长以达到这样的能力。有很多的文字都提到,在成长之路完成后便能收获微服务架构所能带来的各种好处。这篇文章将描述一个大企业为达到Martin Fowler的能力基线,所经历的挑战和决心。

01
Image by Fabio Pereira

开篇

在行业中,微服务有着众多不同的定义。某些定义认为它是一种和Unix编程风格类似,以构建很多能够专注于做好一件事情的、微小的、有自治进程的架构风格。某些定义认为它是第一个后DevOps革命的架构。它从DevOps和持续交付中吸取到所有的经验,并且对其进行了优化。

我们采用微服务的初衷是为了提供一种能够扩大团队规模的途径。我们从一个单体应用开始,康威定律启迪我们分解这个单体应用。经过这个过程,我们最终变成了很多小而优的团队来进行交付。

团队的结构影响着系统的架构并不是一个新鲜事了。Nygard总结得很到位——“团队的工作分配方式就是架构的初稿”。这种最初对团队伸缩性的需求是将一批最佳实践带入微服务和持续集成的催化剂。

团队的工作分配方式就是架构的初稿。

拆分单体应用

当我们拆分单体应用时,我们面临的第一个挑战是尝试找到系统的接缝或者界限上下文。这种尝试带来了一个非常有意思的副作用,即我们可以通过识别系统中那些比起其他地方更不容易变化的地方,来解耦我们的系统。这和重构代码以让你的代码更经得起时间的考验类似,只不过是在架构层次上罢了。

从单体应用中分割出多个服务是很不简单的。包括创建单独的代码仓库,构建管道和基础设施建设。这些都要消耗时间。因为很多企业都对频繁构建新服务持悲观的态度,我们经常会听到犹豫之声:“如果构建一个服务都用了这么多时间,我们怎么做其他的服务呢?”

我们是在每个迭代中逐步攻克这个问题的。我们改进我们的构建管道、工程的创建、环境设置和部署。这是从微服务架构中所获得的很大一部分益处——它不仅强制性的将质量和成熟度构建进你的软件,而且也包含了开发流程和实践。

步入云端

在改进DevOps最佳实践的路上,配置环境成了畔脚石。如果需要另一个部门花几周的时间,去配置好一个新的机器来托管你的新服务,你就有麻烦了。幸运的是我们已经准备好步入云端,并且已经在企业层面上准备好了支持云部署所必须的工具链。

即便有这些工具,开发人员也必须开始拥有他们的软件,包括生产环境的部署和支持。封闭的组织有一个趋势是将他们的开发人员与真实地生产环境隔离开。“谁构建,谁拥有”这个思想非常的重要,它能促进最佳实践地成熟。亦即,如果开发人员已经对生产环境的稳定性和支持有浓厚的兴趣,那么很有可能是他们构建了这个系统。

谁构建,谁拥有

消费者驱动的契约测试

我们已经拥有了很多已经部署到云端的RESTFul API,并且由团队自己维护这些API。这就意味着团队可以不借助其他人的力量进行独立的开发。不过,我们开始发现依赖的服务在逐渐改变我们的行为。测试和利益相关者开始抱怨系统不稳定——但当我们是单体应用时这根本不是问题。

我们使用集成测试来解决这个问题。服务提供者为其API的消费者维护一组测试,而不是消费者自己为契约写集成测试。这样做有很多的好处:首先,如果服务提供者破坏了契约,它会直接被告知;另外,如果消费者增加了新的测试场景,但这个场景破坏了契约,那么构建也同样会失败。这些考量驱动出更重要的事情,也即团队之间的交流以及尽快的开始这些对话。API始终可以进化,但它会有足够的约束来保证稳定性。

蝴蝶效应

解耦单体应用的另一个副作用是监控的工作量成倍增加。之前你可能在一个应用中只有一个终端(endpoint),而现在你有很多的服务。为了更好地保证应用正常工作,你必须要为支撑你的服务的基础设置上提供足够的监控。

在单体应用中,如果一个服务调用失败了,我们会清楚地知道哪里出错了。但在一个经常变化的解耦架构中不再成立。当一个服务失败了,真实的失败位置可能是在依赖树上的数个服务。这就意味着平均修复时间(MTTR)会受影响——我们必须要调查受影响的服务,也会调查这个服务所有的依赖。我们使用PRTG监控工具来解决这个问题。PRTG给我们提供了一个高级的仪表盘(dashboard)来图形化系统的不同的部件。

我们发现的另外一种必要地监控是分布式的日志收集。这允许我们从不同的服务器收集日志并且可以做联合查询。我们甚至可以引入相关性ID来改进它。这样分布式的日志允许我们跟踪一个请求的在系统的不同服务中的跳转过程。

展望

当然我们还有一些需要在未来解决的困难问题。包括一些棘手的组织变化,例如将交付与产品对齐而不是和工程对齐,并且使开发人员能够在DevOps的环境中工作。这也包含了更多的环境配置,包括更多的通过Ansible来进行基础设施自动化。尽管还有未来的挑战,我们已经开始收获一些引入微服务先决条件所带来的好处了。你的微服务的旅程是怎样的呢?

Share

基于微服务架构,改造企业核心系统之实践

1. 背景与挑战

随着公司国际化战略的推行以及本土业务的高速发展,后台支撑系统已经不堪重负。在吞吐量、稳定性以及可扩展性上都无法满足日益增长的业务需求。对于每10万元额度的合同,从销售团队准备材料、与客户签单、递交给合同部门,再到合同生效大概需要3.5人天。随着业务量的快速增长,签订合同的成本急剧增加。

合同管理系统是后台支撑系统中重要的一部分。当前的合同系统是5年前使用.NET基于SAGE CRM二次开发的产品。 一方面,系统架构过于陈旧,性能、可靠性无法满足现有的需求。另一方面,功能繁杂,结构混乱,定制的代码与SAGE CRM系统耦合度极高。由于是遗留系统,熟悉该代码的人早已离职多时,新团队对其望而却步,只能做些周边的修补工作。同时,还要承担着边补测试,边整理逻辑的工作。

1

在无法中断业务处理的情况下,为了解决当前面临的问题,团队制定了如下的策略:

  1. 在现有合同管理系统的外围,构建功能服务接口,将系统核心的功能分离出来。
  2. 利用这些功能服务接口作为代理,解耦原合同系统与其调用者之间的依赖;
  3. 通过不断构建功能服务接口,逐渐将原有系统分解成多个独立的服务。
  4. 摒弃原有的合同管理系统,使用全新构建的(微)服务接口替代。

2. 什么是微服务

多年来,我们一直在技术的浪潮中不断乘风破浪,扬帆奋进,寻找更好的方式构建IT系统。微服务架构(Micro Service Architect)是近一段时间在软件体系架构领域里出现的一个新名词。它通过将功能分解到多个独立的服务,以实现对解决方案或者复杂系统的解耦。

微服务的诞生并非偶然: 领域驱动设计指导我们如何分析并模型化复杂的业务;敏捷方法论帮助我们消除浪费,快速反馈;持续交付促使我们构建更快、更可靠、更频繁的软件部署和交付能力;虚拟化和基础设施自动化( Infrastructure As Code)则帮助我们简化环境的创建、安装;DevOps文化的流行以及特性团队的出现,使得小团队更加全功能化。这些都是推动微服务诞生的重要因素。

实际上,微服务本身并没有一个严格的定义。不过从业界的讨论来看,微服务通常有如下几个特征:

小,且专注于做一件事情

每个服务都是很小的应用,至于有多小,是一个非常有趣的话题。有人喜欢100行以内,有人赞成1000行以内。数字并不是最重要的。仁者见仁,智者见智,只要团队觉得合适就好。只关注一个业务功能,这一点和我们平常谈论的面向对象原则中的”单一职责原则”类似,每个服务只做一件事情,并且把它做好。

运行在独立的进程中

每个服务都运行在一个独立的操作系统进程中,这意味着不同的服务能被部署到不同的主机上。

轻量级的通信机制

服务和服务之间通过轻量级的机制实现彼此间的通信。所谓轻量级通信机制,通常指基于语言无关、平台无关的这类协议,例如XML、JSON,而不是传统我们熟知的Java RMI或者.Net Remoting等。

松耦合

不需要改变依赖,只更改当前服务本身,就可以独立部署。这意味着该服务和其他服务之间在部署和运行上呈现相互独立的状态。

综上所述,微服务架构采用多个服务间互相协作的方式构建传统应用。每个服务独立运行在不同的进程中,服务与服务之间通过轻量的通讯机制交互,并且每个服务可以通过自动化部署方式独立部署。

3.微服务的优势

相比传统的单块架构系统(monolithic),微服务在如下诸多方面有着显著的优势:

异构性

问题有其具体性,解决方案也应有其针对性。用最适合的技术方案去解决具体的问题,往往会事半功倍。传统的单块架构系统倾向采用统一的技术平台或方案来解决所有问题。而微服务的异构性,可以针对不同的业务特征选择不同的技术方案,有针对性的解决具体的业务问题。

对于单块架构的系统,初始的技术选型严重限制将来采用不同语言或框架的能力。如果想尝试新的编程语言或者框架,没有完备的功能测试集,很难平滑的完成替换,而且系统规模越大,风险越高。基于微服务架构,使我们更容易在遗留系统上尝试新的技术或解决方案。譬如说,可以先挑选风险最小的服务作为尝试,快速得到反馈后再决定是否试用于其他服务。这也意味着,即便对一项新技术的尝试失败,也可以抛弃这个方案,并不会对整个产品带来风险。

2

该图引用自Martin Fowler的Microservices一文

独立测试与部署

单块架构系统运行在一个进程中,因此系统中任何程序的改变,都需要对整个系统重新测试并部署。 而对于微服务架构而言,不同服务之间的打包、测试或者部署等,与其它服务都是完全独立的。对某个服务所做的改动,只需要关注该服务本身。从这个角度来说,使用微服务后,代码修改、测试、打包以及部署的成本和风险都比单块架构系统降低很多。

按需伸缩

单块架构系统由于单进程的局限性,水平扩展时只能基于整个系统进行扩展,无法针对某一个功能模块按需扩展。 而服务架构则可以完美地解决伸缩性的扩展问题。系统可以根据需要,实施细粒度的自由扩展。

错误隔离性

微服务架构同时也能提升故障的隔离性。例如,如果某个服务的内存泄露,只会影响自己,其他服务能够继续正常地工作。与之形成对比的是,单块架构中如果有一个不合格的组件发生异常,有可能会拖垮整个系统。

团队全功能化

康威定律(Conway’s law)指出:一个组织的设计成果,其结构往往对应于这个组织中的沟通结构(organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations)。传统的开发模式在分工时往往以技术为单位,比如UI团队、服务端团队和数据库团队,这样的分工可能会导致任何功能上的改变都需要跨团队沟通和协调。而微服务则倡导围绕服务来分工,团队需要具备服务设计、开发、测试到部署所需的所有技能。

4. 微服务快速开发实践

随着团队对业务的理解加深和对微服务实践的尝试,数个微服务程序已经成功构建出来。不过,问题同时也出现了:对于这些不同的微服务程序而言,虽然具体实现的代码细节不同,但其结构、开发方式、持续集成环境、测试策略、部署机制以及监控和告警等,都有着类似的实现方式。那么如何满足DRY原则并消除浪费呢?带着这个问题,经过团队的努力,Stencil诞生了。 Stencil是一个帮助快速构建Ruby微服务应用的开发框架,主要包括四部分:Stencil模板、代码生成工具,持续集成模板以及一键部署工具。

3

Stencil模板

Stencil模板是一个独立的Ruby代码工程库,主要包括代码模板以及一组配置文件模板。

代码模板使用Webmachine作为Web框架,RESTful和JSON构建服务之间的通信方式,RSpec作为测试框架。同时,代码模板还定义了一组Rake任务,譬如运行测试,查看测试报告,将当前的微服务生成RPM包,使用Koji给RPM包打标签等。

除此之外,该模板也提供了一组通用的URL,帮助使用者查看微服务的当前版本、配置信息以及检测该微服务程序是否健康运行等。

[
    {
        rel: "index",
        path: "/diagnostic/"
    },
    {
        rel: "version",
        path: "/diagnostic/version"
    },
    {
        rel: "config",
        path: "/diagnostic/config"
    },
    {
        rel: "hostname",
        path: "/diagnostic/hostname"
    },
    {
        rel: "heartbeat",
        path: "/diagnostic/status/heartbeat"
    },
    {
        rel: "nagios",
        path: "/diagnostic/status/nagios"
    }
]

配置文件模板主要包括NewRelic配置,Passenger配置、Nagios配置、Apache配置以及Splunk配置。通过定义这些配置文件模板,当把新的微服务程序部署到验收环境或者产品环境时,我们立刻就可以使用Nagios、NewRelic以及Splunk等第三方服务提供的功能,帮助我们有效的监控微服务,并在超过初始阈值时获得告警。

代码生成工具

借助Stencil代码生成工具,我们能在很短时间内就构建出一个可以立即运行的微服务应用程序。随着系统越来越复杂,微服务程序的不断增多,Stencil模板和代码生成工具帮助我们大大简化了创建微服务的流程,让开发人员更关注如何实现业务逻辑并快速验证。

Create a project from the stencil template (version 0.1.27)
        --name, -n <s>:   New project name. eg. things-and-stuff
        --git-owner, -g <s>:   Git owner (default: which team or owner)
        --database, -d:   Include database connection code
        --triggered-task, -t:   Include triggered task code
        --provider, -p:   Is it a service provider? (other services use this service)
        --consumer, -c:   Is it a service consumer? (it uses other services)
        --branch, -b <s>:   Specify a particular branch of Stencil
        --face-palm, -f:   Overide name validation 
        --help, -h:   Show this message

如上代码所示,通过指定不同参数,我们能创建具有数据库访问能力的微服务程序,或者是包含异步队列处理的微服务程序。同时,我们也可以标记该服务是数据消费者还是数据生产者,能帮助我们理解多个微服务之间的联系。

持续集成模板

基于持续集成服务器Bamboo,团队创建了针对Stencil的持续集成模板工程,并定义了三个主要阶段:

  • 打包:运行单元测试,集成测试,等待测试通过后生成RPM包。
  • 发布:将RPM包发布到Koji服务器上,并打上相应的Tag。然后使用Packer在亚马逊 AWS云环境中创建AMI,建好的AMI上已经安装了当前微服务程序的最新RPM包。
  • 部署:基于指定版本的AMI,将应用快速部署到验收环境或者产品环境上。

利用持续集成模板工程,团队仅需花费很少的时间,就可以针对新建的微应用程序,在Bamboo上快速定义其对应的持续集成环境。

一键部署工具

所有的微服务程序都部署并运行在亚马逊AWS云环境上。同时,我们使用Asgard对AWS云环境中的资源进行创建、部署和管理。 Asgard是一套功能强大的基于Web的AWS云部署和管理工具,由Netflix采用Groovy on Grails开发,其主要优点有:

  • 基于B/S的AWS部署及管理工具,使用户能通过浏览器直接访问AWS云资源,无需设置Secret Key和Access Key;
  • 定义了Application以及Cluster等逻辑概念,更清晰、有效地描述了应用程序在AWS云环境中对应的部署拓扑结构。
  • 在对应用的部署操作中,集成了AWS Elastic Load Balancer、AWS EC2以及AWS Autoscaling Group,并将这些资源自动关联起来。
  • 提供RESTful接口,能够方便地与其他系统集成。
  • 简洁易用的用户接口,提供可视化的方式完成一键部署以及流量切换。

由于Asgard对RESTful的良好支持,团队实现了一套基于Asgard的命令行部署工具,只需如下一条命令,提供应用程序的名称以及版本号,就可自动完成资源的创建、部署、流量切换、删除旧的应用等操作。

asgard-deploy [AppName] [AppVersion]

同时,基于命令行的部署工具,也可以很容易的将自动化部署集成到Bamboo持续集成环境。

通过使用微服务框架Stencil,大大缩短了团队开发微服务的周期。同时,基于Stencil,我们定义了一套团队内部的开发流程,帮助团队的每一位成员理解并快速构建微服务。

微服务架构下的新系统

经过5个月的努力,我们重新构建了合同管理系统,将之前的产品、价格、销售人员、合同签署、合同审查以及PDF生成都定义成了独立的服务接口。相比之前大而全、难以维护的合同管理系统而言,新的系统由不同功能的微服务组成,每个微服务程序只关注单一的功能。每个微服务应用都有相关的负责人,通过使用Page Duty建立消息通知机制。每当有监控出现告警的时候,责任人能立即收到消息并快速做出响应。

4

由于微服务具有高内聚,低耦合的特点,每个应用都是一个独立的个体。当出现问题时,很容易定位问题并解决问题,大大缩短了修复缺陷的周期。另外,通过使用不同功能的微服务接口提供数据,用户接口(UI)部分变成了一个非常简洁、轻量级的应用,更关注如何渲染页面以及表单提交等交互功能。

总结

通过使用微服务架构,在不影响现有业务运转的情况下,我们有效的将遗留的大系统逐渐分解成不同功能的微服务接口。同时,通过Stencil微服务开发框架,我们能够快速地构建不同功能的微服务接口,并能方便地将其部署到验收环境或者产品环境。最后,得益于微服务架构的灵活性以及扩展性,使得我们能够快速构建低耦合、易扩展、易伸缩性的应用系统。

参考文献

  1. http://martinfowler.com/articles/microservices.html
  2. http://jaxenter.com/cracking-microservices-practices-50692.html
  3. http://microservices.io/patterns/microservices.html
Share