原文:SERVICE-ORIENTED ARCHITECTURE: SCALING THE UBER ENGINEERING CODEBASE AS WE GROW
译者:杰微刊兼职翻译汪建
像许多初创公司一样,Uber也是从整体架构开始了它的旅程,在单一的城市构建一个单一的产品。当时,所有使用Uber的客户只能选择UberBLACK(高级轿车)选项,并且我们整个“世界”就是旧金山范围内。 当时拥有单一整体代码库看起来似乎很“干净”了,并且解决了我们的核心业务问题,这其中包括连接乘客和司机、乘客下订单、乘客支付等业务。当时把Uber的所有业务逻辑放到一个地方是合理的,但随着我们的业务迅速扩展到更多的城市,并且推出更多的新产品,这种单一架构很快就被更改。
由于核心领域模型的增长及新功能的推出,我们的组件之间变得紧密耦合,强制封装使得要解耦分离很艰难。持续集成变成了一种不利因素,因为每次部署代码库就意味着所有一切都要部署一遍。我们工程团队经历了快速地增长和扩展,这不仅意味着我们可以处理更多的请求,而且也意味着要处理更多的开发者的行为。在单一整体的代码库中添加一个新的功能、修复一个bug和解决技术债务变得非常困难。在试图改变单一结构之前必须要先有部落知识。
迁移到SOA
我们决定效仿其他高速增长的公司,例如亚马逊、Netflix、SoundCloud、推特等等公司,将单一整体的架构基于SOA拆分成多个代码库,使之成为一个面向服务的架构(SOA)。具体而言,由于SOA术语往往可以意味着不同的意思,我们采用了微服务架构。这种设计模式强制这些微服务的开发必须专用于特定的封装良好的模块域。每个服务可以使用自己的语言或框架编写,也可以有属于自己的数据库或者不用数据库。
从单一整体架构迁移到分布式SOA解决了我们很多问题,但与此同时也带来了一些新的问题,这些问题分为三个主要方面:
1、易于理解性
2、安全
3、弹性
易于理解性
在一个500+服务的环境中找到合适的服务变得很艰巨,一旦确定服务,如何使用此服务不够明显,因为每个微服务有其自己的构造方式。服务提供了REST或RPC调用方式(你可以像调用本地方法一样调用),一般都是提供比较弱的契约,而我们的情况是,这些微服务之间的契约差别很大。添加JSON模式到REST API 能提高安全性和服务开发的过程,但编写和维护也不是轻而易举的事。最后,这些解决方案没有提供关于容错或延迟的任何保证,没有一个统一的标准方式处理客户端超时和中断问题,或者保证一个服务的中断不会引发连锁中断反应。一个系统的整体弹性能力将会受到这些缺点的负面影响,正如一位开发者所说,我们“将我们单一整体架构的API转变成为一个分布式的单一整体架构的API”。
现在已经很清楚了,我们需要一个标准的通信方式,它要提供类型安全,校验和容错能力。其他的目标包括:
1、简单的方式提供客户端库
2、跨语言支持
3、可调默认超时和重试策略
4、高效的测试和开发
在我们这么高速发展阶段,Uber的工程师一直都在评估技术和工具以适应我们的目标,其中有一件事我们肯定知道的是从一开始就使用接口定义语言IDL提供大量预建的工具是比较理想的。
我们评估了现有的很多工具,发现Apache Thrift(通过Facebook和Twitter流行起来的)最能满足我们的需求。Thrift是一组创建可扩展的跨语言的服务的库和工具,要做到这点,数据类型和服务接口在一个与语言无关的文件中定义,然后,代码通过抽象服务之间的传输和RPC消息编码生成,这些服务可以使用我们支持所有语言的编写(Python、Node、Go等等)。
除了Thrift,我们创建了生命周期工具用于将这些客户端到发布打包系统(例如Python的pip和Node的npm)。除了文档和维基,服务客户端也可以作为学习工具。
安全性
对于Thrift最有说服力的论据就是它的安全性,Thrift通过绑定服务使用严格的约定从而保证了安全性,约定描述了如何与该服务进行交互,包括如何调用服务生产者,提供了什么样的输入,期望什么样的输出。在下面的Thrift IDL我们在动物园服务定义了一个makeSound函数,这个函数传入一个动物名字符串,然后返回一个字符串或抛出一个异常。
严格的约定意味着将花费更少的时间去计算服务之间如何通信和序列化处理。另外,随着微服务的发展,我们不必担心接口突然变化,并且可以从消费者上单独部署服务,这对于Uber工程师来说是非常好的消息。由于Thrift解决了安全性问题,我们可以将其他项目和工具迁移出整体架构。
弹性
最后,我们在解决灵活性问题方面的库容错性和延迟受到了和其他公司类似的挑战,例如Netflix的Hystrix库和Twitter的Finagle库。为了解决弹性问题,我们参考了这些库,并且我们编写了能确保客户端可以成功处理故障情况的库(这将在未来的文章中讨论更多的细节)。
我们的发展方向
当然,没有任何一种解决方案是完美的,所有的解决方案都面临挑战。不幸的是,Thrift的工具相对比较年轻,而且为Python和Node提供的工具并不丰富,这将导致存在需要投资大量时间去开发这些工具的风险,此外,没有更高级别的头部支持,例如认证和服务调用跟踪就是两个很有挑战性的问题,导致更高级别的元数据在每次通信都要传递。
拆除我们陈旧的单一整体架构我们已经等了很久了,虽然它一直是我们的业务过去爆炸式增长的关键组成部分,但它现在已经变得太繁琐而且难以进一步扩展和维护。
在2015年剩下的日子里,我们的目标就是摆脱这种单一整体的架构,通过我们的微服务使关系更加清晰,提供更好的组织的可扩展性,提供更多的弹性和容错能力。