这是一位95后程序员的自我思考之旅,关于为何放弃难得的保研与出国机会、为何离开看似最佳选择的大厂加入前途未知的创业公司、为何从如火如荼的AI领域转入数据库领域的故事。《新程序员》带你走进刘继聪的程序人生。希望能给职业成长路上有些许迷茫与困惑的年轻程序员们一点启发。
作者
刘继聪
出品
《新程序员》编辑部
我叫刘继聪,是复旦大学级的学生,最初在自然科学试验班,分流进入物理系,然后转专业进入计算机系,也因转专业政策降了一级,于年毕业。我放弃了保研和出国的机会,进入阿里巴巴一年后,加入涛思数据。
刘继聪
在我短短三年的职业生涯中,先后辗转字节跳动(实习)、某创业公司(实习)、阿里巴巴、涛思数据。研究方向从NLP(自然语言处理)、ML(机器学习)转为DB(数据库)。本文是我对过去三年职业方向选择的回顾与复盘,希望你读过我曾经历的迷茫与困惑后,不会走上同样的弯路。
本文已收录《新程序员-》
菜鸟起航——从物理系转入计算机系
一个刚刚参加完高考的学生,不会很清楚大学设立的专业是做什么的,进入大学后可能会很难接受所选专业与预期的差距。因此,大二时我选择从物理系转入计算机系。
和计算机结缘,源于物理系的一个项目——实验室设备管理系统。当时,我负责Web与服务器相关的功能,因为无人指导,只能在网上找教程自学。我清晰地记得,当我用“世界上最好的语言”PHP写出第一版程序——一个LAMP架构的CRUD系统时的感受。随着项目的迭代,系统不断重构,前端后续换成了JavaScript,后端则换成了基于Python的Django。
与此同时,我选修了物理系的C++课程,这门课主要讲C++如何应用于计算和模拟方面。我人生中第一个较大的C++项目就是那门课的期末作业:用经典的蒙特卡洛方法,做伊辛模型(Isingmodel)的模拟。我做了一个命令行程序,只要输入不同的参数,就能够生成对应的模拟结果,并用OpenMP做并行的加速优化。后来我才发现由于数据依赖,我写的并行程序比串行程序慢几倍。
我觉得写代码非常有意思,或许比在物理实验室修设备更有趣,于是,顶着降级的代价,我转入了计算机系。很快我发现,即使我自认为已经写过很多代码,但也一度被各路大神虐得体无完肤。
我所在的拔尖班大部分同学都在ACM队,而数据结构课的老师是复旦大学ACM队教练。整个课程体系非常竞赛化,每周都会用OJ(在线判题系统)测试,OJ类似于LeetCode,但区别在于,它只显示答案错了,却不告知产生错误的输入数据。因此,调试就完全靠猜。开始时,我在测试中一分都拿不到,每周光是完成作业就几乎花掉我所有时间。为了获得更好的测试成绩,有很多次,我都坐在通宵自习室写代码,直到天亮。渐渐地,我能够在每周的OJ测试中拿到满分,期末拿到A。在刚转入计算机系最痛苦的第一学年,我的绩点便拿到全系第一。
后来,我在复旦获得各种各样的奖项:一等、二等、三等奖学金,以及数模竞赛和物理学术竞赛的国家一等奖和上海市一等奖,还有泛海学者、优秀学生等荣誉(见图1)。从自我怀疑、焦虑不安变得坚定、自信。如今想来,我认为那门数据结构课程是一个台阶,拼尽全力再上一层,才能看到别样风景。也正是这段竭尽全力刷算法题的经历,使我在几乎零准备的情况下进入字节跳动实习,并拿到了各个互联网大厂的秋招Offer。
图1在复旦大学所获奖项与荣誉
在计算机系的选修课中,我接触到了更广阔的计算机世界与更深入的知识,也做了很多有趣的项目。
比如,数据库课程的期末项目与PostgreSQL有关,要求利用PostgreSQL的框架,实现两个UDF函数,计算字符串的LevensheteinDistance和JaccardDistance,并且尽可能优化执行效率。通常,优化无非是降低算法复杂度、常数优化。助教提醒我们把PostgreSQL的嵌套循环连接改成块嵌套循环连接,显然这是附加题,且难度较大,很少有同学去做。临近期末季,即使项目完成得再好,也不会比其他人的成绩好多少,而将时间完全投入复习,争取考试拿高分才是更有性价比的选择。不过,在我看来,做项目比复习有意思。在期末考试前的那个周末,我将全部时间投入项目中,看着评测的执行时间从十几秒优化到一秒内,过程中的收获,远比期末的成绩A更重要。
此外,我还选修了分布式系统、数据挖掘、密码学原理,甚至是这辈子都可能不会再用到的信息安全原理、计算理论基础等课程。同时,我和同学组队做了很多有趣的项目,部署Hadoop和Hive、分析不同任务中的性能瓶颈、使用Spark进行分布式计算等等。很多项目,如LZMA压缩算法、Hive的性能分析实验、SVM的实现与收敛分析等,都让我觉得分外有趣。
职业选择——从AI到DB
除了上课,计算机学生的另一条主线是科研与实习。
进入计算机系的拔尖班后,要求选导师、进实验室,由于当时的主流是AI,复旦大学的NLP实验室很强,于是我就加入了NLP实验室。在实验室异常拥挤,本科生一座难求的情况下,稀里糊涂过了一年。当时我想去校外看看工业界都在做什么。
大三时我在字节跳动的AILab实习。适逢BERT以横扫六合之势,刷新了NLP众多Task的SOTA(StateOfTheArt,最高水平),我们基于BERT做中文错别字检测工作,该项目后来发表于ACL会议。
改模型的工作不够有趣,在进入大四前那个暑假,我进入香港科技大学交流,跟着导师研究GAN(GenerativeAdversarialNetworks,生成对抗网络)。虽然我在AI领域学习的导师都是业界、学术界的知名专家,但我却没能产出亮眼的成果,以至于我现在都不敢提他们的名字。
机缘巧合下,一位做数据库的同学引领我接触到另一个领域——DataInfra(数据基础设施)。作为TiDB的贡献者,他也带着我进入TiDB社区做贡献。彼时,我开始系统性地学习分布式系统和数据库,成为开源社区的贡献者,并萌生了去做数据库,更准确地说,去做DataInfra的想法。
临近毕业季,摆在我面前的路有三条:保研升学、参加工作、出国深造。由于保研外校需要参加夏令营(而当时我正在香港科技大学交流),本校又没有特别合适的导师,我便放弃保研,去追秋招的末班车。出国留学是我给自己的后路,因为我有科研经历,能拿到推荐信,英语也不成问题。但出乎我意料的是,秋招非常顺利,我拿到了所有面试公司的Offer,其中既有阿里巴巴、腾讯这类大型企业,也有PingCAP这类创业公司。
在某互联网大厂的终面中,我遇到了一位同样复旦毕业、工作十多年的学长。当我透露出想做数据库的意向时,他反问我:“年轻人都想去做些有技术挑战的事情,我当年也是。但去创业公司做数据库,你真的想好了吗?未来好几年,你可能都比在互联网大厂做业务的同学薪资低,同时还要承担更多不确定的风险,你能接受吗?”
最终,我选择了阿里云,从事基础架构的工作。
秋招结束后,距离毕业和正式入职还有好几个月的时间,我了解到一位学长在某创业公司做技术负责人,便去做了实习生。在那里,我第一次用Go写微服务,第一次接触生产环境中的真正运行的Kubernetes,第一次用C++写TensorFlow……这段经历,也让我对创业公司留下好感,至少不输互联网大厂。
疫情在那个冬天爆发,无法返校,毕业时也只和部分同学匆匆相聚又相别。上海到杭州不过49分钟动车车程,恰如从学生到职场人的转变,快得令人猝不及防。下一站,杭州阿里。
第一份正式工作——阿里巴巴
或许是得益于我之前还算丰富的实习经历,又或许是我在学生时代做了很多有挑战的课程项目,使我很快适应了新的环境,上手了新的工作内容。在入职首月,就解决了一个困扰组内技术专家半年之久的问题。
背景是这样的,当时我们的监控系统探针直接安装在客户的ECS(阿里云服务器)上,客户可能会购买很多ECS,组成集群。我们需要抓取集群内的网络拓扑关系如TCP连接,然后绘制成可视化的拓扑图。该过程相比一般的分布式微服务追踪,难点在于:
不对客户的技术栈做假设,Java、Python、PHP都有可能;
要做无侵入式,不希望用户改代码;
要适配Kubernetes、非Kubernetes及各种Linux内核版本。
彼时已经实现的技术方案有根本缺陷,是通过轮询/proc目录下的TCP文件来做的,但问题是,采用轮询方式只能抓取此刻正存在的连接,如果连接是不断发起并立刻释放,那么,轮询方式无法保证抓取全量数据。解决该问题最好的方法是用eBPF,而eBPF是相对新的技术,只有在较新版本的Linux内核中才能得到良好支持。据当时的统计数据,绝大多数客户的技术环境都不能良好支持eBPF。
当我加入后,了解到这个问题已经困扰大家许久,我花了一周业余时间做调研,发现有一个内核模块—auditd,可以较好地解决这个问题。
auditd是一个比较轻量级的内核Log模块,能够在很旧的内核版本中运行,可以对所有的监控系统进行调用,并打出Log。那几天,我的业余时间都在做新方案的Demo。如我所料,它确实可以实现我们要的功能。后来,我又花了几周时间调试,真正将Demo转变成一个生产环境中可用的产品,并将其上线。正因此,我在转正时得到了极好的评价:“确实超出预期了,没想过让他做这件事”。
此外,我每天晚上都会花费大量时间阅读组里的代码,入职第三个月时,我已经可以画出组里系统的架构图,给入职的新人讲解各个模块的划分及其相互关系。入职第六个月,我已经阅读了权限范围内的几乎所有代码。后续随着组织架构的变动,我开始成为一些项目的Owner。
当然,我也遇到过一些不大不小的挑战。比如:对于JavaAgent,为了在premain模式下实现字段和方法的添加与删除,我用ByteBuddy写了一套注入工具替换原本的JVM-Sandbox。与此同时,我开始对软件工程和云有了更深的认知,也对基础架构的理解更加深刻——同样被称作基础架构,但工作内容可能天差地别。
如果把云上的众多服务以平台侧和管控侧作划分:平台侧相当于提供云上数据库、消息队列等服务,部署形式对用户可以完全透明,用户可仅以API的方式调用;管控侧的典型特点是侵入性,例如探针,需要将服务安装到用户的环境中。我所在的工作组,工作内容便属于后者,管控侧的麻烦在于适配用户各种各样复杂的环境,其自身的核心能力、技术难度与挑战都明显弱于平台侧。因此,我发现自己进步的速度也渐趋缓慢,组里业务的发展状况也不如人意。
岁末、春节,疫情仍在持续,在那个冬天,我没能回家。
杭州开放了很多免费娱乐,给这个冬日带来了一丝暖意,可我无心玩乐。尽管在这份工作中取得了不错的个人成绩,但我确信一定会离开这里,这一次,我必须确定目标、规划未来。
春节后,我开始和公司内部的不同团队接触,结果却让我失望。很多我以为做着内核研发的部门实际上只做管控和写控制台;还有一些内核部门,工作范围太小;至于其余部门,则并不欢迎校招新人转岗。与此同时,我看到DataInfra的环境正在悄然改变。一些令人振奋的声音传到了我的耳朵里:
“所有人都看好DoorDash这些明星Pre-IPO,而Snowflake这类公司当时招人都挺困难,但那些进去的人后来发现他们的收益远远超过了其他人……”
“Snowflake的发行价已经上调了,上市当天就暴涨了超过%……”
“Snowflake的数据确实好,它的NRR是%,也就是说假设一个用户去年在Snowflake上花费了1美元,那么今年他将平均花费1.73美元……”
“Confluent上市、Databricks大额融资……”
这时,我才发现:去创业公司做数据库,已经不再是一件需要靠情怀去支撑的事。随着这个行业内热钱的涌入,有前途的创业公司会获得大额融资,更关键的是,对于这类技术公司,人才是核心的投入与资产,因此,他们愿意花钱,也有充足的资金和大厂抢人。确定大方向后,我又花了一些时间做调研,以找到更具体的目标。
期间,我也在积极地准备面试。一方面,我开始系统性地研究基于LSMTree的存储引擎,完整阅读LevelDB的代码,阅读部分RocksDB的源码,并对比、分析MergeTree、WiredTiger等存储引擎;另一方面,研究LockFree与WaitFree等算法,学习HazardPointer、RCU等技术,阅读Folly中MPMCQueue的实现。我将学习内容以及过往在数据库领域的经历都写入简历,作为面试的敲门砖。
最终,我敲定首选目标——做时序数据库TDengine的涛思数据。
首先,TDengine是开源产品,我已经多次看到TDengine登上GitHubTrending榜,我能够从代码及其运行直接判断它是否有真材实料;其次,通用OLAP数据库虽然被炒得火热,但竞争也非常激烈,或许不如从细分赛道入手,而在细分赛道中,时序数据库与图数据库的增长最为迅速,我相信IoT设备会持续不断地增长,产出越来越多的数据,我看好它的前景;再次,涛思数据的招聘页上醒目地写着“超越BAT的薪资”,我认同这样的人才观,愿景与待遇缺一不可,如此才能招聘到最优秀的人才;最后,面试本质是双向选择的过程,我需要充分利用面试的机会了解未来TDengine还要做哪些事、有哪些挑战,如果这个产品被认为已趋成熟、只能做些小修小改,那不是我想要的。
新的征程——涛思数据
从杭州搬到北京,我与涛思数据的故事从年7月拉开帷幕。数据库内核的研发工作我很快上手,并在转正后的首次季度总结中拿到了“最佳新人奖”(见图2)。
图2涛思数据“最佳新人奖”
现在,我在涛思数据负责流式计算引擎的研发。TDengine2.0中提供了连续查询的能力,它本质是一个时间驱动的批处理,无法处理乱序数据的问题,且性能消耗很大。我的目标是为现有的TDengine实现一个真正的流式计算引擎,挑战之大远远超乎此前所有的工作与项目。
我一边系统性地学习流式计算中的ExactlyOnceMessageProcessing、分布式快照等容错理论,一边研究Kafka、Flink的源码与实现机制,并跟踪SIGMOD、VLDB、ICDE等大会中相关研究的最新进展,了解Railgun、HazalcastJet、RayStreaming等新引擎的设计,同时