微服务化小团队集群的组织和管理

在组织管理方面,越来越多的挑战被识别出来,把“自组织”当做银弹来回答这样集群小团队组织和管理中的问题是行业里存在的一个不好趋势。在最近和Matin Fowler的讨论中,我们达成共识的一点是:与其说微服务是一种技术架构,还不如说是一种企业组织架构。

希望通过本文与大家一起研讨这样服务化小团队集群的组织和管理方法。

随着微服务架构风格的流行,组织内部不可避免的产生了许多小规模团队,原来一个几十上百人的产品团队被拆分成了类似Amazon这样的2 pizza(6~10人)小团队。组织结构上也由之前的层级化职能团队设置变成了扁平的小团队集群。每个做这样调整的企业都希望借助小团队的灵活性在这个科技时代跟上市场变化和创新的脚步。

(组织的两种基本模式示意)

当然这样的组织方式本身就带来了一系列的挑战,技术实践方面Martin Fowler已经通过微服务的定义文章做了很形象的叙述,还用了“你必须长这么高”(You must be this tall!)来比喻在技术实践方面所需做出的投入。

在组织管理方面,越来越多的挑战被识别出来,把“自组织”当做银弹来回答这样集群小团队组织和管理中的问题是行业里存在的一个不好趋势。在最近和Matin Fowler的讨论中,我们达成共识的一点是:与其说微服务是一种技术架构,还不如说是一种企业组织架构

希望通过本文与大家一起研讨这样服务化小团队集群的组织和管理方法。

组织原则:对齐业务

科技时代带来的最大变化就是对变化本身的认知。在上一个时代,变化被认为是高成本甚至有害的,大家总是希望能够尽量减少变化,或者能够循序渐进地变化;而现在我们会认为变化是必然的,是会给我们带来新的市场机会的,甚至是会引发颠覆式创新的。

由于这样的认知变化,企业必须具备相应的灵活性,而扁平的小团队集群组织结构被不少企业(例如Google和韩都衣舍)证明是提供这种灵活性的有效组织方式。很多企业争相效仿,但不少会发现并没有得到之前追求的灵活性,反而做事情更为复杂了,以前一个接口部门变成了现在的多个团队。

作为咨询顾问,这两年经常接触到不少人抱怨划分成小团队后需求的实现成本增加很多,以前简单的业务需求现在要分解到很多“服务”团队才能完成。有的团队觉得这是技术问题 – 架构设计前瞻性不够;有的团队觉得是管理问题 – 没有跨团队的协调机制。而我往往喜欢从组织入手,让团队从业务需求出发,分析为什么那么多“集成”。

熟悉康威定律的同学可能已经理解我的用意了,以上团队出现的问题核心症结在于团队的组织结构和市场业务没有对应关系,大量的服务集成等同于把市场需求“翻译”适配到已有的组织结构。这样的痛苦在过去职能组织结构的时候就广泛存在,而错误的“服务化”只是把这种痛苦转换成了小团队之间的集成。

所以微服务化组织结构的核心原则是: 小团队的组织结构应该和市场业务对齐,并跟随市场业务变化而改变

稍微实例化一下以上的原则,比如最近一个客户的电商团队,100人左右规模。开始时候划分成了10人上下的小团队,每个团队负责几个服务,其中有几个“重要”服务,如customer(客户)和catalog(目录)。在一段时间里,几乎所有的需求都要经过这两个服务,工作量很大,团队实际上也根本不是十几人,而是几十人。团队告诉我:撑过这段高峰期模型就稳定了,以后就可以真正“微服务化”了(很多同学可能都有同样的幻想~)。

我用领域驱动设计的思想很快指出,就现在业务需求驱动出来的大量集成已经可以明确这些“重要”服务的划分是有问题的,新增加电子产品(比如点卡)的customer和之前的电商customer已经有不同的含义了,电子产品要求客户提供对应电子平台的账号和授权。按照上面的原则,不应该继续去扩充已有的customer服务,而应该重新分析新的业务模式下是否应该有新的服务(其中也包含新的customer模型)。

(不同的customer概念)

以上的例子在很多走向服务化组织结构的企业里比比皆是,而遇到问题时大家更容易从看似明确的技术或管理手段入手。这里希望读到此文的各位在遇到这样的挑战时别忘了康威定律。 管理原则:适应不确定性

《大教堂与集市》这本书中对比了两种不同的组织架构模式,很多特质也能够类比到单体架构和微服务架构之上。当微服务架构落地形成生态时(如Google),好比一个繁荣的集市,中央管理固然重要,但各个经营摊位却自主在为客户提供着琳琅满目的商品和服务。

作为一个企业,不太可能完全容忍集市一样的市场化,当然也有企业如海尔,把这种市场化更大程度上地引入到了内部。假设刚刚从过去职能层级组织结构转换到扁平小团队集群结构,这个时候管理者最大挑战就是如何还能够获取过去一样的“全面”信息。管理者往往会告诉我他们感觉到随时可能失控,因为每个团队都没有统一的开发方法和流程。

很不幸的是大多数从MBA课堂走出的企业管理者们都是管理“大教堂”建设的高手,却很难驾驭集市带来的“混乱”。然而我们的管理者们又都渴望着自己的企业能够有创新的环境。根据创新理论(Edge of Chaos),集市混乱产生创新的可能要远大于大教堂的整齐划一。那么在这个你不创新就被别人创新颠覆的时代,管理者就必须正视这个挑战。

管理的原则应该改变为: 放弃对掌控全局的虚幻追求,拥抱不确定性带来的挑战和机遇

同样实例化一下上面的原则,一个300多人的大产品经理与我谈他的困惑,自从转型成为扁平小团队结构后,他的会议比之前多了N倍,基本每天正常工作时间全部在开会。自己的产品方向和运营只能靠夜深人静的时候加班。他告诉我如果这就是所谓小团队要付出的代价,那么他觉得这事儿没法持久。

我首先肯定了他的观点,即如果作为产品经理都没有时间看产品方向和运营,那么模式上肯定是错了。进而询问最占用他时间的会议需要他做什么?很有意思的是他的答案是“我需要知道xxx信息才能保证计划的拉通”、“开会已经同步各个小团队最高效的方式了”… 这里表达出来的行为全部是希望掌控全局的急迫,和他嘴巴里说出的赋权团队、打造自组织文化大相径庭,当然知行合一本身就是困难的。于是我们花了很多时间来讨论为什么一定要有拉通的计划、为什么要中央同步各个小团队 … 这位管理者最后带着更多的困惑离开了那次讨论。

对比组织结构的调整,管理者思想和方法的转型确实任重道远。我往往给出的建议是从小处着手,比如放弃给每人安排任务,让大家自己来选择;又比如在团队之间冲突时,放弃作为管理者的“拍板权”,让团队通过快速实验来验证哪种方式更好。另一位大师级同事Jim Highsmith在八年前就总结出了适应性领导力(Adaptive Leadership),十年后的今天仍是少有管理者能够真正践行。

合作原则:简化集成关系

微服务下小团队集群的结构不可避免的需要更多的团队合作。一个运作良好的微服务生态圈背后是一个紧密协作的小团队网络,而团队之间的合作就是这个网络里无形的手。合作很多时候是团队和团队在运营过程中实时发生的,并非是预先设定或中央控制的,这个时候如何高效合作就成了一个很大的挑战。

在共同满足业务需求的过程中,大多数的合作是由于实现过程中服务之间的集成关系产生的。如前面讨论组织结构时的案例,很多组织在转型后反而集成多了,造成交流沟通成本持续增高,最后不得不安装很多会议和流程来“协调”这样的合作。结果当然是响应力越来越差,小团队名存实亡。

在团队合作方面DDD(领域驱动设计)原书中提出的(业务)限界上下文(Bounded Context)的关联关系可以借鉴。服务划分强调从业务视角出发,限界上下文提供了很好的划分指导,即可以根据限界上下文来设计相关的服务边界。由于每个服务对应一个小团队,那么实际上我们就建立了团队之间集成关系的模式。Eric Evans用了一个子章节描述了不同的基于领域模型耦合方式的关系模式(248页14.4),并零散地叙述了几个模式的演进场景,很多读者可能都不会特别关注那个章节。然而在微服务开始落地实施的时候,这些模式就变得十分重要了,因为这些模式本身也是团队之间的合作模式。

(限界上下文关系模式要求矩阵)

Eric Evans通过上面的矩阵总结告诉我们集成是高成本的,要尽量避免集成。同样道理在咱们团队合作的过程中,原则也是简化集成关系。在微服务架构下,显然右上角的“大泥球”(Single Bounded Context)和“共享内核”(Shared Kernel)是不推荐的;“独立自主”(Separate Ways)这种完全松耦合的关系是值得我们考虑的,但大多数情况下小颗粒度的服务之间还是会被不同的业务需求串联起来。

很多刚转型的企业会出现不少的“用户/供应商”(Customer/Suppier Teams)模式,主要是团队之间关系还是由更高层的领导在指挥,作为一个演进的合作关系应该更进一步向“开放主机服务”(Open Host Service)模式推进。现代的服务多是以API的形式更为规范的对外提供接口,所以其实在团队沟通方面的成本并非比领导中央决策高多少。

随着API规范的更进一步发展(如采用RESTful架构),很多时候服务团队之间只用做简单的翻译或遵从提供者的模型即可,耦合程度进一步下降,这个时候左下角的另外两种模式“防护层”(Anticorpution Layer)和“跟随者”(Conformist)就更为常见了。显然这两种模式集成关系更为简单,合作过程中调用服务的团队是不需要知道提供方的内部模型细节的,当然这个时候团队之间的信任关系也应该是相当高的。

服务化转型小结

采用微服务架构后自然会产生小团队扁平化组织结构,我们应该意识到在组织、管理、合作的原则及思维观念上都应该发生转变,忽略任何一个方面最终都会造成转型的失败。

  • 在组织结构上,团队划分要面向业务能力,持续提供市场价值。
  • 在企业管理上,管理者要拥抱不确定性,持续提升管理适应力。
  • 在团队合作上,考虑跨团队领域模型的耦合,持续简化集成关系。

更多精彩洞见,请关注微信公众号:思特沃克

Share

认识微服务——一颗银弹

如今微服务架构正逐渐演变成一种主流的架构风格,那么微服务架构是一颗银弹吗?我们提倡微服务架构的目的是什么?

微服务架构是一颗银弹吗?

如今微服务架构正逐渐演变成一种主流的架构风格,那么微服务架构是一颗银弹吗?我们提倡微服务架构的目的是什么?

1987 IBM大型机之父Fred Brooks在《没有银弹:软件工程的本质性与附属性工作》中提出软件工程包括本质性工作和附属性工作。本质性工作是创造出一种由抽象的软件实体所组成的复杂概念结构;附属性工作是用程序语言来表达抽象概念,并在空间和时间的限制下,翻译成机器语言。《没有银弹》主张并断言在未来的十年之内(从1986年文章发表后开始计算),不会有任何单一软件工程上的突破,能够让程序开发的生产力得到一个数量级(10倍)的提升。

我们讨论或推广一项软件实践或技术的时候,实际上是在谈如何提高生产力。本文试图利用《没有银弹》对本质性工作四个原因的归类,去认识微服务架构的生产力。《没有银弹》认为本质性工作大部分的活动是发生在人们的脑海里,具有四大难题:复杂性、隐匿性、配合性和易变性。

软件工程的本质性难题

1、复杂性

软件要解决的问题通常牵扯到多种元素和计算步骤,这是一种人为的、抽象的智能活动,多半是复杂的。随着“软件吞噬世界”不断深入,软件所对应的社会活动越来越多,也越来越复杂。

单体架构系统的困境

系统往往是承载的业务越成功,越容易失败。因为系统会随着业务的发展,增加越来越多的特性和功能,使得系统复杂到没有一个人能全面理解,没有一个人敢去修改原有的功能或代码。

微服务的救赎

微服务提出以业务边界作为模块划分原则,每个模块独立进程。一个业务由很多独立的小业务组合而成,系统也是由独立的小系统组合而成,这样的好处是每个小系统都很容易被理解,一个大系统可以根据业务组合微服务,或者逐渐发展独立的微服务。

微服务救赎的代价

但是微服务架构真的降低了系统的复杂性吗? 其实并没有, 而且是增加了系统的总体复杂性!

微服务之间的连接更加复杂,网络通讯不可靠和性能损耗,协议匹配,接口对接和转换,版本协作,微服务注册和发现,编排和调度,分布式业务和数据一致性等等复杂性都是单体架构不需要考虑的。

微服务为了降低单个微服务的复杂性,导致整体系统的复杂性急剧增加。

如果单体架构根据业务进行模块划分,每个模块之间根据宿主语言接口进行交互,就像OSGi那样模块化/插件架构完全可以做到模块化开发。

那么为了降低系统复杂性,是不是放弃使用微服务? 答案既是肯定也是否定。

我们刚才发现微服务架构带来的复杂性,大多是附属性问题。对于本质性复杂性,微服务使用上下文界限实践,可以更好的隔离依赖业务概念的复杂性,从而降低单个微服务的复杂性。

利弊权衡

既然有得有失,我们是否需要做个权衡?

《没有银弹》的主张只说对了一半,10年内对于本质性问题确实是不会有十倍生产力的提高。但历史上来看,附属性问题是有十倍生产力提高的。 而我们在软件工程实践中,往往本质性问题和附属行问题混合在一起,我们需要不断的加深认识,区分两者,并相互改进。

微服务架构带来的附属性复杂性必须要有一个功能齐全的PaaS进行转移。

微服务推广初期(包括现在)所面临的很多问题,其实是缺乏一个功能齐全的PaaS,比如AWS/Azue,Rancher/OpenShrift,用以解决附属性问题。

所以我们不需要做太多的利弊权衡,因为PaaS日趋成熟,成本将降到几乎可以忽略不计。微服务架构利用PaaS对附属性问题的转移,降低一点点本质性问题的复杂性,也是非常价值的。只是发挥这种价值,需要掌握DDD设计实践,利用好上下文界限来降低单个微服务的本质性复杂性。

2、隐匿性

软件在没有应用到业务之前,各种信息和思考大多在每个人的脑海里,很难完全呈现和想象出来。客户只是模糊的知道自己想要什么,但并不知道要怎样的软件;项目经理有把握软件开发进度的能力,但不知道软件每个阶段后能长成什么样;业务分析员能把业务分析清楚但不能确认转变成最终软件会成为什么样;开发设计人员知道怎么做软件,但又不能完全理解业务需求。

单体架构系统的困境

单体架构中的各个模块隐藏在大系统的页面下,在交付之前对外界是不可见的。 更麻烦是单体架构往往是按技术维度进行分层设计,导致没有人能看清全貌,各自都想把事情做到最好,但组合起来却不是客户想要的东西。

微服务的救赎

微服务强调小、独立业务价值,独立进程独立部署交付,使软件尽快尽早得交付到最终用户手中,来交付和验证业务价值。

微服务架构并没有改变软件开发过程中的隐匿性,而是通过缩短从需求到交付这段软件开发周期,减少隐匿时间,来降低软件工程总体的隐匿性。

利弊权衡

听起来快速交付很好,但会有什么问题吗?

组织必须要具备自动部署持续交付能力。假如一个系统上线需要3个小时进行部署,如果我们要持续部署,每天都部署一次,那就需要每天拿出3小时做部署,我想这个成本是不能接受的。一个全自动化的持续部署平台是必须的,而且还需要保证每次交付都是高质量的,就需要做到全流程内建质量。

这里就引出另外一个问题,由于要求快速交付,就需要打通或模糊软件开发过程的需求、设计、实现、测试、验收、运维、运营等环节,对照以往按照单体架构组织的软件开发流程会发现打通这些过程是有生产力损耗的。比如可以专门做需求分析一个月,不用考虑开发实现,这样更加专注在需求分析当然效率更高了,我们为什么要拉通需求、开发、测试,使得各个环节不专注,生产效率反而下降呢?

3、配合性

在大型软件环境中,各子系统的接口必须协同一致。考虑到时间和环境的演变,要维持这样的一致性通常十分困难。

单体架构系统的困境

一个系统在最初开发的时候往往是最舒服的,虽然没有现成的可重用组件,但也没有沉重的历史包袱。假如系统从零开始做的话,头三个月开发会比较慢,因为需要搭建和熟悉一些开发、测试、部署基础设施,随着基础设施和公共组件的完善,接下来的半年到一年开发会加速,但是再往后开发速度又会逐渐降低。因为那些一开始提高开发效率的接口、共享表、依赖组件都变成了复杂网络缠绕在一起,变成了所谓的牵一发而动全身,改一行代码都不知道会影响到什么地方。重构也变得非常困难,因为需要相互配合的地方太多了,协同成本极速占据开发成本的比例。

微服务的救赎

既然一个系统在初步开发、规模不大的时候生产效率最高,要想使得生产效率维持在一个较高的水平,那就保持系统总是很小。这样因为系统本身很小,微服务系统的配合性问题总是在一个可控的范围内。比如微服务独立数据库表结构,那我们根据业务需要改表结构的时候就不需要去考虑会不会影响到其他业务,因为其他业务和这个表结构完全没关系。

我们区别看待微服务对外和对内的配合。刚才讨论的单体架构配合性问题都是对内的问题,但微服务架构因为把系统做小,从而把一些原本对内的配合问题变成对外的配合了,极大增加了对外配合性问题。

然而单纯从接口协同一致上来说,微服务架构非常糟糕。 单体架构的接口之间配合是相同的编程语言,基本上在编译时就能发现错误。而微服务的接口往往是远程服务,在开发时没法对齐。

利弊权衡

微服务控制了对内配合性问题,增加了对外配合性。版本依赖怎么管理、公共组件怎么共享、事务一致性如何协调都是让人纠结的问题。所以微服务的划分就变得非常重要,总体的指导思想是减少对外配合,把复杂的配合性问题留在微服务内部。

4、易变性

软件所应用的环境常是由人群、法规、硬件设备、应用领域等各因素汇集而成,而这些因素皆会快速变化。

单体架构系统的困境

单体架构往往需要将所有的模块都整合在一起才能发布,所有模块必须要步调一致。但需求的变化却是不同节奏的,单体架构系统只能强制的把对需求的变化响应放在一个时间点发布,进而使需求得不到及时响应。

单体架构也不能很好的响应性能变化的需求。比如某个模块的流量突然增加,或者需要大内存,单体架构只能为极少的模块增加整个系统的计算资源,又因为增加整个系统的计算资源成本很高、实施时间长,导致性能需求迟迟不能得到满足。

微服务的救赎

微服务要求独立进程,可以完全根据需求定制不同类型的计算资源,更精细化分类的利用内存、IO、CPU。因为小,可以更快水平扩展响应性能需求变化。

更关键是,微服务小,强调独立业务价值。根据康威定律,系统架构和组织架构相互影响,微服务小组需要是独立特性的全功能团队,面向最终用户需求直接对战。小团队直接面对客户需求做决策,所有信息和想法在小范围内快速交流,业务价值流动更容易可见,更快速的响应变化。

利弊权衡

微服务架构需要改变组织结构小团队充分授权、业务交付模式。

对于传统组织而言,这点是最难的,尤其是大公司往往采用层级组织结构,要求把权力下放到小团队,这会触动已有的权力结构不说,还会引起组织的不安全感。

微服务的交付模式也会挑战客户接受软件更新的习惯,这也是成功的传统企业面对传统客户需要面对的挑战。

结论: 微服务的终极价值

微服务架构解放小团队生产力,提高市场响应力。

微服务是颗子弹,需要PaaS作枪,瞄准的是快速变化的目标。

Share

技术雷达之微服务架构

最近几年,微服务架构异军突起,与容器技术相辅相成,成为架构设计领域热议的话题。而《技术雷达》作为ThoughtWorks出品的一份关于技术趋势的报告,在技术社区也一直有着非常好的口碑。本篇文章就试图结合技术雷达与微服务架构,以往期技术雷达中微服务架构的演变来审视一下这个新兴架构的整个发展过程。

最近几年,微服务架构异军突起,与容器技术相辅相成,成为架构设计领域热议的话题。而《技术雷达》作为ThoughtWorks出品的一份关于技术趋势的报告,在技术社区也一直有着非常好的口碑。本篇文章就试图结合技术雷达与微服务架构,以往期技术雷达中微服务架构的演变来审视一下这个新兴架构的整个发展过程。

相信大家了解微服务架构或者听说微服务架构也都是近两年的事情,从Google Trends的搜索数据统计上看,微服务架构确实也是从2014年才逐渐兴起,到目前呈现出一个爆发的趋势。

但技术雷达在2012年的3月份就已经提及了微服务架构相关的内容,在当时,其所处的状态还是评估(Assess)阶段,这就说明技术雷达早在2012年初的时候就已经成功捕获到微服务架构这个新的技术架构。

(微服务2012年第一次出现在技术雷达上)

到底什么才是微服务架构,Martin Fowler在那篇著名的描述微服务架构的文章中第一次定义了微服务架构并阐述了其九大特性。而我一开始接触微服务架构的时候也觉得这好像不是一个新的概念,很早之前就有RPC和SOA这种面向服务的分布式架构,又冒出一个新的微服务架构,他们到底有什么区别?

看到Martin Fowler的定义以后,才慢慢清楚他们的区别,在Martin Fowler的定义中有几个关键字可以让我们甄别一个分布式架构是传统的面向服务架构还是新的微服务架构:每个服务是不是跑在独立的进程中?是不是采用轻量级的通讯机制?是不是可以做到独立的部署?

(微服务架构的定义)

时间来到了2012年的10月份,在这期的技术雷达中,微服务架构已经从评估(Assess)阶段被移到实验(Trial)阶段。什么叫实验阶段?ThoughtWorks内部有一个解释,就是这项技术已经可以运用在实际项目中,但你仍要控制风险,也就是说此项技术已经可以在风险比较低的项目中使用了。

一个项目要能被移到试验的阶段,还有一个必须要满足的条件,就是必须在ThoughtWorks自己的项目中已经开始实际使用。幸运的是,我当时所在的项目也是在2012年10月份左右开始采用微服务架构的,结果也是非常好的。我们在3个月完成一个新的应用并成功上线,当时客户评价很高。

实际体验下来,微服务架构对我们究竟有哪些好处?这几点是我体会到的:

首先是组件化,作为一个软件开发人员,我们一直都有一个梦想,就是希望有朝一日可以把一堆组件像乐高一样通过直接拼装的方式快速构建我们的应用。无论是最早基于拖拽的方式构建应用,还是现在大热的前端组件化,我们一直都在试图寻找一种更好的组件化方式,微服务架构也是其中之一。但构建软件本身仍是一个非常复杂的过程,微服务架构为我们提供了一种组件化的可能,但直到现在还不好说它能不能达到我们作为整体组件化的目标,但是至少从实际体验来看,它确实能给我们带来组件化的很多好处。

然后是弹性架构,在2015年11月期技术雷达中推荐了亚马逊的弹性计算平台,如果我们的系统是由按业务划分的服务构成,结合容器技术和云平台我们就可以构建一个极具弹性的架构。通过云平台实时的监控,一旦发现资源紧张,立刻就可以通过云平台和容器技术自动瞬间扩展出成百上千的服务资源,在高峰过去之后又可以立即把所有的服务注销掉,释放资源,整个过程完全是自动化的。

去中心化和快速响应也是微服务架构给我们带来的好处。在单体架构下,会非常依赖于项目一开始时对于技术选择,一旦选择了一个技术栈,那么之后几年都被绑定在了这样一个技术栈下,很难应对变化。微服务架构则给我们提供了一个更细粒度使用技术的可能,在不同的服务里可以使用完全不同的技术栈,不同的语言、框架甚至数据库,真正做到用最适合的技术解决最适合的问题,从而让我们可以更加敏捷地响应需求和市场的变化,增加了竞争力。

(微服务架构的好处)

从2012年10月份一直到2014年的7月份,在这个时间段有大量与微服务架构相关的工具、技术和框架出现在技术雷达上。包含了很多领域:语言、测试,框架、CI、CD、持续交付,安全等等。

从2012年的3月份微服务架构第一次出现在技术雷达上一直到2014年7月份,虽然微服务架构已经有了比较大的发展,技术雷达上也已经推荐了大量相关的内容,但在当时社区中谈论微服务架构的声音并不多,这也体现出了技术雷达的前瞻性。

(技术雷达上微服务架构相关项目)

从2014年7月份开始微服务在社区就开始呈现出一种爆发的趋势,但在紧接着的2015年1月刊的技术雷达中却出现一个非常有意思的项目:Microservice Envy。通俗点儿讲就是“微服务红眼病”,或者说是“微服务你有我也要”。

这意味着在社区刚刚爆发,对于微服务架构踩下油门的时候,我们已经踩下了一脚刹车。但这并不是代表我们不看好微服务架构,而是认为需要认真思考我们是否真正需要以及何时以何种方式使用微服务架构,不能看别的人都在使用也盲目切换到微服务架构下。

这是因为微服务架构并不是免费的午餐,使用微服务架构是需要门槛和成本的。我们需要问自己:用微服务我们够“个”吗?或是说用微服务我们够“格”么?我们是否有这个能力和足够的资源驾驭这个新的架构?

Martin Fowler在他的《企业应用架构模式》中,就提到了分布式对象设计的第一原则:“设计分布式对象的第一个原则就是不要使用分布式对象”。因为分布式系统会给我们带来很大的挑战,让系统复杂度大幅增加的同时,我们还需要面对开发环境、测试、部署、运维、监控,一致性和事务等一系列的问题。

(Microservice Envy)

所以说,微服务架构虽然看起来非常美好,但是也是有很大附加成本的。通过下面这张图可以看到,横轴是时间轴,纵轴是生产力。当软件的复杂度很低的时候,单体架构下的生产力是要高于微服务架构的,但随着复杂度的不断增加,无论是单体应用还是微服务应用的生产力都会下降,只是微服务架构的下降会相对缓慢一些。

这也容易理解,因为在微服务架构中,我们的系统是由很多的小的服务组成,每一个服务都很小,相对简单,技术栈也很独立。这样做局部的变更也会更加容易,随着系统复杂度的不断增加,微服务的优势也就慢慢地体现出来了。

那要如何应对呢?为了追求生产力的最大化,一开始我们可以选择从一个单体架构开始,然后争取在微服务架构生产力超越单体架构的那个复杂度点切换到微服务架构上来,这样才能实现生产力的最大化。这就是Martin Fowler提出的单体应用优先原则(MonolithFirst),以单体架构开始,通过演进式设计逐渐重构到微服务架构。

(MonolithFirst)

为了保证从单体架构演进到微服务架构的重构过程安全可控,还需要有一套良好的质量守护机制。下图描述的就是Martin Fowler提出的微服务架构下的测试策略,我所在项目就是按照这种方式来划分和设计我们各种不同类型的测试,帮助我们在对于服务的抽取合并分离的重构过程中做到安全可控。

(Testing Strategies in a Microservice Architecture)

我们刚才提到了康威定律,康威定律说的是设计系统的组织产生的设计和架构等价于组织间的沟通结构。而康威定律还有一个逆定律:如果想改变一个设计架构方式,首先要改变组织结构。我们经常发现推动技术架构的转型和演进很难,因为我们在调整技术架构的同时却忽略了组织结构也要对应做相应的调整以匹配技术架构的变化,当组织结构与技术架构不匹配的时候,就会相互拉扯,这些都是在当时的技术雷达中着重强调的。

截至目前,以上内容都还是在谈论2015年以前各期技术雷达里的内容。在这之后直到现在,技术雷达也还在持续地推荐微服务架构相关的内容。所以说踩下刹车并不是因为我们走错了路,只是走的太快了,需要时刻提醒自己不要盲目,要清楚微服务给我们带来了什么和有着什么样的挑战,最终解决我们的问题。

来到最新的几期技术雷达,微服务架构还在不断的演进,而且慢慢的与其他新兴的技术融合形成一整套的新的不同以往的构建软件的解决方案。例如无服务器架构、对Docker的应用、对PaaS各种云的应用等,这些技术的发展,会不会对微服务架构的演进提供更多可能?是否可以为微服务架构早一天落地、改变我们的开发方式提供可能?让我们大家一起拭目以待。


更多精彩洞见,请关注微信公众号:思特沃克

Share

在微服务中使用领域事件

在软件开发领域,事件驱动架构(Event Driven Architecture,EDA)早已被开发者用于各种实践,典型的应用场景比如浏览器对用户输入的处理、消息机制以及SOA。最近几年重新进入开发者视野的响应式编程(Reactive Programming)更是将事件作为该编程模型中的一等公民。可见,“事件”这个概念一直在计算机科学领域中扮演着重要的角色。

稍微回想一下计算机硬件的工作原理我们便不难发现,整个计算机的工作过程其实就是一个对事件的处理过程。当你点击鼠标、敲击键盘或者插上U盘时,计算机便以中断的形式处理各种外部事件。在软件开发领域,事件驱动架构(Event Driven Architecture,EDA)早已被开发者用于各种实践,典型的应用场景比如浏览器对用户输入的处理、消息机制以及SOA。最近几年重新进入开发者视野的响应式编程(Reactive Programming)更是将事件作为该编程模型中的一等公民。可见,“事件”这个概念一直在计算机科学领域中扮演着重要的角色。

认识领域事件

领域事件(Domain Events)是领域驱动设计(Domain Driven Design,DDD)中的一个概念,用于捕获我们所建模的领域中所发生过的事情。领域事件本身也作为通用语言(Ubiquitous Language)的一部分成为包括领域专家在内的所有项目成员的交流用语。比如,在用户注册过程中,我们可能会说“当用户注册成功之后,发送一封欢迎邮件给客户。”,此时的“用户已经注册”便是一个领域事件。

当然,并不是所有发生过的事情都可以成为领域事件。一个领域事件必须对业务有价值,有助于形成完整的业务闭环,也即一个领域事件将导致进一步的业务操作。举个咖啡厅建模的例子,当客户来到前台时将产生“客户已到达”的事件,如果你关注的是客户接待,比如需要为客户预留位置等,那么此时的“客户已到达”便是一个典型的领域事件,因为它将用于触发下一步——“预留位置”操作;但是如果你建模的是咖啡结账系统,那么此时的“客户已到达”便没有多大存在的必要——你不可能在用户到达时就立即向客户要钱对吧,而”客户已下单“才是对结账系统有用的事件。

微服务(Microservices)架构实践中,人们大量地借用了DDD中的概念和技术,比如一个微服务应该对应DDD中的一个限界上下文(Bounded Context);在微服务设计中应该首先识别出DDD中的聚合根(Aggregate Root);还有在微服务之间集成时采用DDD中的防腐层(Anti-Corruption Layer, ACL);我们甚至可以说DDD和微服务有着天生的默契。更多有关DDD的内容,请参考笔者的另一篇文章或参考《领域驱动设计》《实现领域驱动设计》

在DDD中有一条原则:一个业务用例对应一个事务,一个事务对应一个聚合根,也即在一次事务中,只能对一个聚合根进行操作。但是在实际应用中,我们经常发现一个用例需要修改多个聚合根的情况,并且不同的聚合根还处于不同的限界上下文中。比如,当你在电商网站上买了东西之后,你的积分会相应增加。这里的购买行为可能被建模为一个订单(Order)对象,而积分可以建模成账户(Account)对象的某个属性,订单和账户均为聚合根,并且分别属于订单系统和账户系统。显然,我们需要在订单和积分之间维护数据一致性,通常的做法是在同一个事务中同时更新两者,但是这会存在以下问题:

  • 违背DDD中”单个事务修改单个聚合根”的设计原则;
  • 需要在不同的系统之间采用重量级的分布式事务(Distributed Transactioin,也叫XA事务或者全局事务);
  • 在不同系统之间产生强耦合。

通过引入领域事件,我们可以很好地解决上述问题。 总的来说,领域事件给我们带来以下好处:

  • 解耦微服务(限界上下文);
  • 帮助我们深入理解领域模型;
  • 提供审计和报告的数据来源;
  • 迈向事件溯源(Event Sourcing)和CQRS等。

还是以上面的电商网站为例,当用户下单之后,订单系统将发出一个“用户已下单”的领域事件,并发布到消息系统中,此时下单便完成了。账户系统订阅了消息系统中的“用户已下单”事件,当事件到达时进行处理,提取事件中的订单信息,再调用自身的积分引擎(也有可能是另一个微服务)计算积分,最后更新用户积分。可以看到,此时的订单系统在发送了事件之后,整个用例操作便结束了,根本不用关心是谁收到了事件或者对事件做了什么处理。事件的消费方可以是账户系统,也可以是任何一个对事件感兴趣的第三方,比如物流系统。由此,各个微服务之间的耦合关系便解开了。值得注意的一点是,此时各个微服务之间不再是强一致性,而是基于事件的最终一致性。

事件风暴(Event Storming)

事件风暴是一项团队活动,旨在通过领域事件识别出聚合根,进而划分微服务的限界上下文。在活动中,团队先通过头脑风暴的形式罗列出领域中所有的领域事件,整合之后形成最终的领域事件集合,然后对于每一个事件,标注出导致该事件的命令(Command),再然后为每个事件标注出命令发起方的角色,命令可以是用户发起,也可以是第三方系统调用或者是定时器触发等。最后对事件进行分类整理出聚合根以及限界上下文。事件风暴还有一个额外的好处是可以加深参与人员对领域的认识。需要注意的是,在事件风暴活动中,领域专家是必须在场的。更多有关事件风暴的内容,请参考这里

创建领域事件

领域事件应该回答“什么人什么时候做了什么事情”这样的问题,在实际编码中,可以考虑采用层超类型(Layer Supertype)来包含事件的某些共有属性:

public abstract class Event {
    private final UUID id;
    private final DateTime createdTime;

    public Event() {
        this.id = UUID.randomUUID();
        this.createdTime = new DateTime();
    }
}

可以看到,领域事件还包含了ID,但是该ID并不是实体(Entity)层面的ID概念,而是主要用于事件追溯和日志。另外,由于领域事件描述的是过去发生的事情,我们应该将领域事件建模成不可变的(Immutable)。从DDD概念上讲,领域事件更像一种特殊的值对象(Value Object)。对于上文中提到的咖啡厅例子,创建“客户已到达”事件如下:

public final class CustomerArrivedEvent extends Event {
    private final int customerNumber;

    public CustomerArrivedEvent(int customerNumber) {
        super();
        this.customerNumber = customerNumber;
    }
}

在这个CustomerArrivedEvent事件中,除了继承自Event的属性外,还自定义了一个与该事件密切关联的业务属性——客户人数(customerNumber)——这样后续操作便可预留相应数目的座位了。另外,我们将所有属性以及CustomerArrivedEvent本身都声明成了final,并且不向外暴露任何可能修改这些属性的方法,这样便保证了事件的不变性。

发布领域事件

在使用领域事件时,我们通常采用“发布-订阅”的方式来集成不同的模块或系统。在单个微服务内部,我们可以使用领域事件来集成不同的功能组件,比如在上文中提到的“用户注册之后向用户发送欢迎邮件”的例子中,注册组件发出一个事件,邮件发送组件接收到该事件后向用户发送邮件。

在微服务内部使用领域事件时,我们不一定非得引入消息中间件(比如ActiveMQ等)。还是以上面的“注册后发送欢迎邮件”为例,注册行为和发送邮件行为虽然通过领域事件集成,但是他们依然发生在同一个线程中,并且是同步的。另外需要注意的是,在限界上下文之内使用领域事件时,我们依然需要遵循“一个事务只更新一个聚合根”的原则,违反之往往意味着我们对聚合根的拆分是错的。即便确实存在这样的情况,也应该通过异步的方式(此时需要引入消息中间件)对不同的聚合根采用不同的事务,此时可以考虑使用后台任务。

除了用于微服务的内部,领域事件更多的是被用于集成不同的微服务,如上文中的“电商订单”例子。

通常,领域事件产生于领域对象中,或者更准确的说是产生于聚合根中。在具体编码实现时,有多种方式可用于发布领域事件。

一种直接的方式是在聚合根中直接调用发布事件的Service对象。以上文中的“电商订单”为例,当创建订单时,发布“订单已创建”领域事件。此时可以考虑在订单对象的构造函数中发布事件:

public class Order {
    public Order(EventPublisher eventPublisher) {
        //create order        
        //…        
        eventPublisher.publish(new OrderPlacedEvent());    
        }
}

注:为了把焦点集中在事件发布上,我们对Order对象做了简化,Order对象本身在实际编码中不具备参考性。

可以看到,为了发布OrderPlacedEvent事件,我们需要将Service对象EventPublisher传入,这显然是一种API污染,即Order作为一个领域对象只需要关注和业务相关的数据,而不是诸如EventPublisher这样的基础设施对象。另一种方法是由NServiceBus的创始人Udi Dahan提出来的,即在领域对象中通过调用EventPublisher上的静态方法发布领域事件:

 public class Order {
    public Order() {
        //create order
        //...
        EventPublisher.publish(new OrderPlacedEvent());
    }
}

这种方法虽然避免了API污染,但是这里的publish()静态方法将产生副作用,对Order对象的测试带来了难处。此时,我们可以采用“在聚合根中临时保存领域事件”的方式予以改进:

public class Order {

    private List<Event> events;

    public Order() {
        //create order
        //...
        events.add(new OrderPlacedEvent());
    }

    public List<Event> getEvents() {
        return events;
    }

    public void clearEvents() {
        events.clear();

    }
}

在测试Order对象时,我们便你可以通过验证events集合保证Order对象在创建时的确发布了OrderPlacedEvent事件:

@Test
public void shouldPublishEventWhenCreateOrder() {
    Order order = new Order();
    List<Event> events = order.getEvents();
    assertEquals(1, events.size());
    Event event = events.get(0);
    assertTrue(event instanceof OrderPlacedEvent);
}

在这种方式中,聚合根对领域事件的保存只能是临时的,在对该聚合根操作完成之后,我们应该将领域事件发布出去并及时清空events集合。可以考虑在持久化聚合根时进行这样的操作,在DDD中即为资源库(Repository):

public class OrderRepository {
    private EventPublisher eventPublisher;

    public void save(Order order) {
        List<Event> events = order.getEvents();
        events.forEach(event -> eventPublisher.publish(event));
        order.clearEvents();

        //save the order
        //...
    }
}

除此之外,还有一种与“临时保存领域事件”相似的做法是“在聚合根方法中直接返回领域事件”,然后在Repository中进行发布。这种方式依然有很好的可测性,并且开发人员不用手动清空先前的事件集合,不过还是得记住在Repository中将事件发布出去。另外,这种方式不适合创建聚合根的场景,因为此时的创建过程既要返回聚合根本身,又要返回领域事件。

这种方式也有不好的地方,比如它要求开发人员在每次更新聚合根时都必须记得清空events集合,忘记这么做将为程序带来严重的bug。不过虽然如此,这依然是笔者比较推荐的方式。

业务操作和事件发布的原子性

虽然在不同聚合根之间我们采用了基于领域事件的最终一致性,但是在业务操作和事件发布之间我们依然需要采用强一致性,也即这两者的发生应该是原子的,要么全部成功,要么全部失败,否则最终一致性根本无从谈起。以上文中“订单积分”为例,如果客户下单成功,但是事件发送失败,下游的账户系统便拿不到事件,导致最终客户的积分并不增加。

要保证业务操作和事件发布之间的原子性,最直接的方法便是采用XA事务,比如Java中的JTA,这种方式由于其重量级并不被人们所看好。但是,对于一些对性能要求不那么高的系统,这种方式未尝不是一个选择。一些开发框架已经能够支持独立于应用服务器的XA事务管理器(如AtomikosBitronix),比如Spring Boot作为一个微服务框架便提供了对Atomikos和Bitronix的支持

如果JTA不是你的选项,那么可以考虑采用事件表的方式。这种方式首先将事件保存到聚合根所在的数据库中,由于事件表和聚合根表同属一个数据库,整个过程只需要一个本地事务就能完成。然后,在一个单独的后台任务中读取事件表中未发布的事件,再将事件发布到消息中间件中。

这种方式需要注意两个问题,第一个是由于发布了事件之后需要将表中的事件标记成“已发布”状态,即依然涉及到对数据库的操作,因此发布事件和标记“已发布”之间需要原子性。当然,此时依旧可以采用XA事务,但是这违背了采用事件表的初衷。一种解决方法是将事件的消费方创建成幂等的,即消费方可以多次消费同一个事件而不污染系统数据。这个过程大致为:整个过程中事件发送和数据库更新采用各自的事务管理,此时有可能发生的情况是事件发送成功而数据库更新失败,这样在下一次事件发布操作中,由于先前发布过的事件在数据库中依然是“未发布”状态,该事件将被重新发布到消息系统中,导致事件重复,但由于事件的消费方是幂等的,因此事件重复不会存在问题。

另外一个需要注意的问题是持久化机制的选择。其实对于DDD中的聚合根来说,NoSQL是相比于关系型数据库更合适的选择,比如用MongoDB的Document保存聚合根便是种很自然的方式。但是多数NoSQL是不支持ACID的,也就是说不能保证聚合更新和事件发布之间的原子性。还好,关系型数据库也在向NoSQL方向发展,比如新版本的PostgreSQL(版本9.4)和MySQL(版本5.7)已经能够提供具备NoSQL特征的JSON存储和基于JSON的查询。此时,我们可以考虑将聚合根序列化成JSON格式的数据进行保存,从而避免了使用重量级的ORM工具,又可以在多个数据之间保证ACID,何乐而不为?

总结

领域事件主要用于解耦微服务,此时各个微服务之间将形成最终一致性。事件风暴活动有助于我们对微服务进行拆分,并且有助于我们深入了解某个领域。领域事件作为已经发生过的历史数据,在建模时应该将其创建为不可变的特殊值对象。存在多种方式用于发布领域事件,其中“在聚合中临时保存领域事件”的方式是值得推崇的。另外,我们需要考虑到聚合更新和事件发布之间的原子性,可以考虑使用XA事务或者采用单独的事件表。为了避免事件重复带来的问题,最好的方式是将事件的消费方创建为幂等的。


更多精彩洞见,请关注微信公众号:思特沃克

Share

持续部署Microservices的实践和准则

每个Microservices完成一个独立的业务逻辑,它可以是一个HTTP API服务,提供给其他服务或者客户端使用。也可以是一个ETL服务,用于完成数据迁移工作。每个Microservices除了在业务独立外,也会有自己独立的运行环境,独立的开发、部署流程。

这种独立性给服务的部署和运营带来很大的挑战。因此持续部署(Continuous Deployment)是Microservices场景下一个重要的技术实践。本文将介绍持续部署Microservices的实践和准则。

当我们讨论Microservices架构时,我们通常会和Monolithic架构(单体架构 )进行比较。

在Monolithic架构中,一个简单的应用会随着功能的增加、时间的推移变得越来越庞大。当Monoltithic App变成一个庞然大物,就没有人能够完全理解它究竟做了什么。此时无论是添加新功能,还是修复Bug,都是一个非常痛苦、异常耗时的过程。

Microservices架构渐渐被许多公司采用(AmazoneBayNetflix),用于解决Monolithic架构带来的问题。其思路是将应用分解为小的、可以相互组合的Microservices。这些Microservices通过轻量级的机制进行交互,通常会采用基于HTTP协议的服务。

每个Microservices完成一个独立的业务逻辑,它可以是一个HTTP API服务,提供给其他服务或者客户端使用。也可以是一个ETL服务,用于完成数据迁移工作。每个Microservices除了在业务独立外,也会有自己独立的运行环境,独立的开发、部署流程。

这种独立性给服务的部署和运营带来很大的挑战。因此持续部署(Continuous Deployment)是Microservices场景下一个重要的技术实践。本文将介绍持续部署Microservices的实践和准则。

实践:

  • 使用Docker容器化服务
  • 采用Docker Compose运行测试

准则:

  • 构建适合团队的持续部署流水线
  • 版本化一切
  • 容器化一切

使用Docker容器化服务

我们在构建和发布服务的时候,不仅要发布服务本身,还需要为其配置服务器环境。使用Docker容器化微服务,可以让我们不仅发布服务,同时还发布其需要的运行环境。容器化之后,我们可以基于Docker构建我们的持续部署流水线:

上图描述了一个基于Ruby on Rails(简称:Rails)服务的持续部署流水线。我们用Dockerfile配置Rails项目运行所需的环境,并将Dockerfile和项目同时放在Git代码仓库中进行版本管理。下面Dockerfile可以描述一个Rails项目的基础环境:

FROM ruby:2.3.3

RUN apt-get update -y && \
    apt-get install -y libpq-dev nodejs git

WORKDIR /app

ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install

ADD . /app

EXPOSE 80

CMD ["bin/run"]

在持续集成服务器上会将项目代码和Dockerfile同时下载(git clone)下来进行构建(Build Image)、单元测试(Testing)、最终发布(Publish)。此时整个构建过程都基于Docker进行,构建结果为Docker Image,并且将最终发布到Docker Registry

在部署阶段,部署机器只需要配置Docker环境,从Docker Registry上Pull Image进行部署。

在服务容器化之后,我们可以让整套持续部署流水线只依赖Docker,并不需要为环境各异的服务进行单独配置。

使用Docker Compose运行测试

在整个持续部署流水线中,我们需要在持续集成服务器上部署服务、运行单元测试和集成测试Docker Compose为我们提供了很好的解决方案。

Docker Compose可以将多个Docker Image进行组合。在服务需要访问数据库时,我们可以通过Docker Compose将服务的Image和数据库的Image组合在一起,然后使用Docker Compose在持续集成服务器上进行部署并运行测试。

上图描述了Rails服务和Postgres数据库的组装过程。我们只需在项目中额外添加一个docker-compose.yml来描述组装过程:

db:
  image: postgres:9.4
  ports:
    - "5432"

service:
  build: .
  command: ./bin/run
  volumes:
    - .:/app
  ports:
    - "3000:3000"

dev:
  extends:
    file: docker-compose.yml
    service: service
  links:
    - db
  environment:
    - RAILS_ENV=development

ci:
  extends:
    file: docker-compose.yml
    service: service
  links:
    - db
  environment:
    - RAILS_ENV=test

采用Docker Compose运行单元测试和集成测试:

docker-compose run -rm ci bundle exec rake

构建适合团队的持续部署流水线

当我们的代码提交到代码仓库后,持续部署流水线应该能够对服务进行构建、测试、并最终部署到生产环境。

为了让持续部署流水线更好的服务团队,我们通常会对持续部署流水线做一些调整,使其更好的服务于团队的工作流程。例如下图所示的,一个敏捷团队的工作流程:

通常团队会有业务分析师(BA)做需求分析,业务分析师将需求转换成适合工作的用户故事卡(Story Card),开发人员(Dev)在拿到新的用户故事卡时会先做分析,之后和业务分析师、技术主管(Tech Lead)讨论需求和技术实现方案(Kick off)。

开发人员在开发阶段会在分支(Branch)上进行开发,采用Pull Request的方式提交代码,并且邀请他人进行代码评审(Review)。在Pull Request被评审通过之后,分支会被合并到Master分支,此时代码会被自动部署到测试环境(Test)

在Microservices场景下,本地很难搭建一整套集成环境,通常测试环境具有完整的集成环境,在部署到测试环境之后,测试人员(QA)会在测试环境上进行测试。

测试完成后,测试人员会跟业务分析师、技术主管进行验收测试(User Acceptance Test),确认需求的实现和技术实现方案,进行验收。验收后的用户故事卡会被部署到生产环境(Production)。

在上述团队工作的流程下,如果持续部署流水线仅对Master分支进行打包、测试、发布。在开发阶段(即:代码还在分支)时,无法从持续集成上得到反馈,直到代码被合并到Master并运行构建后才能得到反馈,通常会造成“本地测试成功,但是持续集成失败”的场景

因此,团队对仅基于Master分支的持续部署流水线做一些改进。使其可以支持对Pull Request代码的构建:

如上图所示:

  • 持续部署流水线区分Pull Request和Master。Pull Request上只运行单元测试,Master运行完成全部构建并自动将代码部署到测试环境。
  • 为生产环境部署引入手动操作,在验收测试完成之后再手动触发生产环境部署。经过调整后的持续部署流水线可以使团队在开发阶段快速从持续集成上得到反馈,并且对生产环境的部署有更好的控制。

版本化一切

版本化一切,即将服务开发、部署相关的系统都版本化控制。我们不仅将项目代码纳入版本管理,同时将项目相关的服务、基础设施都进行版本化管理。 对于一个服务,我们一般会为它单独配置持续部署流水线,为它配置独立的用于运行的基础设施。此时会涉及两个非常重要的技术实践:

  • 构建流水线即代码
  • 基础设施即代码

构建流水线即代码。通常我们使用Jenkins或者Bamboo来搭建配置持续部署流水线,每次创建流水线需要手动配置,这些手动操作不易重用,并且可读性很差,每次对流水线配置的改动并不会保存在历史记录中,也就是说我们无从追踪配置的改动。

在今年上半年,团队将所有的持续部署流水线从Bamboo迁移到了BuildKite,BuildKite对构建流水线即代码有很好的支持。下图描述了BuildKite的工作方式:

在BuildKite场景下,我们会在每个服务代码库中新增一个pipeline.yml来描述构建步骤。构建服务器(CI Service)会从项目的pipeline.yml中读取配置,生成构建步骤。例如,我们可以使用如下代码描述流水线:

steps:
  -
    name: "Run my tests"
    command: "shared_ci_script/bin/test"
    agents:
      queue: test

  - wait

  -
    name: "Push docker image"
    command: "shared_ci_script/bin/docker-tag"
    branches: "master"
    agents:
      queue: test

  - wait

  -
    name: "Deploy To Test"
    command: "shared_ci_script/bin/deploy"
    branches: "master"
    env:
      DEPLOYMENT_ENV: test
    agents:
      queue: test

  - block

  - name: "Deploy to Production"
    command: "shared_ci_script/bin/deploy"
    branches: "master"
    env:
      DEPLOYMENT_ENV: production
    agents:
      queue: production

在上述配置中,command中的步骤(即:test、docker-tag、deploy)分别是具体的构建脚本,这些脚本被放在一个公共的shared_ci_script代码库中,shared_ci_script会以git submodule的方式被引入到每个服务代码库中。

经过构建流水线即代码方式的改造,对于持续部署流水线的任何改动都会在Git中被追踪,并且有很好的可读性。

基础设施即代码。对于一个基于HTTP协议的API服务基础设施可以是:

  • 用于部署的机器
  • 机器的IP和网络配置
  • 设备硬件监控服务(CPU,Memory等)
  • 负载均衡(Load Balancer)
  • DNS服务
  • AutoScaling Service(自动伸缩服务)
  • Splunk日志收集
  • NewRelic性能监控
  • PagerDuty报警

这些基础设施我们可以使用代码进行描述,AWS Cloudformation在这方面提供了很好的支持。我们可以使用AWS Cloudformation设计器或者遵循AWS Cloudformation的语法配置基础设施。下图为一个服务的基础设施构件图,图中构建了上面提到的大部分基础设施:

在AWS Cloudformation中,基础设施描述代码可以是JSON文件,也可以是YAML文件。我们将这些文件也放到项目的代码库中进行版本化管理。

所有对基础设施的操作,我们都通过修改AWS Cloudformation配置进行修改,并且所有修改都应该在Git的版本化控制中。

由于我们采用代码描述基础设施,并且大部分服务遵循相通的部署流程和基础设施,基础设施代码的相似度很高。DevOps团队会为团队创建属于自己的部署工具来简化基础设施配置和部署流程。

容器化一切

通常在部署服务时,我们还需要一些辅助服务,这些服务我们也将其容器化,并使用Docker运行。下图描述了一个服务在AWS EC2 Instance上面的运行环境:

在服务部署到AWS EC2 Instance时,我们需要为日志配置收集服务,需要为服务配置Nginx反向代理。

按照12-factors原则,我们基于fluentd,采用日志流的方式处理日志。其中logs-router用来分发日志、splunk-forwarder负责将日志转发到Splunk

在容器化一切之后,我们的服务启动只需要依赖Docker环境,相关服务的依赖也可以通过Docker的机制运行。

总结

Microservices给业务和技术的扩展性带来了极大的便利,同时在组织和技术层面带来了极大的挑战。由于在架构的演进过程中,会有很多新服务产生,持续部署是技术层面的挑战之一,好的持续部署实践和准则可以让团队从基础设施抽离出来,关注与产生业务价值的功能实现。


更多精彩洞见,请关注微信公众号:思特沃克

Share

给CxO的微服务指南

企业高管可能会认为微服务是最新流行词而不予考虑,但那些了解这种架构影响的人可以获得实实在在的好处。

文/Jim Highsmith & Neal Ford 译者/禚娴静

这是技术雷达系列文章的第四篇。在这一系列的文章中,技术雷达的作者们向企业领导者分享了他们对于那些推动业务差异化的技术问题和解决方案的洞见和经验。ThoughtWorks技术雷达由ThoughtWorks全球技术专家咨询委员会创建,它包含了对软件开发和业务战略有显著影响的技术趋势组合,2016年是技术雷达发布的第七年。

未来已经在这里,它只是分布不均。—— 威廉·吉布森

数字时代就在我们身边。将软件开发视为高成本开销而不是竞争力的企业将会举步维艰。为了参与并在这个数字世界中繁荣兴旺,企业必须学会适应我们这个时代的不确定性—更快地将新产品推向市场,快速而有效地改进当前的产品,并为客户提供有意义的数字体验。

为了实现这些目标,企业需要将敏捷性/适应性集成到三个领域:人、流程和技术。

人们需要适应试验和改变。以“迭代”的形式推进这一过程,并加强学习。技术,包括技术架构,需要支持快速地交付产品与服务。

技术的敏捷性不能被忽视——敏捷的团队和流程并不能弥补笨重的技术所带来的缺陷。

但作为一个高级管理人员,在这样一个充满了新技术的世界,你应该将注意力投注在哪里呢?

这个问题的答案被那些不停地讨论着看似神秘话题的技术人员们所掩盖。哪些神秘的话题需要得到管理层的注意呢?其中最重要的话题之一是微服务。原因如下所述:

微服务影响人、流程和技术:包括团队组织结构,流程和实践(如DevOps),以及重新调整架构以适应我们要解决的问题,而并非纯技术层面。微服务促进了每个领域的适应性。它值得管理层花时间去了解其潜在的贡献。

技术敏捷度

微服务架构风格的特性是由一组极小的服务组成,它们彼此独立且单独部署。微服务由Netflix这样的公司推广开来。每个服务包含一个离散的业务功能,它在技术上与其他服务相隔离,产生了类似乐高积木的效果:开发人员可以将服务切换为一个新的服务,而无需破坏其他服务。就像巨型的乐高模型可以由75,000块乐高组成,大型的数字应用程序也可以由这些受乐高思想启发的服务所构建。

这种架构有一些明显的好处。首先,每个服务与其他服务高度解耦,这意味着它们是自包含的。因此,一个服务中的技术更改(例如数据结构更改)不会影响另一个服务。服务仍然可以通过消息传递进行通信,但是任何一个服务都不允许修改另一个服务的实现细节。

第二,因为开发人员不需要共享基础设施,他们可以选用适合于该问题复杂度的技术栈来实现每一个服务。考虑到当今技术栈的复杂性,在同一应用程序中,使用简单工具处理简单问题和使用复杂工具处理复杂问题的能力使开发团队在增加了灵活性的同时也降低了成本。领导者重视能将复杂问题简化的技术专家。

第三,每个服务封装了业务功能,它更容易促成围绕特定问题域的团队,而不是通过作业功能分割的团队。例如,服务团队通常包括开发人员、业务分析师、DBA、运维人员和QA—即构建和部署服务所需的一切角色。这样一来便降低了协调成本。

“从功能性组织结构向产品或服务结构转变”是敏捷企业转型的一个日益增长的趋势。而微服务架构支持了这种趋势的变化。

最后,因为每个服务是相互隔离的,所以以服务组成的架构既快速又灵活。同时因为服务的业务范围很小,对服务的更改可以快速地发生,这为开发人员提供了新的高级功能。一旦架构师设计了一个小型独立服务的系统,其中应用程序由部署的多个服务之间的消息传递组成,多变量测试等操作就变得容易了。

例如,企业可能对他们网站的未来发展方向并不确定。因此他们设计了具有相似性但功能不同的两种服务,并向不同的用户组部署不同的版本,以获取结果从而推动未来的发展。像Facebook这样的公司也是通过使用这种类型的A / B测试进行试验来分析他们的用户。

标准化一直是IT组织降低成本的方式之一。不幸的是,它也降低了灵活性—标准化越多,灵活性越少。通过采纳微服务架构,架构师和开发人员可以使用更加多样化且能够紧密反映问题复杂度的技术栈来设计应用程序。

微服务架构风格与许多企业部署软件和分配IT资源的方式相反。许多架构风格的主要目标之一是有效地利用共享资源(操作系统、数据库服务器、应用程序服务器等)。由于资源的成本影响了底线,因此这些公司建立了软件架构以最大化共享资源。

然而,共享资源有一个缺点。无论开发人员如何有效地构建与这些服务器的隔离,都会出现对这些资源的竞争。有时组件由于依赖管理而互相干扰,有时由于两个组件争用某些资源(例如CPU)而产生问题。这就不可避免地会导致共享组件以并不期望的方式进行交互。

容器和解耦

在软件交付中,有两个关键的技术“环境”:开发人员工作的开发环境,以及IT运维人员管辖范围的部署环境。传统情况下,在这两个环境之间移动代码充满了技术错误,冗长的时间延迟以及组织层面的沟通不畅。

几年前,一些有趣的事情发生了:Linux对于大多数企业足够友好,Linux的变体在商业上免费—但是这不足以影响技术架构。

接下来,开源的创新与敏捷开发技术的结合鼓励了开发人员创建各种工具,将许多繁琐的运维手工操作自动化。这被许多人称为DevOps革命。

这场革命使得开发团队和IT运维人员通过使用Puppet、Chef和Docker等高级工具更加紧密地联系在一起。意外地,Linux的变体允许免费操作,开发人员可以在不受干扰的情况下将其组件部署到一个原始的操作系统。一整个可能的错误类别就突然消失了,因为组件之间能够相互解耦。

如果开发人员可以构建他们自己的现实环境,他们必须减少与运维部门的协调,也就减少了组织间的摩擦。用程序启动类生产环境的能力消除了测试、集成、共享环境下的资源竞争、以及一系列其他问题。

21世纪的架构敏捷度

在治理方面,微服务架构风格有其他的好处。传统的做法是,企业架构师定义了组织的共享技术栈,以最大化项目间的资源使用,同时最大程度地减少支撑成本。例如,一个大型企业可能将Java和Oracle标准化以作为其主要开发平台。如果每个人都使用Oracle,那么该企业的一切都可以存储在一个工业强度的数据库中。

规范化治理有一个缺点—通常,为了标准化的某一目的,团队必须使用并不太理想的工具。与此同时,还有一个潜在的更微妙的缺点。例如,考虑一个已经选择在特定消息队列上标准化的大型企业。在评估需要此共享基础设施的所有项目时,企业架构师会找出最复杂的场景,并选择一个适合这种复杂度的工具来处理它们。

然而,许多项目并不具备这个最复杂的场景。但因为每个项目必须共享相同的基础设施,所以每个团队都得承担其他团队的最大复杂度。这种情况也发生在数据库层。许多项目只需要几个记录的简单数据存储,并不需要复杂的查询功能,但最终都使用了具有工业强度的数据库服务器,只因为这个企业的标准如此。

容器化技术解决了这个问题,因为它远离了共享基础设施—每个项目都部署在自己原始的、容器化的环境中。因为不存在共享的动力去选择工具,所以每个项目刻意选择更适合自己的工具。

当然,如果企业架构师允许每个项目选择自己的技术栈,那么会存在一些严重的缺点。我们经常看到一个称之为““Goldilocks治理”(企业架构师选择几个技术栈—简单、中等和复杂,并根据规模分配新项目)的实践,它适用于高度解耦的环境。这些知识是可迁移的,该公司仍然可以从中受益,但是却可以远离那些由于疏忽大意而将问题过于复杂化的行为。

为什么我们会谈到这儿?

Eric Evans的《领域驱动设计》一书对微服务架构发展产生了巨大的影响。它介绍了一种将大问题空间分解为领域或重要实体(如客户和订单)及其关系(客户下订单)和行为的技术。领域定义的一部分是有关边界上下文的概念:每个领域形成一个围绕实现细节的封装层。

例如,如果分析人员识别了一个Customer领域,那么它存在于自己的边界上下文中。在Customer的上下文中,开发人员知道所有的实现细节:类结构,数据库模式等。

但是,其他边界上下文(如Orders)不能看到这些实现细节。虽然领域可以为了协调的目的互相发送消息,但是任何一个领域都不能穿透另一个领域的边界上下文。因此,在一个边界上下文中的开发人员可以自由地更改实现,而不必担心破坏其他领域。

微服务中的容器是领域驱动设计(DDD)中边界上下文的物理表现:每个容器代表了一层封装,以防止实现细节干扰系统的其他部分。边界上下文提供的隔离展示了结构化软件架构的不同方式。

在过去,设计架构主要围绕技术架构:架构模式、库、框架等。例如,分层架构在许多组织中是很常见的:

[图1]传统的分层架构

在图1中,架构师沿技术层进行分离,使之在需要时可以很容易地替换一整层的内容。例如,如果开发人员需要更改持久机制,所有相关代码都会出现在持久层中。

那么开发人员多久更换一次整个持久层呢?几乎从不。但你的团队多长时间基于像Customer这样的概念工作呢?每天!

在图1分层架构中,Customer处于哪一层呢?其中部分在表示层、业务层、持久层等等。因此,项目架构上通用单元的变化在分层架构中并没有得到很好的支持,原因是通用的变更影响到了每一层。

如果不同层代表了开发团队的不同部分,其影响会更严重。例如,对Customer的更改可能涉及到用户界面、业务逻辑、持久层和其他特性。如果你的组织由开发人员、DBA、用户界面设计师和运维人员组成,而他们在相互隔离的HR部门下,那么进行常见更改的协调成本是巨大的。

微服务强调解耦和容器化,翻转了图1中的传统做法,使领域成为架构的主要元素,如图2所示。

图2:以领域为中心的架构

在图2中,分层结构仍然存在,但是其耦合边界是领域的边界,它将技术架构归入实现细节,并用领域对其进行封装。以技术为中心的组织单元中,员工与员工彼此隔离,要想在这样的组织中构建以领域为中心的架构是很难的。

许多技术人员在构建数字化企业中会遇到这样的问题:想要支持如移动应用等新举措,却被那些需要拆分的遗留系统所拖累。如今,这些企业越来越多地引入微服务作为这种拆分过程的关键战略。

Greenfield项目得益于DDD实践。通过DDD理解了他们的问题领域以及重要组件之间的接口所在。对于现有项目,更加模块化的系统会促使开发者解开事务性的泥球,并且可以在那些属于一起的组件和偶然耦合的组件之间做更清楚地区分。

团队

你还将遇到微服务对团队影响:一个架构风格是如何推动开发团队重组的。

1968年,梅尔文·康威对软件开发做了一个很有预见性的观察,被称为康威定律

设计系统的组织,其产生的设计等价于这些组织间的沟通结构。

康威定律对软件开发的意义是什么呢?你的设计反映了你的团队结构。企业常见的团队结构是由人力资源部门推动的,他们将职能类似的技术专家组织在一起。虽然这是一种符合逻辑的排序算法,但这种结构在设计自包含服务时效果不佳。

如我们认为的康威逆定律,现在许多公司在围绕业务领域组织跨职能团队,而不是围绕技术分层构建。例如,一个Customer服务可能包括一个业务分析师、开发人员、QA、UX、DBA和运维人员。

团队会在准备好之后再部署服务,而不是先构建一些东西传递到运维人员,使之包含在下一个巨大的发布中。许多选择微服务的公司使用持续部署,以尽快将变更投入生产环境中。

总结

企业高管可能会认为微服务是最新流行词而不予考虑,但那些了解这种架构影响的人可以获得实实在在的好处。微服务可以提高交付速度,增强灵活性,并提高效率。他们将工作进行重组,并与业务问题域保持一致,而不是技术域;从一组独立,更易于开发和维护的服务中创建业务应用程序;更好地匹配技术解决方案与业务问题的复杂程度;构建可以帮助重组现有遗留系统以及创建能够快速响应不断变化的业务条件的新产品和服务的自适应架构。

威廉·吉布森是正确的—许多公司已经将IT竞争力看作鲁棒性一个新的度量。对于这些企业来说,未来已经在这里了。其他还没有意识到这一点的公司可能会发现他们的未来已经受到了影响。

原文链接:https://www.thoughtworks.com/cn/insights/blog/cxo-guide-microservices


更多精彩洞见,请关注微信公众号:思特沃克

Share