点击上方 "编程技术圈"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

日英文

Tough people aren't born that way, they become that way when no one's there to wipe their tears away.

人并非生来就坚强,没人替自己擦泪,就只能变得坚强。

每日掏心话

你要知道,人生并没有那么多的公平可言。人生走过一段路,风景毕竟不相同。

责编:乐乐 | 来自:架构头条

编程技术圈(ID:study_tech)第 1160 次推文

往日回顾:基于SSM框架实现的支付宝支付功能

     

   正文   

作者 | McLaren Stanley

译者 | 王者

策划 | 万佳

本文讲述了数年前,在高速扩张的背景下,Uber 工程团队为解决技术问题而重写应用程序的“噩梦般”经历。

1高速扩张的隐忧

2016 年,特朗普还没当上美国总统,“删除 Uber”运动还未爆发,Travis Kalanick 还是 Uber CEO。那时,Uber 还处在国际化扩展的高速增长期,业务蒸蒸日上,公众情绪非常积极。

但是,高速扩张不可能一直风平浪静,Uber App 开始出现一些问题。那个时候,Uber 工程团队的规模几乎每年都在翻倍增长。当一家公司以如此快的速度增长,最终要面临的是令人难以置信的技术性爆炸问题。

再加上团队提倡的“让开发者放手去干”的理念,我们的应用架构变得既复杂又脆弱。Uber 当时非常注重客户端逻辑,所以应用程序会出现很多问题。我们一直在做热修复,不断发布版本,设计的扩展性也变得很差。

2噩梦开始:重写应用程序

因为这些问题的出现,公司各个层面开始出现一种运动,主要的想法是“从头开始重写应用程序”。人们普遍认为,我们的架构正在拖累我们,只有重新开始才会让我们走得更快。因此,Uber 成立了一个团队,为新 App 构建全新的移动架构。这个团队的目标是构建一个能够“在未来 5 年内支撑 Uber 移动开发”的架构。

我们要同时支持两个平台,产品和设计也重新来过。在 iOS 平台方面,这次重写为采用 Swift(当时 Swift 的版本是 2.x)带来了机会。Uber 之前也尝试过 Swift,但早期使用过它的人都知道,它存在的问题比较多,所以在重写之前就被禁止了。

不过,架构团队的总体感觉是,当时 Swift 的大多数问题都集中在与 Objective-C 的互操作性上,所以如果我们开发的是一个纯 Swift 应用,就能规避这些问题。

架构团队希望在 Android 和 iOS 这两个平台上使用相同的架构模式。Android 团队都是 RxJava 的忠实粉丝,而 Swift 也有一个支持函数式编程的 RxSwift 库。于是,这个由设计、产品和架构组成的核心团队在一个房间里工作了几个月,使用新的函数式和反应式模式、新的编程语言开发新的应用程序,一切都进行得很顺利。

这个架构高度依赖了 Swift 的高级语言特性。新的 UI 设计为 Uber 不断增长的产品提供支持,函数式编程非常强大(虽然学习曲线有一定的坡度),新的架构以我们的新实时流网络协议为基础。

几个月后,通过一系列演示,这种势头逐渐形成。这个项目看起来很成功。他们在很短的时间内与少数工程师一起创造了令人惊叹的体验,核心产品的大部分功能都已经完成。

于是,在全公司范围内的推广开始了。各个团队开始将更多的功能引入到新 App 中。最初,新应用带来的兴奋感激发了他们的积极性和生产力。新架构的主要特点是功能隔离,可以让团队快速开发新功能。

3问题不断:开发速度变慢、App 启动时间变长......

但是,使用 Swift 的工程师数量一旦超过 10 个,开发速度就会慢下来。当时,Swift 编译器仍然比 Objective-C 慢得多,因此构建时间大大增加,甚至几乎无法进行调试。

有一个 Uber 工程师在 Xcode 中输入了一行代码,等了 45 秒之后,字母才慢慢地、一个接一个地出现在编辑器中。

随后,我们又遇到动态链接器问题。那个时候,我们只能动态地链接 Swift 库,而链接器的执行时间是多项式时间,苹果建议单个二进制文件的最大链接库数量是 6,而我们有 92 个,而且还在不断增加。因此,在点击应用图标后,需要 8 秒到 12 秒才开始调用主函数。新 App 的启动速度比老款还要慢。

紧接着的是 App 的文件大小问题。

当这些问题开始出现时,我们已经走过了可以回头的临界点。

此时,整个公司都将精力倾注在新 App 上。数千人参与其中,花费数百万美元(我不能告诉你确切的数字,但肯定比你想象的多),管理层已经完全相信一切尽早掌握之中。

4搞定各种难题

我私下里和主管提过“我们必须停下来”的话题。但他告诉我说,如果这个计划失败,他就要卷铺盖走人。他的老板,老板的老板,一直到副总裁,都要走人。没有回头路了!

所以我们撸起袖子,让最优秀的人负责处理每一个棘手的问题(动态链接、二进制文件大小)。

我们很快发现,将所有代码放到主文件中就可以解决 App 启动时的链接问题。但我们都知道,Swift 的命名空间与框架是混合在一起的,如果要这么做,就需要修改大量的代码,包括检查命名空间。

这时,聪明的 Richard Howell 发现,在读取 Xcode 的构建输出时,可以在构建完成后用自定义脚本将所有中间目标文件重新链接到主文件。由于 Swift 在编译时将对象命名空间转换为符号名称,这意味着他可以安全地保留命名空间。于是我们可以静态链接库,并将之前的时间从 10 秒减少到 0。

下一个是 App 大小问题。当时,我们计划将新 App 包含在旧 App 包中,并一步一步“安全”地发布出去。为节省空间,我们做的第一件事就是移除旧 App。我们将这种策略称为“Yolo”,由当时的 CEO 做的决定。

我们还用类替换了 Swift 的结构体。由于对象扁平化以及复制和自动初始化需要额外的机器代码,值类型通常需要大量的开销,所以替换掉结构体为我们节省了一些空间。

但随着 App 的不断发展,很快就达到了二进制文件(iOS 8 和更早的版本)的下载限制 (100MB),这意味着有大量用户无法注册。

此时距离公开发布日期只有几周时间。我们得到一家公司的帮助,但他们不能解决我们的问题。我们唯一能做的就是为 Objective-C 重新生成所有的模型代码(占总代码总量的 25%)或放弃支持 iOS 8。iOS 9 引入了新架构,可以把大小降到原来的一半。因为留给我们的时间只有一周了,所以我们决定放弃支持 iOS 8。

我们的普遍想法是,iOS 9 版本的二进制文件大小减小了一半,所以我们仍然拥有足够的空间,可以在重写完成后,在未来的某个时间解决问题。不幸的是,我们完全想错了。

在 App 发布后,我们举办了一个盛大的派对。新 App 受到了媒体的好评,它快速、时髦、设计新颖。

一群人得到了升职。我们都松了一口气。在连续奋战了 90 个礼拜之后,我们消停了几个星期。

5更糟糕的事情发生了

随后,公众的情绪开始发生转变。新 App 的设计核心是让用户先进入到目的地,这样他们就可以预先知道打车价格。如果不手动选择位置,就会以最后接收到的 GPS 位置为准。但这个非常不准确(尤其是在高楼林立的城市),司机可能会走错街区。这是一种很糟糕的用户体验。

为了改进位置获取功能,我们修改了位置权限,在后台收集位置信息,这样就可以把司机派到用户当前的位置。但人们被这个做法惊到了。我的一些 Twitter 旧同事建议我离开这家会追踪用户位置的“坏”公司。受到“惊吓”的人们关闭了手机的位置权限,但新 App 并没有相应的解决办法。

在公众号后端架构师后台回复“架构整洁”,获取一份惊喜礼包。

我们赶紧想办法讨论对策。我们想过关闭后台位置收集,但这样会破坏用户体验。

在特朗普入主白宫后(这是在新 App 发布三个月后),这个问题引发了连锁反应,导致“删除 Uber”运动的爆发。

在这段时间里,Swift 代码量一直在快速增长。问题的持续存在和缓慢的开发环境在 Uber 的 iOS 工程师中形成了两个敌对派别,我称它们为“Swift 狂热派”和“Objective-C 顽固派”。外部的压力和内部的派系斗争让气氛变得高度紧张。Swift 狂热派否认 Swift 所造成的问题。这些坏脾气的人抱怨着一切,却不怎么提供解决方案。

正是在这个时候,我们遇到了 App 大小的问题。我做好随时待命的准备,而发布团队在提交 App 时遇到了麻烦。事实证明,我们针对动态链接问题提出的解决方案创建的主文件对于某些平台来说太大了。在临时解决了这个问题后,我们做了一些调查,发现编译的代码大小以每周 1.3 MB 的速度增长。如果我们不采取行动,在 3 周内就会达到手机下载的上限。

但因为内部斗争太过激烈,我们被“无视”了。一位技术负责人写了两页的材料,试图证明手机下载限制并不是个问题。

我们的一名数据科学家设计了一个测试,人为地将架构的一部分推到限制阈值,并观察对业务指标的影响。在接下来的一个星期,我们把之前的部分下架,再把另一个部分推到限制阈值。

结果是灾难性的,这种做法对业务的负面影响比 Swift 重写的成本要大几个数量级。事实证明,很多人在第一次下载 Uber App 时就使用了手机网络。

我们组建了另一支突击团队。我们开始反编译目标文件,并逐行检查,看看为什么 Swift 代码生成的文件体积会这么大。我们删除了一些没有被使用的特性,并把 watchOS 应用重新改回了 Objective-C。

我们几乎达到了极限,精疲力尽,但每个人都努力打起精神。这是真正优秀的工程师开始散发光芒的时刻。阿姆斯特丹的一名开发人员想到了重新优化编译器 pass。关于编译器的 pass,我需要解释一下。

现代编译器会对代码进行大量的 pass,例如 pass 内联函数,或者用值来替换常量表达式。根据执行顺序的不同,可能会得到更小体积的机器码。

如果内联函数碰到一个常量,编译器就会知道,并进行替换。于是,如果先进行内联,

int x = 3
func(x) {
X + 4
}

就会变成常量 7,这样生成的机器码就更少。

如果内联是后进行的,就无法推断函数体,会生成更多的机器码。当然,这完全取决于你所写的代码是什么样的,因此很难对 pass 的顺序进行通用的优化。

阿姆斯特丹的这位工程师在构建过程中使用退火算法来重新排序编译器优化,最小化生成的机器码。这减少了 11MB 的机器码,为我们提供了足够的空间继续开发功能。

但这却吓坏了 Swift 编译器工程师,他们担心未经测试的编译器优化命令会导致未经测试的 bug(即使每个 pass 都被认为是安全的,但很难推断出可能出现的组合)。不过,我们并没有遇到什么大问题。

我们也尝试了一些其他的解决方案,并按照开发周数来测算它们给我们带来的好处。但我们发现,真正的问题是增长曲线,它总是让我们的努力“功亏一篑”。

最终,我们让苹果将手机下载限制提高到 150MB,他们还添加了一些编译器选项 (-Osize),帮我们进行文件大小优化。Swift 团队也承认,Swift 编译器不可能像 Objective-C 编译器那样将文件编译到很小。

但到了 2020 年,他们将 Swift 编译生成的机器码大小降至 Objective-C 的 1.5 倍,并将下载限制提升至 200MB 的可选上限。这足够让我们再撑好几年了。

如果不是因为苹果提高了上限,我们将被迫重新回到 Objective-C。最终,我们也解决了其他问题。聪明的 Alan Zeino 和他的团队让 Uber 的 BUCK 构建系统支持 Swift,极大地加快了构建速度。

一路下来,我们的很多同事都感到精疲力竭。Uber 花了一大笔钱,也吸取了惨痛的教训,但直到今天,大多数人仍然坚持认为 重写 是值得的。新加入的工程师喜欢新架构的一致性,但他们并不知道我们为了实现这一目标经历了怎样的痛苦。

社区也从我们的经历中受益。Ellie 做了一个很棒的演示,并通过巡回演讲来分享我们的经验。我用我的经验去教其他团队如何做出更好的决策。

6写在最后

我认为,计算机科学当中的一切东西都存在一种权衡,不存在所谓的通用的高级语言。无论你做什么,都要明白你为什么要这么做,不要让它演变成各派固执己见的政治斗争。

设立好故障点。如果你意识到自己犯了一个错误,你要弄清楚如何做出权衡,并给自己一条出路。你陷在错误决策中的时间越长,成本就越高。不要做一个对解决问题没有贡献的坏脾气的人,不要做一个给别人制造更大问题的狂热者。与我共事过的那些优秀的工程师们都很善于避免落入这两个陷阱。

原文链接:

https://threadreaderapp.com/thread/1336890442768547845.html

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

欢迎加入后端架构师交流群,在后台回复“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

淘宝开源代码质量检测工具!

Jetbrains出品:一款号称最适合程序员的编程字体

一个月薪 12000 的北京程序员的真实生活 !

BAT等大厂Java面试经验总结

别找了,想获取 Java大厂面试题学习资料

扫下方二维码回复「手册」就好了

嘿,你在看吗

我在Uber亲历的最严重的工程灾难相关推荐

  1. iOS 优化 - 瘦身

    前言 iOS 优化将是一个专题,其中会包括包体积优化(瘦身).启动时间优化.UI 优化等等.那么这个专题的开篇就从瘦身开始吧. APP 的大小是分为 APP 下载大小和安装大小两个概念的. 下载大小是 ...

  2. 大规模集群中Docker镜像如何分发管理?试试Uber刚开源的Kraken

    Docker注册表的主要目的是存储和分发Docker镜像,看似是一个相对简单的任务,但是如果遇到了像Uber这样的大规模计算集群,就很容易成为可伸缩性的瓶颈.在多区域和混合云系统的计算环境中,镜像分发 ...

  3. 明星企业Argo AI倒下:曾估值超70亿美元 烧掉数十亿美元

    雷递网 雷建平 10月27日 曾估值超70亿美元,并酝酿上市的自动驾驶初创公司Argo AI走向了灭亡. Argo AI日前发布一份声明,称福特汽车公司决定需要投资在短期内更容易实现的驾驶辅助技术,而 ...

  4. 【好书荐读】《世界因你不同——李开复自传》

    [内容简介] 这是李开复唯一的一本自传,字里行间,是岁月流逝中沉淀下来的宝贵的人生智慧和职场经验.捣蛋的"小皇帝",11岁的"留学生",奥巴马的大学同学,26岁 ...

  5. 五万美元打造一个fackbook

    五万美元打造一个fackbook BY 月上河 · 十二月 12, 2013 创业阶段许多创业人都纷纷抱怨没钱,或者没有足够的钱来打造一个让人满意的产品.尤其是在中国,创业层基本上都是这种现状,那么打 ...

  6. 3年65倍增长,我在Uber学到的三堂课

    很多比我更专业的同事已经在各种渠道分享过Uber的运营和增长经验了.对我而言,我更想谈论的是一套维持高速增长的系统构成,以及可以应用在各个行业的基本规律. 三月二十六日,周一,阴,我在办公室的最后一天 ...

  7. 成都Uber优步司机奖励政策(1月9日)

    1月9日 奖励政策 滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblog ...

  8. uber_这就是我本可以免费骑Uber的方式

    uber by AppSecure 通过AppSecure 这就是我本可以免费骑Uber的方式 (Here's how I could've ridden for free with Uber) 摘要 ...

  9. Uber 前无人驾驶工程师告诉你,国内无人驾驶之路还要走多久?

    受访者 | Graviti 创始人&CEO 崔运凯 记者 | Aholiab,编辑 | Carol 出品 | AI科技大本营(ID:rgznai100) 经过数年的发展,现在的人们谈到&quo ...

最新文章

  1. rails3 ajax替换成js
  2. Jersey Restful部署到Tomcat注意事项
  3. phalcon:跟踪sql语句
  4. java接口如何接受语音参数_Java 是如何优雅地实现接口数据校验的?
  5. 7种有害的IT团队行为,不根除就坏大事了
  6. flutter RotationTransition实现旋转动画
  7. IPV6 IPV4双栈互通与静态路由
  8. Mybatis_day3_Mybatis的多表查询
  9. 互联网产品的测试策略应该如何设计?
  10. java方法分为类方法和_— Must we finish copying all these articles this morning? — No, you( )._学小易找答案...
  11. linux的使用 --- 安装node和express
  12. 计算机程序设计实验报告总结,c语言实验总结(大一c语言实验报告总结)
  13. 【弹出USB大容量存储设备时出问题】两步搞定
  14. 通过浏览器geolocation属性获取经度和纬度(1)
  15. Android经典蓝牙连接
  16. pg客户端连接报错:不支援 10 验证类型。请核对您已经组态  ..
  17. arduino八段数码管使用
  18. 996下的程序员,该如何保证自己的身体健康?
  19. 莘城苑:面包种类选择
  20. 封装一个常用的js工具类

热门文章

  1. docker安装rabbitMQ
  2. 《Javaweb医院分诊挂号管理系统》计算机毕业设计|java毕业设计|课程设计|医院分诊|医院挂号系统|医院管理系统|免费查重
  3. qt学生管理系统(一)
  4. linux无法访问root文件夹,asp.net core 2.1部署到Linux无法访问wwwroot文件夹中的资源...
  5. 二十世纪模型论发展迅猛,势不可挡
  6. 苹果cms模板_苹果cms怎么添加TV电视直播栏目?
  7. 7月9日王者荣耀服务器维护,《王者荣耀》7月9日体验服停机更新公告
  8. 程序员究竟在做什么——职业程序员的工作内容(我们不是修电脑的),以及如何转型成为一名程序员?
  9. zookeeper分布式应用程序协调服务
  10. 基于SSM的微信小程序在线视频学习平台