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

我们从底层开始讲起。
底层:平台
上篇关注Uber技术平台,即所有支撑着更广泛的其它Uber技术团队的东西。平台团队打造和维护这些技术,让其他团队可以更好地构建程序和功能,以及你使用的App。
基础设施和存储
我们的业务运行在混合云模型上,使用不同的云服务提供商和多处同时提供服务的数据中心。如果一个数据中心出了故障,我们的核心Trip数据会自动完成故障转移,切换到另一个数据中心继续提供服务。我们会尽量把每个城市的数据放到离它地理上最近的数据中心,但每个城市的数据都会在另一个不同的数据中心有备份。这意味着我们所有的数据中心都在同时提供着在线的Trip数据服务,我们并没有传统意义上的“备份”数据中心。要在这样的基础设施上部署程序,我们同时使用了各种内部工具和Terraform。
我们对存储的需求随着业务量的增长而变化。公司刚创立时我们只有一个Postgres数据库实例,但当我们快速发展之后,我们需要增大可用的磁盘存储量和减小系统响应时间。

2014年夏天,Mezzanine项目把系统重构成了上图所示的高级架构。
我们现在使用Schemaless(基于MySQL自行研发),Riak和Cassandra。Schemaless用于数据持久化存储,Riak和Cassandra满足高可用性和低延迟的需求。慢慢地,Schemaless实例会替换掉单个的MySQL和Postgres实例,而Cassandra会因为速度和性能而替换掉Riak。我们使用Hadoop来存储和分析复杂数据。在这些数据库之上,我们西雅图的工程师们还在开发着一个新的实时数据平台。
我们用Redis作缓存和队列。Twemproxy实现了一致性哈希算法,提供了缓存层的扩展性而又没有牺牲缓存命中率。Celery的团队就是用这些Redis实例来处理异步工作流操作的。
日志
我们的服务会与彼此及手机进行交互,这些交互信息对于内部技术调试以及业务上的动态调价等功能都非常有价值。为了支持日志功能,我们使用了好多套Kafka集群,在数据被Kafka淘汰之前会最终归档到Hadoop和Web服务的文件存储。数据还会被各种服务实时消费掉,并在ELK上为搜索和可视化功能创建索引(ELK是Elasticsearch,Logstash和Kibana的缩写)。
App部署
借助于Aurora的长时间运行服务和定时任务,我们用Mesos的Docker容器来做微服务的一致性可伸缩配置。我们有个基础设施团队——应用平台组——实现了一个模板库,可以把服务构建成可部署的Docker镜像。
路由和服务发现
我们的面向服务架构(SOA)让服务发现和路由功能变得对Uber的成功至关重要。服务必须能在我们的复杂网络中与彼此通信。我们用了HAProxy和Hyperbahn来一起解决这个问题。Hyperbahn是Uber开发的一系列开源软件之一:Ringpop、TChannel和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用作持续集成。我们把Packer、Vagrant、Boto和Unison组合在一起来打造在虚拟机上进行构建、管理和开发的工具。我们用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的现场可靠性保证。
可观测性
可观测性是要保证Uber作为一个整体,或者它的各个不同部分都是正常工作的。这个功能主要由我们纽约团队开发,有许多个系统来作为Uber遍布全世界的技术系统的眼睛、耳朵和免疫系统等。
遥感勘测
我们用Go语言开发了M3来收集和存储Uber技术系统(每台服务器、每个服务和每一行代码)的各项指标。
收集到数据之后,我们会查看趋势。我们修改了Grafana来更容易的通过仪表盘和图形来展示和上下文紧密相关的信息。每个看着仪表盘的工程师都有自己想观察的东西,有的是某个特别城市或区域的数据,有的是若干个实验,或者是某个新产品的数据。我们给Grafana加上了数据分片的功能。
异常检测
我们自行研发了异常检测工具Argos,用于把输入指标和基于历史数据生成的预测模型做对比,判断当前数据是否超出了正常的界限。

我们把由异常值检测算法每小时算出的动态阈值(红线和黄线)与输入数据做对比。我们还画上了实际的每分钟产生的真实数据(蓝线),这部分当然不能提前画上。这些阈值都会紧密的跟进着实际指标的模式。
根据指标做出的反应
Uber的工程师们在看到了实时信息和阈值(不管是静态的还是Argos动态生成的)之后,就可以用工具μMonitor来采取行动了。如果某个数据流越界了,也就是说Trip数据在某个城市下降到某个阈值之下了,这条信息就会被发送给常见行为网关。这是我们自动响应系统的功能。除了在发生故障时给工程师们打电话之外,它也会做些事情来缩短故障持续时间。如果某次部署发生问题,回滚操作也是自动的。
我们有许多观测工具都是限于Uber内部使用的,因为它们和我们的基础设施太相关了。不过我们也希望能把通用部分抽象并开源出来。
创造性地使用数据
Storm和Spark会把数据流处理成有用的业务指标。我们的数据可视化团队也有可重用的架构和应用来处理可视化数据。

表格和置信区间的可视化增强了我们的A/B测试平台Morpheus的功能
地图和实验团队都依靠数据可视化技术来把数据变成清晰、可感知的信息。城市运营团队可以直接实时地看到他们的司机以车的形式在地图上移动,而不是枯燥地做些SQL查询来了解情况。
我们用JavaScript(ES5和ES6)和React来构建了数据产品,作为我们的核心工具。我们也在我们的可视化模块中使用了各种标准图像技术:SVG、Canvas 2D和WebGL等。我们已经开源了许多库,比如我们就是依赖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信息来判断乘客的位置,再辅助以各种其它信息,根据整体行程对乘客的上车地点做出推荐。
면책 조항: 본 게시글에 표현된 견해는 전적으로 작성자의 견해이며 Followme의 공식 입장을 대변하지 않습니다. Followme는 제공된 정보의 정확성, 완전성 또는 신뢰성에 대해 책임을 지지 않으며, 서면으로 명시적으로 언급되지 않는 한 해당 내용을 기반으로 취해진 어떠한 조치에 대해서도 책임을 지지 않습니다.
