着多元化微服务的流行,越来越多的服务开始采用微服务来构建。近日,曾在Google和eBay担任高级职务的Randy Shoup在博客中分享了其从这两个公司所学习到的构建大规模服务架构的经验。本文对Randy谈论的内容进行了总结,为那些没有创建、使用和保护大规模架构的工程师提供参考。
多元化(polyglot)微服务是终极游戏
大规模系统和多元化微服务最终一定会有所牵连。其中,多元化的意思是多种语言共同编写。
创建于1995年的eBay共经历了3次架构变更
-
eBay最早采用的是创始人花费两天时间所编写的Perl程序。
-
之后,它开始使用由340万行C++代码所编写的程序。
-
然后,eBay使用Java编写的分布式系统。
-
现在的ebay仍然使用了大量Java,同时也使用了很多由多种代码编写的多元化微服务。
Twitter的架构变更过程类似
-
最早,Twitter采用的是Ruby on Rails。
-
之后,它开始在前端使用JavaScript和Rails,而后端使用了大量的Scala代码。
-
目前,Twitter也同样依赖于多元化微服务。
Amazon的架构变更同样类似
-
首先,它使用的是C++程序。
-
然后,采用了Java和Scala两种语言。
-
目前,Amazon也依靠多元化微服务。
服务的生态系统
拥有多元化微服务的大规模生态系统运行情况如何呢?
-
eBay和Google采用了数百到数千个单独的服务来协同工作。
-
现在的大规模系统都是以图的形式,而不是层次式或多个连接的形式来构成服务。
-
服务之间相互依赖。
-
只有旧的大规模系统采用高度集成的方式进行组织。
如何创建服务的生态系统
-
目前运行良好的系统都是不断变革的产物。例如,Google从来没有对系统进行过集中的设计。当前的系统纯粹是适应时代和技术发展演变而成的。
-
变异和自然选择。当一个新的问题出现,工程师通常选择利用已有的产品或服务来解决。因此,一个服务只有在不断的提供价值、不断被使用的情况下,才能避免被淘汰的命运。
-
这些大规模系统采用了自底向上的设计方法。
-
以Google App Enginer(GAE)中所使用的一些服务为例:
-
Cloud Datastore嵌套依赖于以下服务:Megastore(一种结构化数据库)→Bigtable(一种集群级的结构化服务)→Colossus(一种集群化文件系统)→Borg(集群管理框架)。
-
层次关系十分清楚。每一层都添加了下一层中不存在的一些东西。
-
它采用自底向上的方法设计而成。首先是构建Colossus文件系统最后才是Cloud Datastore。
-
这是没有架构师的架构。在Goolge,绝大部分的技术决策都是由小团队站在自己的角度所采纳,而非全局角度。
-
2004年的eBay与之截然相反。当时,eBay采用了架构审查的形式,来对所有的大规模项目进行决策。其结果为:
-
通常审查人员都是在无法改变项目的时候才介入。
-
集中决策很快变成了一个瓶颈。它最大的影响就是在最后关头投出否决票。
-
eBay后来改为把经验丰富的工程师的想法放到审查板上,然后执行多个小团队都会用到的项目。把这些想法封装为员工可以自己使用的一个库、一个服务或者一些指南,而不是在最后关头进行决策。
没有架构师的变革如何定义标准
标准化可以在没有集中控制的情况下完成
-
标准化倾向于在不同服务或者架构之间的通信中产生。
通信通常会被标准化为:
-
网络协议。Google采用Stubby协议,而eBay采用REST协议。
-
数据格式。Google采用Protocol Buffer,而eBay使用JSON。
-
接口语法标准。Google采用Protocol Buffer,而JSON由自己的语法。
框架中通常会被标准化的通用模块包括:
-
源码控制。
-
配置管理。
-
集群管理者。
-
监控系统。
-
警告系统。
-
诊断工具
-
所有能够脱离传统的组件。
-
在一个充满变革的环境,标准是强制执行的:编码、提交、代码审查和代码检索。
-
鼓励最佳实践的最好方式就是用实际的代码说话。这不是自顶向下的审查或者前卫的设计,而是某个人能够编写出让工作尽快完成的代码。
-
鼓励是通过团队提供库的方式进行的。
-
鼓励也通过工程师在支持X协议或Y协议时所依赖的服务进行。
-
Google在代码方面广为人知的是:任何一行代码在进行源码控制时,至少由一个额外的编码人员进行审查。这就是一种大家交流自己经验的良好方式。绝大部分情况下,Google的每一个工程师都可以检索整个代码库。在编程人员规划如何实施一个事情时,代码库可以提供很好的参考。在1万个工程师不停工作的情况下,一个编程人员想做的事情可能已经被其他人提前完成了。这就使得一个好的项目能够通过代码库进行传播。当然,代码中的错误也同样可能会传播。
-
为了鼓励通用项目和标准化的习惯,应使得做正确的事情简单,而做错误的事情要困难很多。
-
服务之间是相互独立的:
-
在Google,服务内部是没有标准化实现的。对于外部而言,服务就是一个黑盒子。
-
存在习惯和通用库,但是没有编程语言的要求。最常用的四种语言为:C++、Go、Java和Python。很多不同的服务也采用各种各样的语言编写
-
没有针对框架或持久力机制的标准化工作。
-
在一个成熟的服务生态系统中,标准化的应该是图的弧度,而不是节点本身。定义一个通用的形状,而不是一个通用的实现。
创建新的服务
新服务只有在他们的使用被证实的时候才被创建。
通常,一种功能是为一种特别的使用场景而设计。然后,该功能被发现通用而且有效:
-
形成一个团队,然后服务扩展到它自己单独的单元。
-
只有当一个功能获得成功,而且适用于多种使用场景时,该情况才会发生。
这些架构通过实用主义的方法进行发展。没有人可以命令某项服务应该被添加。
-
Google文件系统支持搜索引擎。分布式文件系统显然更容易变得通用。
-
Bigtable初始支持搜索引擎,但却没有被广泛使用。
-
Megastore设计的初衷是作为Google应用的存储机制,却被广泛使用。
-
GAE初始是由一群工程师为帮助设计网站而构建。
-
Gmail来源于Google内部经常使用的一个项目,然后才被推广到外部使用。
弃用过时的服务
如果一个服务不再被使用会怎么样?
-
可以被重新定义目标的技术可以被重新使用。
-
工程师可以被开除或者分配到其他组。
-
Google Wave不是一个成功的市场案例,但是其中的一些技术却应用到了Google App中。例如,支持多人编辑文档的应用就来自于Wave。
-
通常情况下,核心服务会经历多次的更新换代,而老的版本就会被抛弃。Google内部就经常发生这样的事情。
构建一个服务
当服务拥有者在构建大规模多元化微服务的系统时,情况是什么样的呢?
在一个大规模框架中执行很好的服务应该是:
-
目的单一。它应该拥有一个定义良好的接口。
-
模块化和独立性。也就是所谓的微服务。
-
不应该共享一个持久层。
服务拥有者的目标是什么?
满足客户需求。在合适的质量水平下提供必要的功能,同时满足协议的性能、维护稳定性和可靠性以及不断的改善服务。
以最小的代价和努力满足需求。
-
该目标以鼓励通用框架的使用为动机。
-
每一个团队拥有有限的资源,因此应该使用通用的工具、流程、组件和服务。
-
它也鼓励好的操作行为。自动化服务的构建和部署。
-
它同时鼓励资源的高效使用。
服务拥有者的责任是什么?
谁构建谁运行。
-
一般情况下,一个团队,尤其是一个小的团队,负责服务的设计、开发、部署和最后的销毁。
-
没有额外的运维团队。
-
团队拥有选择他们自己的技术、方法和工作环境的自由。
-
团队要为他们的选择负责任。
作为一个有限上下文的服务。
-
一个团队的认知负载是有限的。
-
没必要理解生态系统中的其他服务。
-
一个团队需要深度理解其服务和他们所依赖的服务。
-
这意味着团队规模可以很小、很灵活。一个典型的团队是3-5个人。(如同美国海军陆战队,一个小队包含4个士兵。)
-
如此小的团队规模意味着团队内的沟通可以非常顺畅。
-
Conway法则可以很好的发挥优势。小团队通常会构建一些小的组件。
不同服务间的关系如何?
-
即使是在同一公司内,不同服务间的关系也如同厂商和客户的关系。
-
尽量友好并合作,但是一定要慎重对待服务间的关系。
-
一定要清楚所属关系。
-
一定要明确每个人的分工。定义一个明确的接口,并维护好。
-
客户可以选择是否使用该服务是调整激励的有效手段。通过客户来鼓励服务朝着正确的方向发展。这也是新服务构建的方式之一。
-
定义SLA。因为服务提供商向客户提供了一定的保证,客户可以依赖该服务。
-
使用服务的团队需要支付服务费用:
-
向服务支付费用可以带来经济上的激励。它可以刺激提供商和客户有效利用资源。
-
当东西免费时,人们总是不懂得珍惜。
-
例如,一个内部客户曾免费使用GAE,并使用了大量资源。要求他们更加高效的使用资源被证实并不管用。但是,在引入收费机制一周后,他们很快就能够通过1-2处的简单修改,减少GAE 90%的资源消耗。
-
并非使用GAE的团队不懂得珍惜。他们有自己更应该关注的方面,因此优化GAE的使用并不在他们的目标列表中。然而,事实证明,更加高效的架构能够带来更好的反应时间。
-
付费也能够刺激服务提供商来保证服务质量。否则,一个内部的客户也可能转而使用其他服务。付费直接带来好的开发和管理项目。代码审查就是一个例子。Google的超大规模设计和测试系统是另外一个例子。Google每天都会运行百万次的自动化测试。一旦代码被版本库接收,所有相关代码的接收测试就会被运行。这种方法可以很好的帮助小团队维护服务质量。
-
一个返利模型可以很好的鼓励小的迭代式变化。小的变化更容易理解。而代码变化的影响是非线性的。一千行代码的变化所带来的风险绝不止一百行代码所带来风险的10倍,而是要100倍左右。
-
维护接口的全正向/逆向兼容:
-
绝对不要破坏客户端代码。
-
这意味着维护多个接口版本。在一些情况下,它意味着维护多个部署:一个用于新版本,其他的用于老的版本。
-
因为小的增量变化,模型接口通常不会被修改。
-
拥有一个显式的丢弃策略。服务提供商主动把所有的用户从版本N升级到版本N+1。
操作大规模服务
作为一个服务提供商,操作多元化微服务的大规模系统中的服务是怎样的呢?
可预测的性能是基本要求。
-
大规模服务很容易出现性能上的变化。
-
性能的可预测性要远比平均性能重要的多。
-
无法保证性能的低延迟根本就不能算是低延迟。
-
当服务提供可预测的性能时,客户更容易针对服务进行编程。
-
当一个服务使用很多其他的服务来完成相关工作时,延迟最大的服务决定了该服务的性能。
-
以平均延迟为1ms、最大延迟以0.001%的概率为1s的服务为例:
-
调用一次该服务就意味着0.01%概率时间延后。
-
那么,对于一个Google所采用的包含5000台机器的大规模服务而言,延后时间概率就为50%。
-
例如,内存缓存相关的问题会以百万分之一的概率被追踪到一个底层数据结构的重新分配事件。但延迟出现大的抖动时,这个问题就会浮现到较高的层。然而,底层的细节对于大规模系统才十分重要。
深度的可靠性。
-
服务中断一般都是人为操作引起,而非硬件或软件错误。
-
一定要可以应对机器、集群和数据中心的失效。
-
当使用其他服务时,要实现负载均衡和流量控制
-
支持快速回滚操作。
-
增量部署。
-
不要一次就部署到所有的机器。选择一个系统,把新版本的软件部署上去,然后观察系统工作情况如何。
-
如果工作情况良好,就把部署的机器规模扩张到10%,然后20%,最后才是所有的机器。
-
如果部署到50%时出现问题,要能够进行回滚操作。
-
eBay利用特征选项来解耦合代码部署和特征部署。通常情况下,首先关闭特征,然后部署代码,最后再选择打开或关闭特征。这使得代码可以在新的特征打开之前被正确部署。同时,这也意味着,如果新的特征存在bug、性能问题或者业务问题,特征可以在不部署新代码的情况下被关闭。
警告可能会多,但监控却永远不会多。
反模式服务
超级服务:
-
其实,客户想要的是一个拥有小服务的生态系统。
-
超级服务难以去理清、扩展、变化,也会构造出更多的上游和下游的依赖
共享的持久化:
-
在分层模型中,服务放置在应用层,而持久化层则是一个提供给应用的通用服务。
-
eBay也采用了同样的方法,但是不管用。它破坏了服务的封装。应用程序可以通过升级数据库“黑”进服务。它最终会重新引进多个服务。共享数据库不允许松耦合服务。
-
微服务通过变得小、隔离和独立来预防该问题,也通过这种方式来保证生态系统健康成长。