它山之石:谷歌和eBay如何搭建微服务生态系统?
如果你看一下谷歌、推特、eBay和亚马逊的大规模系统,就会发现它们的架构已演变成很相似的东西:一套多语种微服务。
如果你处于多语种微服务这种终极形态,会是什么样?Randy Shoup担任过谷歌和eBay的高级职位,他就这个话题做了一次非常有趣的谈话:《大规模服务架构:来自谷歌和eBay的经验》(http://www.infoq.com/presentations/service-arch-scale-google-ebay)。
我真正喜欢Randy谈话的地方在于,他有意要把你带入到你可能没有经历过的体验中:构建、使用、保持及保护大规模架构。
在这次谈话的“服务生态系统”部分,Randy问道:拥有多语种微服务组成的大规模生态系统是什么样子?在“大规模运营服务”部分,他问道:作为服务提供者,运营这样的服务是什么样子?在“构建服务”部分,他问道:如果你是服务所有者,又会是什么样子?在“服务反模式”部分,他问道:哪些会出岔子?
一种非常有效的方法。
对我来说,这次谈话的重点是符合激励(aligning incentives)这个概念,这个主题一直贯穿整项工作。虽然从来没有明确地作为一个单独的策略提出来,但它却是一切背后的动机:为什么你希望小团队开发小的整洁服务,为什么针对内部服务的成本分摊模式如此有效,架构如何不借助架构师也可以发展,整洁设计如何从自下而上的方法发展而来,以及标准如何不借助特设委员会也可以演进。
我学到的是,无论是扩展大型动态企业的规模,还是扩展大型动态代码库的规模,有意符合激励是关键所在。落实的激励机制可以在没有明确控制的情况下推动工作开展,就好比如果你解除锁定、不共享状态、通过消息交流,并将一切并行化处理,分布式系统里面的更多工作可以完成那样。
让我们看看当下如何构建大规模系统......
大规模系统最终演变成了看起来非常相似的东西:一套多语种微服务。多语种意味着微服务是用不止一种语言来编写的。
eBay始于1995年,它现在用的是第五代架构,看你怎么计算了。
最初是一个整体式Perl应用程序,创始人在1995年的劳动节周末编写出了该应用程序。
后来换成了一个整体式C++应用程序,最终一个DLL文件里面有 340万行代码。
之前的经验促使eBay换成了一种采用Java的极其分布式的分区系统。
今天的eBay大量使用Java,不过拥有一套多语种微服务。
推特的演进过程很相似。它现在用的是第三代架构,看你怎么计算了。
最初是一个整体式Ruby on Rails应用程序。
后来换成了这种组合:前端是JavaScript和Rails,后端大量使用Scala。
最终,推特换成了我们今天所说的一套多语种微服务。
亚马逊的发展过程大同小异。
最初是一个整体式C+ +应用程序。
后来换成用Java和Scala编写的服务。
最后使用一套多语种微服务。
拥有多语种微服务组成的大规模生态系统是什么样子?
在eBay和谷歌,成千上万个独立的服务都协同工作。
现代大规模系统按照关系图组合服务,而不是按照层次体系或一套层次组合服务。
服务依赖其他许多服务,同时被许多服务所依赖。
较旧的大规模系统通常按严格层次加以组织。
这些运行最佳的系统与其说是智能设计,还不如说是进化的产物。比如在谷歌,从来不采用自上而下的集中式方法来设计系统,设计方法而是以一种非常自然的方式不断演进、发展。
变异和自然选择。需要解决问题时,就创建一种新的服务,或者更常见的是从现有的服务或产品中提取。只要服务被使用,只要服务提供价值,服务就会存活下来,要不然服务被废弃。
这些大规模系统是自下而上发展起来的。简洁设计是一个新的属性,而不是自上而下设计的产物。
不妨考虑谷歌应用程序引擎(Google App Engine)的一些服务层。
Cloud Datastore(NoSQL服务)建立在Megastore(大规模分布式结构化数据库)上,Megastore建立在Bigtable(集群级结构化服务)上,Bigtable建立在Colossus(下一代集群文件系统)上,而Colossus又建立在Borg(集群管理基础设施)上。
层次整洁。每一层增加了不属于下一层的东西。它不是自上而下设计的产物。
它是自下而上构建的。先构建谷歌文件系统Colossus。几年后,构建Bigtable。又几年后,构建Megastore。再过几年后,Cloud Database迁移到Megastore。
你可以轻松地实现关注点分离( separation of concerns),不需要自上而下的架构。
这是没有架构师的架构。在谷歌,没人拥有架构师的头衔。技术决策不需要集中批准。大多数技术决策由每个团队为自己的目标做出,这种决策是局部性的,而不是全局性的。
形成对照的是2004年的eBay。当时eBay有一个架构审查委员会,负责批准所有大规模项目。
只有为时太晚、无法改变项目时,该委员会通常才会介入。
这个集中审批机构成了瓶颈。通常它唯一的影响力就是在最后一分钟说不。
为了处理这种情形,对eBay来说一种更好的办法是,将审查委员会那些有经验有才干的人拥有的知识编码成每个团队可以重复使用的东西。将这种经验编码成知识库或服务、甚至一套指南,那样人们可以独自使用,而不是在最后一刻才参与到过程。
在没有集中控制的情况下,最后有可能实现标准化。
服务和基础设施的通用部分之间的通信方面往往出现标准化。
标准之所以成为标准,是因为它们比代替方法更合适。
通信方面通常标准化的部分有:
网络协议。谷歌使用了一种名为Stubby的专有协议。eBay使用REST。
数据格式。谷歌使用Protocol Buffers(协议缓冲区)。eBay往往使用JSON。
接口模式标准。谷歌使用协议缓冲区。JSON则有JSON模式。
通用基础设施方面通常标准化的部分有:
源代码控制
配置管理
集群管理器
监测系统
警报系统
诊断工具
所有这些组件都会不断发展。
在不断进化的环境,标准通过这些机制来执行:代码、鼓励、代码审查和代码搜索。
鼓励最佳实践的最简单方法就是通过编写实际代码。重点不是自上而下的审查,也不是前期设计,而是有人编写让工作容易完成的代码。
通过团队提供代码库来进行鼓励。
还可以通过你想依赖、支持X协议或Y协议的服务来进行鼓励。
众所周知,在谷歌,每一行代码都要签入(checked-in),以控制源代码,至少另外一位程序员对此进行审查。这是传播通用最佳实践的一种好方法。
除了少数例外,谷歌的每个工程师都可以搜索整个代码库。程序员试图寻找解决方案时,这带来了显著的附加值。如果公司有1万名工程师,如果你试图想做某事,别人很可能已经做过了。这样一来,始于一个地方的最佳实践就可以通过代码库传播开来,不过错误也会传播开来。
鼓励通用最佳实践和标准化惯例让人们很容易做对事,很难做错事。
单个服务彼此独立
在谷歌,服务内部并没有实现标准化。在外面看来,服务就是个黑盒子。
是有惯例和通用库,但是没有编程语言方面的要求。常用的语言有四种:C++、Go、Java和Python。许多不同的服务是用不同语言编写的。
框架或持久性机制方面也没有实现标准化。
在成熟的服务生态系统中,我们规范的是图形的弧,而不是节点本身。定义通用形状,而不是通用实施方法。
新服务的用途已经得到证明时才创建。
通常为某种特定的使用场合构建某种功能。后来发现,该功能具有普遍性,很有用。
成立团队,并启用服务,只适用于自己的独立部门。
只有某种功能很成功,并适合其他许多使用场合时,才出现这种情况。
这种架构因实用而发展。没有人会高高在上地说应添加某服务。
谷歌文件系统支持搜索引擎。分布式文件系统一般更有用,这不足为奇。
Bigtable最初支持搜索引擎,但用途广泛得多。
Megastore当初作为谷歌应用程序的存储机制而构建,但用途更广泛。
谷歌应用程序引擎本身是由一小群工程师开发的,他们认识到需要帮助构建网站。
Gmail来自一个内部极其有用,然后供外部其他人使用的附带项目。
如果某服务不再使用,会出现什么情况?
可另作他用的技术会重新使用。
人员可能被解雇,或调派到其他团队。
Google Wave在市场上没有取得成功,但它的一些技术最后出现在Google Apps中。比如说,多人编辑文档的功能来自Wave。
更常见的情况是,核心服务历经多代后,老一代服务被废弃。这种事在谷歌经常发生。
如果你是服务所有者,在大规模多语种微服务系统中构建服务,会是什么样子?
大规模架构中一种运作良好的服务具有下列特点:
单一用途。它会有一个明确定义的简单接口。
模块化、独立性。我们可以称之为微服务。
不共享持久层。这点后面会有详述。
满足客户的要求。提供必要的功能,具有适当的质量级别,满足约定的性能级别,保持稳定性和可靠性,同时不断改进服务。
以最少的成本和精力满足要求
这个目标符合激励,并且鼓励使用通用基础设施。
每个团队的资源很有限,所以充分利用久经考验的通用工具、流程、组件和服务有利于每个团队。
它还激励良好的操作行为。实现服务构建和部署自动化。
它还激励优化资源的高效使用。
谁运行,谁构建
从服务的设计、开发、部署直到停用,团队(通常是小团队)全权负责。
没有单独的维护或支持技术团队。
团队可以自行选择技术、方法和工作环境。
团队对自己做出的选择负责。
服务的使用范围有限
团队的认知能力是有限的。
没必要了解生态系统的所有其他服务。
团队只要深入了解其服务和依赖的服务。
这意味着团队极其小巧灵活。一个典型的团队只有三五人。(捎带提一下,美国海军陆战队的一个火力小组只有四人。)
团队规模小意味着内部沟通起来畅通又高效。
康威定律(Conway’s Law)对你有利。通过采用小团队组织方式,你最终会得到小型单个组件。
服务之间的关系好比是供应商与客户的关系,尽管你是在同一个公司。
友好待人、乐于合作,但是关系要有条理。
所有权方面非常明确。
谁负责什么服务方面非常明确。在很大程度上,关键是定义明确的接口,并维护接口。
符合激励,因为选择要不要使用某种服务由客户说了算。这鼓励服务公平对待客户。这是最终构建新服务的方式之一。
定义服务级别协议(SLA)。服务提供者向客户承诺某种级别的服务,那样客户就能依赖其服务。
客户团队为服务付费
对服务收费符合经济激励。这激励双方都极其高效地使用资源。
东西免费的话,我们往往不重视它们,也往往不会优化。
比如说,内部客户免费使用谷歌应用程序引擎(GAE),结果他们使用了大量资源。结果证明,要求他们更有效地使用资源也不是好办法。实行成本分摊后一星期,只进行了一两个简单的变化,他们就将资源使用量减少了90%。
倒不是说使用GAE的团队很坏,他们只是有其他优先事项,所以缺乏动机来优化对GAE的使用。结果表明,使用更高效的架构,他们实际上得到了更短的响应时间。
收费还激励服务提供者保持高质量,不然内部客户可能另辟蹊径。这直接激励良好的开发和管理实践。代码审查就是一个例子。谷歌的超大规模构建和测试系统是另一个例子。谷歌每天执行数百万次自动化测试。每当代码被添加到代码库,需要对所有相关代码进行验收测试,这可以帮助所有小团队保持服务质量。
成本分摊模式鼓励进行小的增量式改动。小改动更容易理解。此外,代码改动的影响是非线性的。改动1000行代码的风险不是比改动100行代码大10倍,更可能是大100倍。
保持接口的完全向后/向前兼容
永远不要破坏客户代码。
这意味着要维护多个接口版本。在某些糟糕的情况下,这意味着要维护多套部署环境,一套用于新版本,其他的用于旧版本。
通常由于小的增量式改动,样板接口并不变化。
要有一项明确的废弃政策。然后,服务提供者会有强烈的动机,将所有客户从版本N迁移到版本N+1。
作为服务提供者,大多语种微服务组成的大规模系统中运营服务是什么样子?
稳定的性能是一个要求
大规模服务很容易受到性能变化的影响。
性能稳定性比平均性能重要得多。
如果延迟低,但性能不一致,实际上根本不是低延迟。
如果服务提供一致的性能,客户针对服务进行编程要容易得多。
由于一种服务使用其他许多服务来执行工作,尾性能(tail latency)对性能起了决定性影响。
假设某服务的中位数延迟为1ms,and at the 99.999%ile (1 in 10,000) ,延迟是1秒。
调用一次意味着你慢了.01%的时间。
如果你使用5000台机器,就像谷歌的许多大规模服务那样,那么你就慢了50%的时间。
比如说,memcached方面一个百万分之一的问题被追查到某个低层数据结构重新分配事件。延迟增加后,这个罕见问题就会在更高层面出现。事实证明,像这样的低层细节在大规模系统中极其重要。
深度弹性
相比硬件或软件故障,人员引起的服务中断其可能性大得多。
机器、集群和数据中心出现故障后,能迅速恢复。
调用其他服务时,做到负载均衡,并提供流量控制。
能够迅速回滚变更。
增量式部署
使用Canary系统。不一次部署到所有机器上。选择一个系统,把某个软件的新版本放到这个系统上,看看它在新环境下的表现。
如果运行正常,开始分阶段部署。先安装到10%的机器上,然后安装到20%的机器上,最后安装到其余机器上。
如果部署到50%的机器上时出现了问题,那么你应该能够回滚。
eBay使用功能标志(feature flag),将代码部署与代码部署分离开来。代码部署时通常某功能被关闭,然后该功能可以打开或关闭。这确保代码在新功能打开之前正确部署。这还意味着,要是新功能有缺陷、性能问题或业务故障,那么就可以关闭该功能,不必部署新代码。
你可能有太多的警报,但不可能有太多的监测。
大型服务
大型服务是指做太多工作的服务。你需要的是由很小整洁服务组成的生态系统。
做太多工作的服务就是另一个整体式对象。很难推理、很难扩展、很难改变,还会带来过多的上下游依赖关系。
共享持久性
在层次模式中,服务被放入到应用程序层,持久层作为一种通用服务提供给应用程序。
eBay就是这么做的,但没成功。它破坏了服务的封装。应用程序可能通过更新数据库,给服务留下后门。最后重新带来了服务耦合。共享式数据库不允许松散耦合的服务。
微服务由于具有小型、隔离、独立的特点,避免了这个问题;你想要确保生态系统健康发展,就要做到小型、隔离、独立。
新闻来源:
云头条编译|未经授权谢绝转载