BT

Uber技术栈全解析之上篇:基础

作者 LUCIE LOZINSKI ,译者 足下 发布于 2016年9月18日 |

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

本文讨论Uber技术栈较底层的部分,关注其余内容请参考下篇“The Edge and Beyond”。

Uber技术

Uber的使命是要让交通可靠得像人人随处可用的自来水一样。要实现这个使命,我们生成并处理着非常复杂的数据,再把它们用平台的方式梳理得整整齐齐的,让司机可以获得订单,让乘客自由出行。

截图为2016年春季纽约、中国和印度的Uber乘客App界面

我们希望能把Uber的App UI设计得简单易用,而后台支撑系统可以很复杂,来处理复杂的交互和大量的请求。为了支撑业务量的快速增长,我们把最初的单体架构拆散成了许多小块以方便扩展。在有了数百个相互依赖的微服务之后,现在要想画出一张Uber系统的工作流程图已经相当困难,而且还演进得非常快。在这个上下两篇的系列文章中我们也只能简介2016年春天的架构。

Uber技术的挑战:没有免费用户和飞速发展

与许多非常成功的软件公司一样,我们也面临着相同的全球发展问题,但不同的是:1)我们才刚成立六年,所以我们还没能完全解决;2)我们的业务是实时地和真实世界打交道的。

2015年四月,Uber运营的300个城市在地图上的分布情况

与“免费+增值服务”的模式不同,Uber只有付费用户:过去是乘客和司机,现在又增加了外卖和导游。人们要靠我们提供的技术来赚钱和去他们要去的地方,所以我们没有可以停歇的时间。我们非常看重系统可用性和可扩展性。

随着我们的业务扩展到越来越多的城市,我们的服务也伴随着一同扩展。我们技术栈的易伸缩性鼓励不同技术之间的比较和竞争,这样最好的方案才能最终胜出,而且胜出的也不一定必须是唯一的方案。如果已经有非常好用的工具了,我们就会拿来用,直到超出了它的承受范围。当我们需要更强大的东西时,我们就自己开发。在过去的几年里Uber技术团队已经表现出了非常棒的适应性、创造力和纪律性。在2016年,我们有更大的计划。当你读到这篇文章时,肯定有很多东西已经变了,但这毕竟也是我们曾经的技术栈的一个快照。通过我们的描述,希望你能理解我们关于使用工具和技术的想法。

Uber的技术栈

我们先想像一棵树,而不是一堆的限制条件。统观Uber使用的技术栈,可以看到一些通用技术(就象一棵树干),每个团队使用得各有侧重(树枝)。所有东西都出自相同的本源,但工具和服务在各个不同方面发挥出完全不同的作用。

我们从底层开始讲起

底层:平台

上篇关注Uber技术平台,即所有支撑着更广泛的其它Uber技术团队的东西。平台团队打造和维护这些技术,让其他团队可以更好地构建程序和功能,以及你使用的App。

基础设施和存储

我们的业务运行在混合云模型上,使用不同的云服务提供商和多处同时提供服务的数据中心。如果一个数据中心出了故障,我们的核心Trip数据会自动完成故障转移,切换到另一个数据中心继续提供服务。我们会尽量把每个城市的数据放到离它地理上最近的数据中心,但每个城市的数据都会在另一个不同的数据中心有备份。这意味着我们所有的数据中心都在同时提供着在线的Trip数据服务,我们并没有传统意义上的“备份”数据中心。要在这样的基础设施上部署程序,我们同时使用了各种内部工具和Terraform

我们对存储的需求随着业务量的增长而变化。公司刚创立时我们只有一个Postgres数据库实例,但当我们快速发展之后,我们需要增大可用的磁盘存储量和减小系统响应时间。

2014年夏天,Mezzanine项目把系统重构成了上图所示的高级架构。

我们现在使用Schemaless(基于MySQL自行研发)RiakCassandra。Schemaless用于数据持久化存储,Riak和Cassandra满足高可用性和低延迟的需求。慢慢地,Schemaless实例会替换掉单个的MySQL和Postgres实例,而Cassandra会因为速度和性能而替换掉Riak。我们使用Hadoop来存储和分析复杂数据。在这些数据库之上,我们西雅图的工程师们还在开发着一个新的实时数据平台。

我们用Redis作缓存和队列。Twemproxy实现了一致性哈希算法,提供了缓存层的扩展性而又没有牺牲缓存命中率。Celery的团队就是用这些Redis实例来处理异步工作流操作的。

日志

我们的服务会与彼此及手机进行交互,这些交互信息对于内部技术调试以及业务上的动态调价等功能都非常有价值。为了支持日志功能,我们使用了好多套Kafka集群,在数据被Kafka淘汰之前会最终归档到Hadoop和Web服务的文件存储。数据还会被各种服务实时消费掉,并在ELK上为搜索和可视化功能创建索引(ELK是ElasticsearchLogstashKibana的缩写)。

App部署

借助于Aurora的长时间运行服务和定时任务,我们用MesosDocker容器来做微服务的一致性可伸缩配置。我们有个基础设施团队——应用平台组——实现了一个模板库,可以把服务构建成可部署的Docker镜像。

路由和服务发现

我们的面向服务架构(SOA)让服务发现和路由功能变得对Uber的成功至关重要。服务必须能在我们的复杂网络中与彼此通信。我们用了HAProxyHyperbahn来一起解决这个问题。Hyperbahn是Uber开发的一系列开源软件之一:RingpopTChannel和Hyperbahn都是要为服务网络增加自动化、智能性和提高性能。

传统的服务会用本地的HAProxy实例来把JSON包通过HTTP请求传送到其它服务,从前端的Web服务器和Nginx到后端服务器。这样清晰的数据传送方式就让查错变得简单,这在去年我们做的演进到新系统的几次升级中起到了关键作用。

然而,在可调试功能之上,我们更看重长期可靠性。在速度和可靠性方面,我们还可以用HTTP之外的SPDY、HTTP/2和TChannel等协议和Thrift或Protobuf等接口定义语言来方便我们演进系统。Ringpop是个一致性哈希层,提供了应用层的协作和自愈功能。Hyperbahn让服务可以简单可靠的进行相互通信,即使服务是用Mesos动态调度的。

我们现在已经迁移到了一种发布-订阅的模式(将更新发布给订阅者)上来,而不是传统的为查看是否有状态改变而要不断轮询方法。HTTP/2和SPDY都更容易实现这种推的模式。如果改用推的模式,Uber App很多现有的基于轮询的模式都会有性能上的飞跃。

开发与部署

很多内部操作都依赖Phabricator,从代码审查到生成文档,到流程自动化。我们用OpenGrok来查找代码。对于Uber开源的项目,我们直接用GitHub来跟进问题和做代码审查。

Uber技术团队一直致力于让开发活动尽可能地模拟实际生产,所以我们大多数时候都在云服务提供商的虚拟机或者开发者的笔记本电脑上进行开发。我们构建了自己的内部部署系统来管理构建活动。Jenkins用作持续集成。我们把PackerVagrantBotoUnison组合在一起来打造在虚拟机上进行构建、管理和开发的工具。我们用Clusto来做开发的清单管理,用Puppet管理系统配置。

我们也一直努力打造和维持稳定的沟通渠道,不止是服务之间,更是在工程师之间。就信息发现功能来说,我们用uBlame(git-blame的改进版)来跟踪哪个团队管理着哪种特定服务,用Whober来查询名字、相片、联系方式、组织架构等。我们内部的文档服务器会用Sphinx自动从存储服务器的信息中构建文档。企业级告警服务会提醒值班的工程师保持系统正常运行。许多开发者都在笔记本电脑上运行OSX,大多数生产环境服务器上运行的Linux都是Debian Jessie。

开发语言

在底层Uber工程师主要用的是Python、Node.js、Go和Java。最早只用了两种语言:Marketplace团队用的是Node.js,其他人用的是Python。到现在Uber运行的大多数服务仍是用这些最初的语言写的。

用Go和Java主要是出于高性能的考虑。Java利用了开源生态系统,并与Hadoop和别的分析工具等结合得非常好。Go可以带给我们高效、简单和高运行时效率。

在把最早期的代码拆成微服务时我们就开始慢慢替换掉原有的Python代码了。异步编程模式让我们有了更好的吞吐量。我们把Tornado和Python一起使用,但Go对并发的原生支持对许多非常重视高性能的新服务来说也非常合适。

必要时我们会用C和C++来写些工具(比如在系统级的高效和快速的代码)。我们会使用这些语言写的工具,比如HAProxy就是其中之一,但大多数情况下我们不会用它们。

当然,比较高些层级的代码就不是用Java、Go、Python和Node.js写的了。

测试

为保证服务可以处理我们生产环境的需求,我们开发了两个内部工具:Hailstorm和uDestroy。Hailstorm用来做集成测试,在非繁忙时间模拟峰值压力。uDestroy会故意搞坏一些东西,来测试我们对非预期故障的处理。

我们的员工都会先使用测试版的App来不断测试新功能,然后才会交到用户手中。我们有个App反馈器,用于记录发布给用户之间遇到的所有问题。每当我们为Uber App生成新版本时,这个功能都会让我们去Phabricator上加一个修复问题的任务。

可靠性

每个写后台服务的工程师都要为他的代码负责。不管谁写的代码搞坏了生产环境的东西,值班人员就会给他打电话。我们用Nagios来做监控告警,绑定到一个告警系统上。

现场可靠性工程师的任务是保证最高的可用性和每天10亿次叫车服务,他们会关注于创造和满足服务运行所需要的条件。

这里有个2016年二月的技术讨论视频,谈到了Uber的现场可靠性保证。

https://youtu.be/qJnS-EfIIIE

可观测性

可观测性是要保证Uber作为一个整体,或者它的各个不同部分都是正常工作的。这个功能主要由我们纽约团队开发,有许多个系统来作为Uber遍布全世界的技术系统的眼睛、耳朵和免疫系统等。

遥感勘测

我们用Go语言开发了M3来收集和存储Uber技术系统(每台服务器、每个服务和每一行代码)的各项指标。

收集到数据之后,我们会查看趋势。我们修改了Grafana来更容易的通过仪表盘和图形来展示和上下文紧密相关的信息。每个看着仪表盘的工程师都有自己想观察的东西,有的是某个特别城市或区域的数据,有的是若干个实验,或者是某个新产品的数据。我们给Grafana加上了数据分片的功能。

异常检测

我们自行研发了异常检测工具Argos,用于把输入指标和基于历史数据生成的预测模型做对比,判断当前数据是否超出了正常的界限。

我们把由异常值检测算法每小时算出的动态阈值(红线和黄线)与输入数据做对比。我们还画上了实际的每分钟产生的真实数据(蓝线),这部分当然不能提前画上。这些阈值都会紧密的跟进着实际指标的模式。

根据指标做出的反应

Uber的工程师们在看到了实时信息和阈值(不管是静态的还是Argos动态生成的)之后,就可以用工具μMonitor来采取行动了。如果某个数据流越界了,也就是说Trip数据在某个城市下降到某个阈值之下了,这条信息就会被发送给常见行为网关。这是我们自动响应系统的功能。除了在发生故障时给工程师们打电话之外,它也会做些事情来缩短故障持续时间。如果某次部署发生问题,回滚操作也是自动的。

我们有许多观测工具都是限于Uber内部使用的,因为它们和我们的基础设施太相关了。不过我们也希望能把通用部分抽象并开源出来。

创造性地使用数据

Storm和Spark会把数据流处理成有用的业务指标。我们的数据可视化团队也有可重用的架构和应用来处理可视化数据。

表格和置信区间的可视化增强了我们的A/B测试平台Morpheus的功能

地图和实验团队都依靠数据可视化技术来把数据变成清晰、可感知的信息。城市运营团队可以直接实时地看到他们的司机以车的形式在地图上移动,而不是枯燥地做些SQL查询来了解情况。

我们用JavaScript(ES5ES6)和React来构建了数据产品,作为我们的核心工具。我们也在我们的可视化模块中使用了各种标准图像技术:SVGCanvas 2DWebGL等。我们已经开源了许多库,比如我们就是依赖react-map-gl来做地图可视化的:

可视化证明了react-map-gl的功能,这是一个由Uber数据可视化团队开发的MapboxGL-js的封装器

我们也开发了可视化框架,让诸如R、Shiny和Python等也可以访问我们的图表组件。我们希望有在浏览器中可以运行得非常流畅的高密度数据可视化技术。要达到这样的目标,我们开发了开源的基于WebGL可视化工具

一张Uber热度地图显示了正在载客的车辆的密度。然后我们还可以把顶层的百分比数据移除掉来看底层数据。

地图

Uber地图团队非常重视数据、算法和与地图数据、展示、路由、收集和推荐地址和位置等功能相关的工具集。地图服务主要是基于Java技术开发的。

这一块量最大的服务是Gurafu,它通过许多精细的路由选项来提供了许多处理道路地图数据的功能,提高了效率和准确性。Gurafu前端是µETA,它在原始ETA之上加了个业务逻辑层。Gurafu和µETA都是基于DropWizard框架开发的Web服务。

我们的业务和客户都非常依赖高度精确的ETA数据,所以地图服务工程师会花费很多时间来保证这些系统运行得正确。我们会作ETA错误分析,来找出并修复出错的源头。在精确性之外,这个问题的级别也是很重要的:每一秒,整个公司的系统都会基于ETA信息做出非常大量的决策。由于这些请求的延迟不能超过5毫秒,那算法效率也就成了个大问题。我们要关注各种细节问题,比如分配内存的方法、并行计算和对服务器磁盘或数据中心网络等低速资源的访问。

地图服务也在支撑着所有乘客和司机App中搜索框底层的后台服务技术,包括自动补齐搜索引擎、预测引擎、反向地理编码服务等。自动补齐搜索引擎提供了对地点和地址的本地搜索功能。预测引擎使用机器学习算法来基于用户历史和其他信息预测乘客的目的地。预测要能占到用户输入目的地的大概50%。反向地理编码服务根据GPS信息来判断乘客的位置,再辅助以各种其它信息,根据整体行程对乘客的上车地点做出推荐。

除了这些,我们的文章还会涉及到与你的手机相关的技术。请继续关注我们的下篇。Uber的技术和面临的挑战都可能会变化,我们的使命和克服困难的文化不会变。你想成为这个团队的一分子吗?

另外,感谢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通知我

讨论
提供反馈
错误报告
商务合作
内容合作
Marketing
InfoQ.com及所有内容,版权所有 © 2006-2016 C4Media Inc. InfoQ.com 服务器由 Contegix提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司 京ICP备09022563号-7 隐私政策
BT

我们发现您在使用ad blocker。

我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。