BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Uber技术栈全解析之下篇:边界与上层

| 作者 LUCIE LOZINSKI 关注 0 他的粉丝 ,译者 足下 关注 1 他的粉丝 发布于 2016年10月24日. 估计阅读时间: 15 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

本文翻译自:“THE UBER ENGINEERING TECH STACK, PART II: THE EDGE AND BEYOND”,作者LUCIE LOZINSKI,已获得原网站授权。

Uber技术

Uber的使命是要让交通可靠得像人人随处可用的自来水一样。在上一篇中,我们讨论了Uber技术栈的底层部分。在这一篇中我们要讨论一下与乘客和司机相关的部分。让我们从全球市场开始,通过网页和手机讨论开来。

中间层:Marketplace

Marketplace是Uber引擎的最前端,负责把真实世界的实时请求和位置数据等送入Uber系统。数据持久存储层、匹配系统和实时交易处理等都在这里。这里也有很多关于UberRUSHUberEATS等产品的逻辑。在Uber系统中,Marketplace的可用性要求是最高的。

要理解Marketplace,非常重要的一点是要记住Uber技术栈中的各个部分是会相互作用的。最底层的基础设施支撑着它上面的所有东西,但上层的功能也会调用下层。就Marketplace而言它自身是独立的,但它上层和下层的东西都会调用到它。

与Uber其他团队一样,Marketplace自身也有着一套技术栈。在它内部,工程师们只为Marketplace自己构建基础设施和数据方案,包括数据组、集成组、前端工程师、后端工程师、还有用我们的四种编程语言(Python、Node.js、Go、Java)写成的各种服务。这种分层架构保证了Uber系统的高可用和对故障免疫。

Uber的核心Trip处理引擎最早是用Node.js写的,主要看中的是它的异步原语和简单的单线程处理。事实上我们也是敢把Node.js用到生产环境的仅有的两个公司之一。Node.js让我们可以管理大量的并发连接。但现在我们大多数服务都是用Go写的了,而且越来越多。我们看中的是Go的并发、效率和类型安全操作。

边界

我们手机App的前端API包含着超过600个无状态节点,它们把许多服务组合起来了,把手机用户输入的请求路由到别的API或服务上。所有东西全都是用Node.js写的,只有边界部分除外,NGINX前端在这里会终止SSL并做一些鉴权。NGINX前端也会借助于HAProxy负载均衡器做前端API的代理。

Marketplace的这一部分集成了许多内部基础设施原语。这个团队的工程师用开源软件logtron来向磁盘和Kafka写日志。我们用uber-statsd-client模块(stasd的Node.js客户端)生成统计信息,再与我们自己研发的M3交互(前文讲过)。

高可用、自愈和持久化

为了支持最大程度的可用性需求,Marketplace技术栈必须实时地接收和处理请求。即使这个模块发生非常短暂的停服,也会对我们的用户和业务产生非常严重的影响。Marketplace的很多技术都是团队里的工程师研发出来先给自己用的。

Ringpop是一套构建协作式分布式系统的库,它在Marketplace被推广到Uber其他团队乃至更外部之前解决了一些问题。对于开发者来说它在应用程序一级提供了与DynamoDB或Riak等分布式数据库类似的高可用、分区容忍等特性。

实时处理乘客和司机的状态数据并把它们作匹配的逻辑是用Node.js和Go语言写的。这些团队用Ringpop和Sevnup来实现功能,并在哈希环中有节点发生故障、或者有别的节点成为主键空间的主节点时做对象角色切换。

速度与吞吐量

为全公司的团队构建跨功能工具的工程师们是在Uber里用Cassandra和Go用得最多的,最主要的原因就是速度。Cassandra做横向扩展非常容易,而Go语言的编译超级快。

吞吐量对Marketplace团队来说也是非常重要的。它们必须能处理最大量的业务请求,因为所有请求都会经过Marketplace。即使是在业务量最高峰的时候,Marketplace也必须能够抗住所有压力,否则请求都压根到不了Uber的其它模块。

优化与均衡

Marketplace用动态调价、智能匹配、健康状态检查等手段来控制优化和均衡。这一块主要用的是Python写的FlaskuWSGI等,但我们为了追求更高的性能而在用Go替换Python。网络调用和I/O产生的阻塞都很诡异地拖慢了我们的服务,要能处理相同量级的请求就只好部署更多的服务。在访问后端的MySQL数据库时Python很好用,但我们已经在用Riak和Cassandra集群替换MySQL的主从架构。

看到并使用数据

Marketplace内部有个小团队是负责把Marketplace的数据可视化的,方便团队理解和观察全世界的状态。我们用JavaScript实现前端应用程序。后端用到的库和框架包括React+Flux、D3Mapbox等。就后端而言,使用的Node.js服务器也和Uber的Web工程师们用的是同一台。

Marketplace内部的数据工程师们综合使用了各种数据库、自行研发的解决方案和外部开源技术,来实现数据处理、流处理、查询、机器学习和图处理等功能。

(点击放大图像)

数据流用的是Kafka和Uber的生产数据库。Hive、MapReduce、HDFS、Elasticsearch和文件存储Web服务等都被用来做特定用途的数据存储,以及处理相应的操作请求。与大家用惯了的LIDAR不同,我们开发了它的一个变种。我们现在只在内部分享交互式数据分析记录的全量数据,运行的是JupyterHub,支撑多用户的Jupyter(IPython)Notebook,并把它与Apache Spark和我们的数据平台做了集成。Marketplace的数据团队是与Uber的数据技术团队不同的,但用的技术大部分还是重叠的。

在Marketplace之上,网页和手机端用的东西是完全不同的了。

上层:网页与手机

我们的网页端和手机端的工程师共用着许多相同的底层模块,但也还是有很多是上层独有的。这些部门的工程师们构建出来的就是你常用的App了,也会构建出给所有网页和手机工程师们共用的库和框架。这个团队最看重的是用户体验和易用性。

网页

网页团队和产品团队一起协作,构建和推广模块化的、分别部署的网页App。所有的App都有相同的用户操作接口和用户体验。

语言

网页的核心功能都是用Node.js写的。Node.js的社区非常大非常活跃,聚集了许多网页开发者。Node.js让我们可以在客户端和服务器之间共享JavaScript代码,以打造通用、同构的网页应用程序。我们客户端的程序需要Nodejs风格的模块,因此我们用Browserify来打包。

网页服务器

我们的基础网页服务器Bedrock构建在应用非常广泛的网页框架Express.js基础之上。Express.js自带了许多中间件,可以提供安全、国际化和其他Uber特有的模块,来集成基础设施。

我们自建的服务通信层Atreyu会处理后端服务之间的通信,并且与Bedrock集成。Atreyu让我们可以很容易地向SOA服务API发送请求,和FalcorRelay非常相似。

(点击放大图像)

如上图所示,司机的主页就是在坚实的Bedrock基础之上构建出来的许多Uber页面之一。

渲染、状态处理和构建

我们用React.js和标准Flux来做应用程序的渲染和状态处理,有些团队已经开始尝试用Redux来做未来的状态容器。我们也在把名为Superfine的现有OOCSS/BEM风格的CSS工具集演进成一套由CSS封装的React.js UI模块,是用样式对象构建的,和Radium类似。

我们的构建系统叫Core Tasks,是一套基于Gulp.js构建的脚本集合,用于编译前端程序并且管理版本,再推送到网页服务的文件存储上,让我们可以利用CDN服务。

最后,我们用内部的NPM注册表来管理超大量的公共注册包和只向内部广播的包。每个工程师都可以向它发布,这就让我们可以轻松地在团队间共享模块和React.js组件。

手机

Uber曾经有过非常专门的手机部门。现在公司的部门都是跨职能的,我们叫应用程序团队。每个跨职能团队的成员都有不同背景,从后端到设计到数据科学都有。

开发语言

Uber的iOS程序员是用Objective C和Swift开发的,Android程序员用Java。也有一些开发React组件。Swift有很多静态分析和编译时安全检查,所以要把代码写的有错也不容易。我们比较喜欢面向协议编程。在手机方面我们的演进方向是基于模块化的库的系统。

在不同的顶层技术方面,我们会用第三方的库,或者为了自己的特定需求而专门构建。许多现有可用的开源库都是通用的,这样会造成App安装包非常庞大。对手机程序来说,每一K字节都很重要。

Android

Android方面用Gradle作构建系统。我们用了OkHttpRetrofitGsonDagger是依赖注入框架。

我们用开源库来使UI代码简洁易用。Butter Knife让我们可以通过处理注解把视图和回调绑定到字段和方法上。Picasso提供了图像加载功能。

Espresso扩展让我们可以在IDE(我们用Android Studio)中用熟悉的Android SDK写出许多自动化代码。为了适应架构,我们用RxJava来简化对异步和基于事件的编程。日志功能我们用Timber

iOS

所有iOS代码都保存在我们用Buck构建的一台集中存储上。Masonry和有自动布局功能的SnapKit用于做组件摆放和大小适配。崩溃检测用的是KSCrash,但上报崩溃报告用的是我们自己的报告框架。我们用OCMock模仿和构造测试类来测试Objective-C代码。我们也用协议生成了虚拟类来测试Swift。

存储

我们用LevelDB做存储。后台是标准的Schemaless和MySQL,我们正在逐步向全部使用Schemaless迁移。

开发

我们主要有四个App:Android乘客端、Android司机端、iOS乘客端和iOS司机端。这意味着每个星期我们每个平台上都要有上百位工程师向代码库中提交代码,并且发布。万一发生了什么问题我们也没办法快速恢复。所以,我们要构建一些系统来让这种开发模式更可靠。

手机程序开发是百分之百确定地,大家都在主分支上进行开发,然后大家各自提交。我们也还在用Git做软件版本管理,但写App代码的工程师都是直接向主分支进行提交的。这么多人在主分支上提交代码风险是非常大的。所以我们用了自己开发的服务和程序配置平台,非常易用,也非常容易基于它进行构建,这样相关团队就可以限制自己的代码改动对Uber其他服务和业务的影响。平台使用功能标记位来在服务器端打开或关闭代码功能。我们会做灰度发布,并且仔细跟进发布的情况。

工程师们不必考虑构建窗口的问题,它们都是按照功能标记增量进行的。我们没有手工的QA流程,相反我们对自动化和监控做了很多投入。我们的持续集成让我们可以快速扩张,而监控让我们的快速响应系统可以捕获并改正任何有问题的代码。

栈外之栈

难以描述好Uber的技术栈,一部分原因是这里没有非常确定性的规则。当大家想到技术栈这个词时,脑海中往往会浮现出一根图腾柱,最下面是基础设施,最上面是用户可用的各具特色的功能。有非常清晰的层次和边界。

而Uber,则差不多每一层都有自己的一套技术栈。比如手机功能团队就前端工程师和后端工程师都是在一起工作的,而且只要能满足自己项目的需要,爱用什么数据存储方案都可以自己决定。而对于某些团队来说,比如财务相关的,相应的技术栈都是以保证自己需求为最高目的的。

总之,我们的工作有趣之处不在于我们用什么技术栈,而在于我们做实时真实交易时要进行的大规模高速处理。支撑Uber的技术一定会变,但我们的速度、可塑性、紧迫感和克服困难的动力会一直都在。听起来有意思吗?加入我们吧

一张由在旧金山随机选取的一百万条匿名行程汇总而成的街道图。较亮的是市中心。

也请阅读: The Uber Engineering Tech Stack, Part I: The Foundation

感谢Conor Myhrvold, Botswana的图片:“Chapman’s Baobab”。
Baobab(猴面包树)以它们的生命力强、长寿、粗厚的树干和树枝而闻名。喀拉哈里沙漠中的这棵猴面包树也是非洲最古老的树之一。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT