回复:选择,获取完整的视频评论。
专家介绍
滴滴出行技术总监杜欢,负责滴滴小巴业务的技术管理工作。他在互联网领域有十年工作经验,曾就职于微软、百度,两次创业。来到滴滴后,也经历了多次项目和业务的变化。他是一名“无所不知”的工程师,在前端、客户端、服务端、运维等方面都有大量实战经验。平时是个二次元宅男,也喜欢看各种技术和非技术文章,拓展视野。平时不太愿意主动说话,但一旦放松下来,就说个不停。
技术选型案例
今天讲一下技术选型这个话题,主要是因为我的经验比较丰富,亲身经历过很多项目的选型过程,自己也做过很多靠谱或者不靠谱的决定,在这方面也有一些自己的想法,我想先从几个案例入手,像讲故事一样讲讲选型背后那些事,作为话题的开端。
我刚开始工作的时候,经历过一次大选事件,而我是这次事件的旁观者。当时公司要做一个非常酷炫的手机界面系统,恰逢 Vista 一系列新技术的发布,包括 WPF、C# 等。这些技术非常火爆,公司对其抱有极高的期望,想尽快用在新一代上。确实界面开发各种效果可以做得非常酷炫,节省界面开发时间,但是尴尬的是遇到了另一个问题,性能问题。
这些东西都是在移动设备上运行的,当时移动设备有 32MB 的内存、1GHz 的 CPU 已经不错了,无法支撑整个界面系统的性能要求。后来公司发现在当时的硬件环境下无法突破性能问题,就把所有界面都重写了,又回到了传统的用 C++ 和各种 API 的界面编写方式来解决问题。这耗费了近千名工程师、一年多的时间,可以说是人力和时间的巨大损失。
当时我还不太明白公司为什么不能早点止损,后来才渐渐明白,原来真的是因为负责人糊涂了。当一个决策做出来,大家自然都希望通过努力解决眼前的问题,但结果却是越陷越深。这也意味着你在一开始选择模型的时候,就必须非常谨慎,尤其是当选择影响巨大的时候,最好是保守一点。
后来我加入了真正的互联网公司,看到技术选型时,稳定性是最重要的。比如像 gcc、Linux 这类非常基础、非常关键的东西,互联网公司基本不会追逐最新版本,只是跟上节奏,非常克制地将一些补丁和功能引入线上环境。真正上线也会经过漫长的灰度验证过程。
我记得很清楚,2009 年的时候选型的时候,单机性能明显优于,而且还支持 PHP 扩展,可以以 mod 的形式运行 PHP。看上去好像可以完全替代,但实际上为了业务稳定,真正的用法是先作为反向代理使用,再用 + 来提供服务。这里的思考是对一个新技术天然的不信任,当技术接受程度不够高,公司里没人能完全理解这个技术的时候,是不愿意让自己的业务第一个去尝试的。
谨慎确实是一种美德,但在一个速度至关重要的行业中,谨慎也可能意味着过于保守而错失良机。
在自己创业的过程中,我选择模特更加积极,也更加享受其中的乐趣。
比如我会主动去用它,它灵活的数据结构、强大的查询语句以及自带的高可用机制让我印象深刻。在它刚发布1.0的时候,我用它来存放一些不重要的数据,后来2.x发布之后,我开始尝试把它作为新业务的核心数据库。也遇到过一些比较严重的坑,比如扩容不及时导致的数据损坏、宕机等,但是因为业务对于这些问题的容忍度比较高,也有一些备份方案,所以并没有成为业务的瓶颈。总体来说利大于弊,可以节省业务开发人员的宝贵时间。
我也决定使用 Node.js 作为主要的服务端开发工具,当时(2013 年)因为客户端要用它作为主要语言,所以服务端和客户端上会有很多可复用的代码,所以很想用 Node.js 来提高开发效率。
为了验证 Node.js 是否可靠,我通读了源码,看了很多相关文章,看了官方的说明和社区动态(、讨论等),也做了一些基本的压力测试。最终的结论是它的性能可以满足要求,稳定性方面也基本合格。考虑到它只是用于无状态服务,在单台服务器上会跑多个实例(当时管理),简单的崩溃不会对系统造成太大影响。再加上当时确实有公司把它作为主要服务,所以最终选择了它。
加入滴滴之后,我结合之前所有的经验,在技术选型上,有做得好的时候,也有做得错误的时候。
2015 年,滴滴内部有一个大型的代码重构项目,涉及大量的服务端和客户端代码,客户端的技术选型相对较好。鉴于当时代码库多业务耦合严重,开发过程中模块间冲突频繁,我们评估并引入了 Maven 作为 iOS 的项目拆分工具,通过代码重构将客户端项目拆分成若干个独立的仓库,让业务可以独立开发的同时,又能通过构建脚本轻松集成到完成的 App 中。
服务器选择错误。考虑到滴滴的业务模型跟actor模型很像,一个叫车流程会涉及到很多可复用的actor,如果直接实现一个分布式的actor模型和数据流管理机制,很多问题就迎刃而解了。但是当时没有这样的机制,我们自己实现的时候分别用Go+Kafka实现了actor和数据流存储。过程中遇到了Kafka消息丢失、定位困难等问题,而且actor模型太抽象,在整个团队中很难实现。最后我们放弃了整个计划。
技术选择方法
技术选型关键要考虑三个方面:技术、业务、人。
角度一:技术
选择技术首先要考虑的当然是技术本身,这里提到的技术包括语言、框架、工具、设计模式、开发模型等等。
选择技术时要遵循两大原则:第一,扬长避短;第二,关注技术的发展前景。
每一项技术都有其特定的应用场景,没有万灵药。开发者经常犯的错误就是盲目追逐新事物。当一种新的语言、框架或工具出现时,尤其是开发者自己已经学会了新技术时,他们会有“拿锤子找钉子”的感觉,将新技术滥用于各种项目。
比如Go语言,近几年在国内非常火,我在开发项目中也经常用到,但绝不能把Go语言当做所有项目都用。Go的优点有启动快,运行时性能高,方便利用多核计算能力等,经常被提及的特点有超轻线程,内置内存队列chan,极快的编译速度,非常适合用来编写各种无状态的应用服务,不需要借助任何第三方框架,就能轻松编写一个高性能的http服务。
但是它的缺点也非常明显,其中最让人头疼的就是 GC。Go 在设计之初就宣称实现了世界上最好的 GC,但时至今日,还相去甚远。它近一年才实现了 JVM 几年前就实现的并发 GC,而且也没有很好的方法解决内存碎片化和对象过多带来的性能问题。这些缺陷使得 Go 不太适合有状态的服务,尤其是与内存管理相关的服务。在这些场景下,还是 C/C++ 更靠谱一些。
技术的发展前景也是一个重要的考量。有些技术设计得很好。比如我个人很喜欢一门叫 Io 的语言,但我不会把它用于真正的项目,因为这个语言缺乏社区和长期支持。即使设计理念写得很好,里面难免会有各种 Bug 和不足,如果没有人能解决,就会造成严重的问题。技术的“前景”可以从几个维度来判断,比如有没有长期的规划,有没有持续投入的人或社区,问题解决的速度有多快,行业用例和口碑,源代码质量等。
选择一项技术的最低标准是该技术的生命周期必须明显长于项目的生命周期。想象一下,如果在项目完成之前,这项技术都没有得到维护,那将是一种什么样的困境。就拿去年非常火的 Vue.js 来说,友达在规划、投入、解决问题的速度上都没有问题,而这些正是这项技术能够火起来的基本保障。再加上其优雅的设计和编写精良的源代码,让它的成功并非偶然。可以预见,随着友达全职开发这款框架,以及越来越多的社区贡献者,Vue.js 撑个几年应该不成问题。
滴滴的 Web 应用,比如微信钱包里的滴滴入口,去年年底已经完全使用 Vue.js 重构。我们看到了 Vue.js 在移动应用开发上的优势,对它的前景很有信心。重构之前,为了确认 Vue.js 真的能担当这么大的任务,2016 年公共前端团队花了半年时间,把 Vue.js 1.0 和 2.0 的所有源码都梳理、评估一遍,还为此出了一本书。在公司大规模使用之前,我们也在滴滴的小巴业务和行程分享功能中试点过,效果非常好。最后我们才真正下定决心要大范围推广。
技术的发展前景是动态变化的,当一项技术走向出路时,我们也应该勇敢地抛弃它。比如它当初是前端开发的必需品,当时很多前端同学写代码都离不开$函数,它对简化DOM操作、抹平浏览器差异做出了极其重要的贡献。但随着浏览器越来越标准化和趋同,它的亮点已经不再吸引人,它的插件化开发模式逐渐被模块化开发所取代,再加上各种历史包袱,它适用的项目会越来越少,不建议在选择新项目时优先考虑它。
对于大公司来说,核心业务的技术选择需要更加谨慎,甚至看前景时需要考虑技术的独立性。仍以 Go 为例,目前 Go 的核心基本都是外包的,并没有独立运营的基金会来负责语言的长期维护,也没有一个公开透明的决策机制来决定语言的未来。如果因为某种原因,停止投入或者改变语言的发展方向,那么这对于大公司来说可能是毁灭性的打击。那些立志成为千亿美元公司的公司,或者潜在的竞争对手,在选择使用 Go 时应该更加谨慎,不能盲目跟风。
视角二:商业
技术选择要基于业务,不同的业务阶段会有不同的选择方式。
对于初创企业来说,选择的关键词就是“灵活”,只要一项技术足够好,开发效率足够高,那么就可以选择它。初创企业往往风险大,不确定性强,每天换单,反复试错是常态,技术要适应业务的节奏,然后才是其他方面。就是一个很好的例子,相较于MySQL,它的数据结构灵活多变,相较于一般的KV存储,它拥有类似SQL的复杂查询能力,此外,它内置的万无一失的高可用性和水平扩展机制,让它能够很好的适应初创企业对效率的追求。
当业务进入稳定期,选择的关键词就是“可靠”。技术永远是业务的基石,当业务稳定但技术不稳定时,就会成为业务的短板,必须改正。一个很好的例子就是放弃 RoR,选择 Java 框架。RoR 以发展迅速著称,但同时 ruby 的性能非常有限。工程团队对 ruby 虚拟机做了大量性能优化,但依然不能满足预期。再加上当时为了提升前端体验,采用模块化、异步的方式来加载页面,服务端基本不负责渲染页面,而是专注于提供各种 API,RoR 的优势并不是很明显。
当业务进入维护期,选型的关键词就是“妥协”。代码总是会发生变化,一般一两年后就需要进行一次较大规模的代码重构。这时候就要正视各种遗留代码的迁移成本。如果改变技术选型会导致重写遗留代码,而这背后的成本对于业务来说是难以承受的,那么就要考虑在现有的技术选型上做一些小修小补或者螺旋式重构。
因为技术选型跟业务相关,所以我们可以观察到一些比较明显的现象:新技术往往被大公司的早期创业团队或者新兴业务所采用;中大型公司的核心业务往往使用已经稳定使用几年的技术;如果一家公司长期使用一项技术,那么它就会倾向于一直使用它,甚至不需要更新版本。这些现象背后都是有原因的。
视角三:人
在技术选择的过程中,最终影响决策的是人自己。这里要强调的是,我说的“人”是个人,而不是团队。
技术选型的决策过程必须是权威的。决策者可以在研究时体谅民情,考虑团队现状,但绝不能采取“少数服从多数”或“顺应大家习惯”的方式来选型。权威主义可以让技术选型更客观、更全面,权责统一。
不是所有人都懂得如何对一个项目负责。一个草根开发者可能会更多地考虑这项技术是否具有挑战性,是否能够成功,甚至未来是否容易找到工作。这些主观因素可能会给选型带来灾难性的后果。专制也使得“螺旋式上升”成为可能。很多时候我们不可能一夜之间就用好某项技术。这时候,我们就需要一个领导者,带领大家沿着曲折的道路坚定地前行,走向成功。
技术选型也非常依赖人的能力。选型是一个很难标准化的过程,选型决策的好坏和人的眼界、经验、业务敏感度、逻辑等息息相关。对我来说,面对选型问题,我首先考虑的是了解公司内部和外部类似问题的解决方式,避免各自为政。然后思考所有的可能性,列出最核心的考虑因素,在脑海里对各种方案的优劣做一个对比。最后整理这些逻辑,做出决策。
滴滴是这样决定客户端动态化方向的。我们把业界所有可能的解决方案都拿出来,了解它们的优缺点,然后在一次会议上,几个核心同学在白板上做一个表格,需要考虑的因素为行,可能的解决方案为列,评估每个方案在各个因素上的优缺点,最后确定一个结论。我们选择的路径是偏向客户端开发的动态化方案,在保留所有代码和工具链的前提下,对开发者透明。这样可以最大程度降低整体的迁移和维护成本。当然,这条路发展起来也相当困难。幸好我们当时找到了最合适的人,还是可以在可以接受的时间内把整个方案落地。
培养选择技术的能力
可以看到,做好一个技术选型相当困难,要做好它需要有足够的知识积累和实际经验。一个不懂选型的新手如果想学好这个,可以从一个小项目开始,慢慢尝试,积累经验。做技术选型的人,最重要的是“逻辑”,每一个决策背后都有很多假设和事实,我们就是靠不断挑战这些背后的东西,才逐渐成长起来的。
例如,在需要使用缓存来加速数据访问的场景下,我们可能很自然地会选择 redis 作为缓存服务。这个看似“直观”的决定也是由一系列假设和事实组成的。你可以问自己一系列问题,看看这个决定在特定场景下是否真的正确。例如,缓存服务除了 redis 之外还有其他选择吗,能不能直接缓存在内存中,redis 是否稳定,redis 性能是否满足要求,数据库访问速度的瓶颈在哪里等等。很有可能最终的结果依然是“使用 redis 做缓存”这个直观的解决方案,但正是因为有了分析的过程,我们下次才能更快、更自信地做出决策。
如何保持敏感性和广度
技术选型是一个需要大量经验的工作,需要大量的信息积累和输入,然后根据具体的现实输出一个结果。选型的时候一定要避免临时抱佛脚,用网上搜集的一些碎片化知识来做决策,这是很危险的。一定要保证所有的思考都是基于之前的事实,还要搞清楚这些事实背后的假设,这需要内化知识,形成经验。
我一直在思考“经验”的本质是什么,可以用什么方法来判断自己的经验是否有增长,而不是不断重复熟悉的东西。我目前的结论是,经验等于“知识指数”的完备度。
我们一生中积累了大量的知识。如果把大脑比作一个数据库,那么我们的大脑存储中一定有一部分专门用于内容索引,这可以帮助我们更快地检索相关知识并辅助决策。经验的增长相当于我们知识索引的增长,这意味着我们可以轻松地调动更多相关知识来做出更全面的决策。
要建构这个知识索引,我们必须保持技术的敏感度与广度,就是要不断地输入、内化信息,发现信息之间的关联性,建构索引,并记录下来,说起来容易做起来难。
首先,大量信息很难输入,忘记了怎么办?我们的大脑不是磁盘,不常用的知识我们会忘记,忘记就等于没见过。我的经验是,一定要把知识压缩,记住最关键的细节,反复回忆这些细节。
比如,我在学习各种语言的时候,都会特别留意一些最有特色的语法特点和应用场景。比如对于 C++,我总是记得很久以前见过的细节,比如编译器默认会生成哪些类方法,默认的析构函数、复制构造函数、= 等,在什么场景下需要显式禁用默认生成的类方法,什么时候应该在构造函数中使用它们等等。这些细节我看过十五年多了,依然记得清清楚楚。
看起来有点难,其实不然。想想自己学过的英文单词,哪怕是最常见的几百个英文单词,都能记得清意思。其实经过压缩之后,技术知识点会比英文单词数量小很多,记忆负担也没有想象中那么大。
难就难在信息更新太快,跟不上技术的发展怎么办。学了很多技术之后发现,这确实是个难题。比如前端开发,每年都有新的框架、新的开发方式出现,如果不提前了解ES7的语法,可能过两年就连语法都搞不懂了。
我也有点担心这个问题,不过也有办法,就是坚持碎片化学习,逐步更新过时的内容,只要养成习惯,就能慢慢找到自己的节奏。如果有些技术细节太多,比如 Node.js,我之前也看过源码,认真研究过内部设计,但随着它不断发展,我不敢说自己对它的内部有多熟悉。这种情况下,我会考虑大胆放弃追逐新的东西,等到可能需要用到的时候再更新到最新的知识。
最后的难点在于如何将信息存入知识索引中。如果知识太过零散,无法形成体系,索引建不起来怎么办?最基本的做法就是看书,看别人如何将知识变成一章一章的信息。要掌握索引背后的方法论,我的经验是先从两门类似的技术入手,找到索引的感觉,然后再拓展学习更多的知识。有这种困惑的开发者,在学习上往往贪得无厌,以为自己记忆力好,可以强行内化知识。这可能在短期内管用,但长期来看,还是会忘记,不会形成经验。
其实技术知识点很相似,有很多共同点可以挖掘。例如在客户端和前端开发中,各个框架在View生命周期管理,消息分发机制等方面非常相似,而后端开发则更加常规。无论使用哪种语言,最基本的分布式服务原理,缓存,队列,数据库等基本组件原理都是相同的。
如果再宏观的看各个领域,我们甚至可以发现领域之间的知识体系划分非常相似。作为表现层的前端和客户端,知识体系可以分为语言、API、工程、框架和设计模式。比如前端语言有 HTML、CSS 还有一些稍微小众的语言等等。API 就是各种标准、接口使用、可以实现的效果、平台限制等等。工程就是各种封装工具、代码转换工具、辅助开发工具等等。框架有 Vue、React 等等。设计模式有 PWA、redux 等等。
对应的,刚才提到的知识在 iOS 或者 中都可以找到,只是有些细节有所改动,这里就不细说了。服务端也是一样,知识体系顶层的占比也非常小,讲到细节,只要了解每个实现背后的利弊就行了。
总结一下,技术选型靠经验,经验又来自知识指数的构建,知识指数的构建靠每天的总结和不断输入新知识。技术是一辈子的事,要投入大量的时间来维持现状。学习永无止境,大家互相鼓励吧。
今日推荐号码
问
StuQ 是 InfoQ 推出的 IT 教育平台,为技术人士提供系统化的实战课程,学习微服务、机器学习、iOS 开发等最热门的技术。回复“课程”即可获取热门课程介绍及优惠码
微信号:
今日推荐文章
点击下方图片即可阅读
扫一扫在手机端查看
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。