大咖助阵 徐祥曦:从销售到分布式存储工程师,我与 Go 的故事 大咖助阵|徐祥曦:从销售到分布式存储工程师,我与 Go 的故事

你好,我是徐祥曦。现在在一家量化交易公司做高性能计算和基础架构相关的工作。很高兴能在这里和你交流。

今天我想和你分享一下我职业启蒙阶段的点点滴滴,看看我从一名小销售到分布式存储工程师都发生了哪些故事。这些小故事不一定会给你带来直接启发,但我敢拍胸脯保证它们都很好玩。

在 Go 之前

14 年,临近毕业的我笃定云计算会是未来高速发展的行业。我的运气也不赖,最后赶在毕业前夕敲定了七牛云的工作。那个时候我还不知道什么是 Go 语言,对计算机语言的认识也只有小学时候屏幕上的小乌龟(注:Logo 语言)和大学时候借(抄)鉴(袭)的 VB 作业。

而七牛云作为国内率先将 Go 语言运用到大型工程的公司之一,内部 Go 风浓郁,于是 Go 语言就这样闯入了我的计算机“孩提时代”。我也像蹒跚学步的孩子一样,在走路之前,不停地摔跟头。

没入门就放弃

当时的七牛云自内向外,自上而下的技术气息也感染了我。况且,我在入行之前就有了学习计算机技术的想法。很快,就在我做了一个月销售后,便向同事表露了对于学习计算机编程的兴趣,善良的他立马伸出了援手,与我约定每天下班后指导我两小时。

这个约定两天后就被我这个学生撕毁了,因为我好几次在屏幕前小鸡啄米般地打起了瞌睡。

“package” “import” “fmt” “func” “main” “()” “{}”……这样奇怪的字符组合放佛是一群面无表情,衣着怪异的天外来客,直愣愣地盯着我,而我一动也不敢动。与此同时,闪烁的光标放佛像是一把对准我的枪,敦促我赶紧做点什么。可我这个时候该干嘛呢?我先是害怕犯错,适应紧张之后我迅速开始麻木,麻木之后便开始疲乏。我这种不争气的行为令我想到了读书的时候班级里所谓的“笨学生”。

老师表示理解,毕竟我文科出身又没有基础,希望我课后多花点时间琢磨和练习。“新事物一开始会让人感到不适,过去就好了。” 而我则以销售任务重为由,希望以后有空再来讨教,便撤出了办公室。那是十月下旬夜里,时不时会有一阵阵凉风拂过,吹得我的脸忽冷忽热。

可能这时候会有人说,那是因为我不够热爱计算机。兴趣是最好的老师,我缺乏足够的兴趣,自然是学不好的。毕竟人类不是机器,不会为了一个忽然从天而降的目标就没头没脑地努力了起来,我们需要兴趣,或者说激情来提供行动力。这样的解释似乎很说得通,我们姑且当作是真理。

于是乎,结论就是要么是我笨,要么是我的兴趣不够真诚。

我是不大愿意承认自己是笨蛋的。 for { 兴趣 -= 1 }

把自己给“演”了

我已经想不起来是何时,从哪知道王垠的了。一直以来我总是喜欢涉猎多个学科,看看不同人在不同的领域的想法,纯在是巧合让我忽然某一天刷起他的博客来,就这样王垠在他不知情的情况下成了我无言的老师。

于是我开始学起了 Scheme。

不知道你有没有观察到一个现象。哪怕是很差的学生,在学习上失败了,他们也会想办法在其他地方找回场子,打架啦,抽烟啦,说唱啦等等。我当初学习 Scheme,看王垠的博客可能也有这种心理作祟。Scheme 少有人深入学习,而程序员又普遍比较严谨谦虚,我就可以大大方方地说些我并不明白的东西,显示自己有见识。

王垠的博客常常用通俗易懂的方式阐述一个复杂的事物,尽管我还是看不懂,但他犀利的文笔和清晰的观点,已经足够能让我在某些场合通过插嘴的方式显示自己有眼光。

有可能我入戏太深,演着演着竟然真对编程有了一丝丝微妙的感觉。这就像是你无意中向院子望去,竟看见一位陌生的美丽少女在暮色中绣花。其实呢,这美少女你早已见过,只是第一次为她屏住了呼吸。

好问题出现在好时间

说起来也巧,当我刚学会如何在 DrRacket 里面把代码跑起来的时候,七牛云举办了第一次算法比赛,题目大致如下: 输入一个 2^64 - 1 的数,求该数内的最大素数。要求使用 Go 语言,if 判断尽量少。

尽管我那个时候还不会 Go,但我可以用 Scheme 写呀!

可是这程序该怎么写呢?用什么算法呢?用什么数据结构呢?更何况,我当时连算法和数据结构到底是在说什么都不知道。困难很多,可我还是胸有成竹,深信自己能写出全公司最快的代码。

我并不是昏了头,相反我很清醒。因为就在算法比赛前几天,我正好闲来无事在看《什么是数学》这本书,正好学了一些数论的基础。于是乎,我立马想到了费马小定理。

当时看书的时候,我就凭直觉猜到费马小定理可以通过变形增加它的强度,结合这个算法题,依我看来, 2^64 - 1 在数学界至多算个天文数字的弟中弟,一定早就有人在更大的范围内将费马小定理得到的伪素数排除殆尽。

进一步,我可以根据现有的素数定理猜测最大素数可能出现的位置,来减少计算的开销。这个定理也是现成的,用高斯的也好,用黎曼的也好,都行。

万事俱备,我非常激动地开始编写属于自己的第一个程序, 你可以在 这里 看到它。

不过,有一个困难是我在开始的时候没有料到的,程序慢到我以为电脑坏了,一番搜索之后,我才知道除法在计算机世界慢得令人发指。原则上,我立马选择无视这个问题算是识时务的英雄,因为我的程序已经是理论上最快的了,毕竟其他人的算法一样也只能在理论上判断上一个大数是不是素数。作为一名菜鸟我做得够好了!更何况我还在做销售呢!

但我没有停下来,通过一步步推算,我自己重新发明了一遍蒙哥马利算法(Montgomery Reduction)。起初我以为自己发现了了不起的东西,后来发现这个算法 1985年就有了。尽管错过了发现的机会,但至少说明我做对了,当时的兴奋感不亚于裸身冲出浴盆的阿基米德。最终,求 3,317,044,064,679,887,385,961,980 (不知比原题的上界大了多少倍) 以内最大的素数只需要几毫秒。

现在回想起来,真是好惊险。还好我当时不会 Go,用的是直接支持大数运算的 Scheme。

虽然由于不符合题目要求,我没有提交我的代码。但这不妨碍我第一次真切感受到了编程的动人之处——将自己的想法实现出来。

在 Go 之上

小目标:把 Go 写的和 Intel 的汇编一样快

不知不觉,两年里,我从销售转变成了售前工程师,这要感谢当时那个粗糙和善良的时代,好让我挂着个“工程师”的招牌却不用真正懂技术。但我并不满足,幻想自己加入公司神秘的分布式存储开发团队,成为一名真正的工程师。

痴痴地想多了,行为竟然也不受控制起来。一个燥热午后,我突然从工位上站起来跑去找存储团队的负责人“你们缺人不?” 未曾想,他爽快地说好,笑容犹如一阵清风,这时我才如梦初醒,感到大事不妙。我就这个水平进去,不得没两月就被公司扫地出门,饭都要吃不上了!真该死,我应该再准备准备的!

但很多时候,我们就是需要惊喜甚至惊悚,不是吗?当什么都准备好的时候,往往也就没有机会试一试了。

当然了,事物发展都是要遵循客观规律的。果然,我干得非常糟糕,严重拉低了团队的下限,后来我才知道我当时离被开除仅有一线之隔,也不知是哪位菩萨保佑了我,才让我晕头转向地继续混在里面。作为知恩图报的人,我得先感谢 Go 的简洁,让我没捅太多娄子。

战战兢兢的我在勉勉强强应付 mentor 给我的任务的同时,还有一个不成熟的心愿,这也是我想要来做存储的初衷,这个问题的种子在我做销售的时候就埋下了——七牛云的纠删码一直说很强,但究竟强在哪呢?但我却没法开口问,既不知道问谁,也不知道提一个什么样的具体问题,因为我连自己到底想知道什么也不知道。

我缺乏一个能引导自己的真正好的问题。

思而不学则怠。我开始像个正儿八经的工程师一样开始读起了论文。一遍看不懂,就再看一遍,看不懂原文就检索相关的资料。最终,我当然还是看不懂。我怎么能看懂呢?我会的不多的线性代数早已忘光,有限域更是天书,编码又是怎么回事呢?至于实现它所需要的体系结构知识,我怎么可能会呢?我甚至连半本计算机书籍都没看完过。

折磨啊,真折磨,逃避一下吧!

说是逃避,但我还是抱有侥幸心理,希望这是一次战略性撤退,事实证明确实如此。由于过去了好几年,我那个时候头脑也乱糟糟,我现在确实想不起来我在哪看到的一门化学公开课,里面运用了有限域。我当时的如意算盘大致如下,由于不是纯数,而偏应用,我或许可以假装在学化学,好让自己在转移注意力,减轻焦虑的同时,悄悄掌握有限域的知识。我那个时候可真敢想啊!万幸,现在也是!

从将信将疑地开始学习,到且战且退,到开发出第一个版本,我花了七上八下的两个星期。

第一个版本的性能大概是 Intel ISA-L 库的 70%,基本符合我的期望。毕竟我是用 Go 及其 Plan9 汇编写的,而 ISA-L 是 C 和汇编,况且我还不大会写代码哩!听上去很合理,很实际,非常经济。

但如果我要真正从实际出发,我就应该进一步提高其性能。因为我还没看到阻碍其性能进一步提高的原因,为什么不继续呢?

最终我以与 ISA-L 不同的矩阵运算形式取得了与其媲美的性能,代码我后来也开源了。这样寥寥一句话的背后,藏匿的是我好几个被指令折磨的日日夜夜,在这之后,我看机器码都可爱起来了。

单单写一个很快的纠删码,我觉得还不够痛快。没多久我又写了一个具备诸多良好性质的编码,白山云的存储现在用的就是这个。人生得意须尽欢嘛!有趣的是,我逞一时之快的作品成了我后来更进一步的敲门砖(我不单单是因为美貌立足社会的)。在那个时候,我真正成了一名程序员,一个工程师。

Go Contributor

这是一段让我面红耳赤的经历。

在编写纠删码算法的过程中,我掌握了些许针对体系结构优化的经验,翻了下 Go 的代码后,发现自己可以尝试对Go部分源代码进行优化。于是,我便开始干了。

很快我就收到了 review 意见。嚯,好家伙!短短几十行汇编提了这么多修改意见。我也变得兴奋起来,开始改吧!当时夜半,有些兴奋过了头,每每改完一版,心满意足地刚一躺下便跳起来大呼不妙 —— 我应该如此如此、这般这般会更加优雅!如此反复,我把 Go Team 的邮箱打爆了,直到被 Brad Fitzpatrick 呵斥,我心里委屈起来,我还只是个孩子啊!

试着去挑战一下成熟而活跃的开源社区吧,难道你不喜欢面红耳赤,心跳加速的感觉吗?

引路人

话接之前我提到的纠删码,我说到自己通过这代码拿到了后来的工作机会。不过在展示这代码之前,可能更重要的是,我与未来的领导关于某些矩阵的子矩阵在有限域中的可逆性问题的讨论,引起了他的好奇。你说两个程序员讨论这干啥呢?但很好玩不是吗?我也正是这样通过玩耍吃上了饭,还获得了友谊。

虽然我之前也在分布式存储团队,但我真的什么也不会。直到来到了白山云,忽然要挑大梁了,我才意识到自己真的要成为一名分布式存储工程师了。

那个时候,我的任务是和团队一起完成纠删码存储集群设计与落地。好艰巨啊,我连写些最基础的代码都得复制粘贴啊!由于之前团队没有使用 Go 语言的经验,领导安排我给大家做一个简单的 Go 语言培训,我居然还真硬着头皮又硬着脸皮去做个了简单的分享。好在同事们都思维缜密,基础扎实,将我的七零八落的只言片语在心里默默地组织起来,很快掌握了 Go。在这里,我必须得再次感谢这门实践性语言的清晰明了。

写到这里我忽然有种担心,这不是活脱脱一个江湖骗子的形象嘛!我也太敢说了。希望看到这里的朋友,天知地知你知我知,不要到处讲。出门在外,大家都是朋友。

不过,我们做事也不能光凭大胆啊。在化肥得到广泛应用之前,小麦的亩产是非常低的。这也是现代化农业之前,大家开垦荒地积极性不高的原因。一亩地一年就出几十斤粮食,稍有不慎,什么都没了。所以,大胆和产能没有直接联系,所以我也没法单凭不要脸把存储给做出来,要讲究科学。

首先,我们需要将设计一套存储系统,转化成一个非常具体的问题,再由一个个具体问题引导出新的具体问题,最终一张有组织的网便生长了出来。这也是架构设计到工程实践的一般步骤。

但是,要得到具有引导性的好问题并不容易,我除了思辨与推演,其实也在不停地翻查别人的设计,每当我有了新的成型想法后,我便拿去和 Leader 讨论,说是讨论,不如说是盘问。在他的 “是还是不是” 以及 “是” 一个追问,“不是” 一个追问的过程中,问题逐渐走向成熟。

这样一轮又一轮的审判持续了四个月,我们终于开始着手真正的架构设计了,它先是出现在脑海中,随后是满是涂鸦的纸片上,再来到了文档里,最后亭亭玉立、大大方方地呈现了出来。

直到我自己开始负责同样大型的项目的时候,我才清晰地意识到他早有许多成型的思路,但就是硬憋着没直接说出来。你们瞧,不但有我这样的神棍,群众里面还有坏人呐!把我折磨,将我蹂躏,带我成长。

能有一位引路人,引导你完成一件很漂亮的作品。这样的故事,每每回想都很有滋味。

之后

一不小心说的有些太多了,我得保留点神秘感,最近三四年的故事以后有机会再谈,让我们先谈谈未来吧。

从阴沟里爬出来的故事中充满着机缘和幸运,那么我为何偏偏是那个幸运男孩呢?或者只是因为没有这份幸运从阴沟里出不来而已?如果能求得一个答案,应该会有助于我在当下和未来保持这份幸运。

首先自然是时代带给我的红利,那个时候我正好赶上了黄金期。加之我一直紧咬困惑自己的问题不放,等到机会来临的时候,我正好把原先的积累找到出口释放了出来。

那么,当大潮退去,又该如何做呢?我想我们能做到的依然还是保持初心,保持自己的求知欲望,这是我们仅能做的。结果是我们不可控的,但至少我们能收获沿途美妙的风景。

想到这里我忽然有些困乏,时候也不早了,远处传来的深圳特产——工地之歌也逐渐稀稀落落。我朝着窗外望得出神,想起自己曾在北京的窗台边听着外头的鸟吱吱的叫声;曾在上海的凌晨听到人行道被扫得唰唰作响;还有那西湖边,夏日的晚风掠过树梢,树叶在月光下窸窸窣窣;在九月的家乡,桂花在我的窗边唱起无声的歌谣。无论在哪,传来什么样的声音,我又从哪扇门走出去,遇上什么样的人,我都未曾改变呐!

我不在乎我不知道什么,缺少什么,也不在乎别人知道什么,拥有什么。我只在乎我自己的渴望。

那么我现在渴望什么呢?我暂时还不能说,因为往往目标被说出来之后就容易沉浸在已经成功的幻觉中,难以踏出艰难的第一步了。等事情浮出水面的时候再轻描淡写地说出来或许更热血沸腾。

光想想就刺激,不是吗?

参考资料

https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Tony%20Bai%20%c2%b7%20Go%e8%af%ad%e8%a8%80%e7%ac%ac%e4%b8%80%e8%af%be/%e5%a4%a7%e5%92%96%e5%8a%a9%e9%98%b5%20%e5%be%90%e7%a5%a5%e6%9b%a6%ef%bc%9a%e4%bb%8e%e9%94%80%e5%94%ae%e5%88%b0%e5%88%86%e5%b8%83%e5%bc%8f%e5%ad%98%e5%82%a8%e5%b7%a5%e7%a8%8b%e5%b8%88%ef%bc%8c%e6%88%91%e4%b8%8e%20Go%20%20%e7%9a%84%e6%95%85%e4%ba%8b.md