三十岁以前的往事(十):深圳

前言

近四个月的实习即将结束,我也没有在这里转正的打算,也许后面都不会在深圳呆这么长的时间了吧,想来这是我第二次来深圳,第一次是大四下学期跟父母来广州深圳香港旅游,在深圳停留了两三天,拜访了父亲的大学同学,去世界之窗逛了一天。

其实这次来深圳实习纯属意外,三月在申请实习岗位的时候,因为缺乏面试经验,我就打算先申请个字节跳动的实习岗位练练手,因为之前听说字节跳动的面试是很难的,负重训练效果更佳。由于我对 Go 比较熟悉,所以就打算选择基础架构研发工程师这一岗位。招聘官网第一个跳出来的 base 是在深圳,也没多想就选择了它。最终暑期实习的面试结果是,阿里挂在了一面,通过了字节跳动和上海腾讯的面试。当时心里想反正又不是校招,去了一个地方可能就要呆很长的时间,实习时长最多几个月,不妨就去个远一点的城市呆一段时间体验一下,所以最终就来了深圳。

2021.05.15 ,早上从苏州坐火车到上海,然后从虹桥机场飞到深圳,由于前一晚有雷暴,飞机晚点了三四个小时,最终是在下午三点多到达深圳宝安机场。飞机落地的那一刻,我为期四个月的深圳实习生活开始了。


田阿姨

七月的某天,我剪完头发出了理发店,正为自己找到了一家剪发只需要十五元的店而暗自得意时,我爸突然给我打来了微信电话,说他最近要来一趟深圳,田阿姨出事了。一开始他介绍情况的时候闪烁其词,但是经不住我的再三追问,最终跟我说田阿姨在公司里出事故去世了。我突然想起前几天田阿姨的儿子zzr在微信朋友圈里发了一条奇怪的话语,并且把头像换成了灰黑色纯色,之前还感觉很奇怪,也没有多问。

可能是因为这一段时间沉迷于手冢治虫的《火鸟》,脑子里满是生死轮回人的生命渺小如虫豸短暂似朝菌,所以当我听到田阿姨去世的消息后在情绪上居然没有任何的波动,就算是后面跟我爸一起去田阿姨家拜访时,也只能在表面上做出悲伤的样子,以迎合当时的气氛。

我非常尊敬且佩服田阿姨,不但因为她是一家外国企业设在深圳的分部的负责人,同时她也善于同各式各样的人交际,是一个各方面能力都非常强人脉很广同时也是一个很热心的人。其实,她早就可以从她的位子上退下来,但是考虑到目前还没有找到合适的接替人,所以她还是继续在企业 CEO 的位置上干了下去。所以说,这场悲剧本不应该发生,田阿姨本应该可以享受她那精彩丰富的退休生活。

我已经记不清第一次见到田阿姨是什么时候了。田阿姨是我爸的大学同学,也是南京人。早年和我爸还有其它的同学来深圳打工。二十世纪九十年代,借着对外开放政策的春风,东南沿海,特别是深圳,经济飞速发展,吸引着其它地区的人们背井离乡到此来淘金。我记得我家那个一万多块的液晶显示屏加台式机就是我爸用在深圳赚的钱买的,同时买我家房子的钱中有很大一比也是我爸在深圳赚的。那台电脑虽然现在连 CS1.6 都带不动,但是它是1995年生产的,比我的年龄都大。后来,我爸回了南京,但是田阿姨却定居在了深圳,只是在春节假期的时候回南京,每次回南京,她都会来我爸做客,早些年有好多次还在我家暂住,我爸也帮着她在这段时间把她被从养老院里接出来照顾。

我和她儿子zzr关系也不错,也算是从小玩到大的,虽然一年就是春节几天见面。在南京还没有全城禁放烟花的时候,每年春节我们都会买烟花来放。2008年那年冬天,雪下得特别大,我和zzr就在厚厚的积雪里挖洞,然后把小型炮仗点上火塞到其中,随后“砰”的一声,积雪被炸的飞溅。

田阿姨回南京的时候还喜欢把几个大学时关系比较好的同学以及他们的家人叫到一起聚餐或者出游,当然这其中包含我爸。那个时候南京玄武湖公园还没免费开放,入园是需要购买门票的。玄武湖边有各式的游船出租,出租点位于公园外,我们就租了两个脚踩游船,现在湖上玩了一圈,然后就在靠在公园里的河畔上了岸,然后让我爸和田阿姨的丈夫朱叔叔去还船。其实当时可能还没意识到逃票,但是在还船的时候被工作人员发现,最后老老实实地补了票。

其实我也是来深圳之前才加了zzr的微信,然后发现他也是星际老男孩的粉丝,跟他交谈才知道,他在美国读本科的时候很无聊,全靠看黄旭东和孙一峰的直播录像解闷。我记得在我读小学的时候,有一次春节他来我家我给他介绍过星际争霸一,并且向他演示了应该怎么玩,不知道是不是我一开始阴差阳错地给他启了星际争霸的蒙。

我大四下学期快毕业的时候,我跟我爸我妈去广州香港深圳玩了几天,在深圳的那几天,田阿姨热情地接待了我们,不但为我们联系好了住处、带着我们参观了她的工作单位,还找了几家她觉得不错的餐馆请吃饭。在前往其中一家餐馆的路上,经过了一个老旧的小区,田阿姨说她当初来深圳的时候就是住在这个小区里的,如今这里应该依旧住着初来深圳打拼的年轻人吧。开车途径一处街道时,田阿姨问我爸说还记得那里曾经是一个汽车站,如今也拆掉了改成集市了。我暑期实习刚开始时的一个周末去到田阿姨家拜访过,她热情地招待了我一天,没想到那次居然是最后一次见面。


都市一隅

清晨和傍晚清凉的微风和宜人的气温,午时强烈地要将人皮肤上的水分都蒸干的烈日。时不时黑云压城来一场倾盆大雨,雨滴像弹珠一样反弹地高高的,穿行在雨中,膝盖一下的裤子几乎全部湿透。但是风卷残云过后,高温又将地面的水分蒸干,只有在树荫下墙角边的低洼处还残余着些许水潭以及漂浮与其上的落叶。

下班已是八九点,经常会穿行过信合自由商城旁的一个喷泉广场,老少夫妻结伴地在环绕着低矮的喷泉池塘的大理石台阶上坐着,几只大毛狗安静地趴在主人的脚边,而穿着深蓝色校服的孩子们大叫着绕着喷泉跑来跑去,躲避着往来的推着婴儿车的年轻母亲。在喷泉广场的隔壁是川流不息的马路,喷泉中喷上又落下的水花声和乘凉的人们的喧闹声似乎掩盖了隔壁躲避闯红灯行人的车辆的短促鸣笛。喷泉广场的一隅,几个身穿绿色上衣的某互联网超市的地推人员正守着一张折贴桌子,桌子上放了一些生活用品,他们卖力地招呼着过往的行人扫码注册有礼品赠送。夹在喷泉广场和机动车道之间的人行道上停满了绿色和黄色的共享自行车,相比于三四年前的野蛮发展,现在的互联网共享自行车变得规范了些,还车时必须要将车辆停在画有白线的指定区域,否则会收取额外的费用。喷泉广场附近有许多或新或旧的住宅区,这些共享自行车像候鸟一样,早晨随着上班的人们来到各大地铁站入口和公交车车站,到了晚上,又随着下班归来的人们回到了喷泉广场附近的指定停车位。如果停驻些许时间,观察非机动车道上驶过的车辆,就会发现,有三分之二是各式的共享单车或电动自行车,而剩下的则是后面有一个箱子、喷漆涂色外形样式各异的外卖电动车,外卖小哥身着不同公司的工作制服,头戴头盔,将箱子里各式的物品送至千家万户,不但是外卖食物,还有超市里出售的各色物品、药店里的药、菜场里卖的生鲜。我相信,就像那七八十年代人们成群结队骑着自行车经过天安门广场的画面一样,身着各式制服穿行于城市大街小巷的外卖小哥、从街边小贩摊点到商场餐厅里随处可见的付款二维码、街边巷口随处可见的共享自行车,这些场景也会是属于二十一世纪一二十年代中国独有的时代画面。

在南山区我住的地方附近,给我的是一种南京新街口的感觉,既有上个世纪建造的民房和集市,又有现代化的摩天大厦和商业广场,崭新的南油地铁入口旁,是未涂漆的橙灰色的围墙以及其里侧的参天大树,树下,七八层楼高的老旧公寓的窗户依旧是金属边框内嵌玻璃的样式。川流不息的南海大道两侧林立着商业大厦和高层公寓,车辆从平整的柏油马路上飞驰而过,每到早晚,两侧的职场着装的人们来去匆匆,而与其平行的南商路则是上个世纪的画风。沿街商铺大都是木质的门面,卖都大都是一些衣服玩具生活用品等杂货,许多的大爷大妈呆在大树的阴凉侧乘凉。沿街的商铺中夹杂了一些理发店,其中最便宜的剪一次头才十五元,非常实惠。


最后的大小周

对字节跳动的最初印象,是18年的时候,当时只知道字节跳动是一家独角兽企业,抖音、今日头条都是他们做的,同时这家公司的推荐算法特别强,而且面试特别难。随着几年的发展,字节跳动迅速壮大,不但在中国,在海外也设立了许多的研发中心,同时也开始了大量的扩招,互联网编程论坛上随处可见字节跳动员工发的内推码,也听说了字节跳动的大小周工作制,虽然也没了解过怎么个大小周发,当时以为比 996 还要严重。

由于从 18 年开始我的主力编程语言由 JavaScript / Java 变成了 Go ,又听说字节跳动后端研发语言也是以 Go 为主,所以就对其有了一点小小的向往。今年有幸加入到字节跳动深圳对象存储部门进行了一段为期近四个月的暑期实习,体验还是不错的。

首先,我终于弄明白了大小周的意思是一周双休一周单休,感觉比 996 稍微好了一些,虽然它们都是严重违反劳动法的。而我自己亲身体会下来感觉挺辛苦的,主要是单休的那一周只休一天时间实在是太短,导致下一周精力有些不足。不过好在,今年国家重拳出击,互联网企业在重压之下纷纷取消不合理的加班制度,字节跳动是从八月份开始取消大小周恢复双休的,我个人也算是经历了这一个转折点吧。大致是在七月上旬的时候收到了从下个月起取消大小周的邮件,收到邮件的那个下午的工区和晚上的食堂里,到处都是在讨论这件事情的员工,同时关于这件事的讨论也上了各大互联网社区的排行榜。

这里需要提一件事情,单休那周是周末上班,刚开始实习的那段时间,我是天天都把工作电脑装书包带回住处的,但是上班的那个周末早晨,我有些神志不清,书包里装电脑的隔档的拉链没有拉好,导致我在路边想从书包里拿东西的时候,卸书包的过程中电脑被甩了出来磕到了路牙子上,咣的一声,电脑的一角顿时被甩瘪了进去,同时其它地方也有不同程度的凹陷。我的工作电脑是 MacBook Pro ,如果仅是边框摔坏的话,需要赔的修理费是 4750 ,其中个人承担 50%,当时我就无语了,不过目前还没有发现其它地方的损坏。自此之后,我的工作电脑就一直呆在我的工位上再也没有拿回过住处了。其实那一天我本来想请假翘班不来的,但是为了那一天四百的实习薪水外加免费的三餐还是来了,唉,赔了夫人又折兵,万恶的大小周。

对象存储部门所在的工区位于深圳南山区公园一号的办公楼里,七到十七层是字节跳动包下的,工区是一排排的长桌子,坐三到四个人,虽然从远处看有些拥挤,但是真正坐在其中,专心些代码的话是看不到旁边坐的人的,但是会看到对面的人,比较好的方案的弄两个显示器,完全当初视线即可。人体工学椅坐的挺舒服了,据说要好几千块很贵。工位的周围有单独的隔间作为会议室,从三人小会议室到十三人大会议室都有,不过由于工区人数的增加以及部分封闭研发任务,有些会议室被下线来满足这些需求,所以经常会出现等不到会议室的情况,大致提前三天至一周预定才能定好相应时段的会议室。

字节跳动用飞书作为办公软件,飞书是字节跳动字节研发的,集文档、会议、IM、日程、小程序等为一体,集成的非常好,在日历上制定日程,之中就可以预定会议室、拉相应群组中的人员加入、会议开始时能开启在线会议。在群组里发送的云文档可以小视图预览,云文档功能丰富,除了表格、列表这些基本的功能外,还有绘制流程图、嵌入日程、@指定人等高级功能,唯一可惜的是对 markdown 支持的不好。

公园一号工区的餐厅是在十一楼,早饭是九点开始,中饭是十二点开始,晚饭是六点五十开始,人都特别多,需要提前五分钟下来排队,否则晚下来两分钟,最后就要多排十分钟。特别是周日上班的下一个周期的周三中午,食堂伙食会变得好一些,即使提前五分钟下来排队最终也需要排二十分钟。个人感觉三餐伙食一般,分别是:米饭、面食/炒饭、素菜、素菜、小荤、小荤、大荤、大荤、汤、水果、饮料,基本能吃,其实对于我来说,吃饭只是补充能量的手段,没肥肉就行。下午三点半左右会有下午茶,内容主要是水果或者其它的一些小零食。

公司自己的小健身房在十三楼,只有些基本的健身器械、哑铃和跑步机,我去过几次,也算是基本够用吧,不过只有固定滑动轨迹的杠铃,没有自由竿,也就是说无法做硬拉、自由深蹲、自由卧推,所以我去的那几次就用哑铃练练胸肌,拿 25 kg 的哑铃做卧推,感觉还行。不过后来就去小区的健身区拉拉单双杠,健身房也就没去过了。后来公司和三楼的时代健身达成了合作,员工只要刷飞书就可以免费进健身房锻炼,不过我没有去过。公司所在的办公楼在马路边上,晚上下班的时候都会经过步行天桥,经常会有时代健身的工作人员上来推销健身卡,后来我都是走完天桥才把字节跳动的工牌摘下来,那些工作人员也没有再和我搭过话了。

字节跳动的工作时间因组而异,合同上写的工作时间是上午十点到晚上七点,其中十二点到下午两点是午休时间。我早上九点不到到公司的时候就已经有人坐在工位上了,而我走的最晚的一次,十点半,工位上还坐着不少的同事。就我所在的组而言,基本上都是早上十点到十点半到,晚上九点多到十点走,但是没有强制,如果想提前走或者晚点来直接在飞书群里说一下就行了,还是宽松的。我一般都是早上九点到,晚上八点半到九点走,感觉还可以。

我所在的对象存储组的同事都非常的 nice,负责人一 xl 是我的二面面试官,也是对象存储的负责人之一,目前主要负责稳定性建设,是我的 mentor ,有次好奇在领英搜了一下他的简历,发现他之前是在华为对象存储部门干了好多年,19年的时候才来到字节跳动的。由于对象存储在成都也有 base ,所以他基本上是这几个星期在深圳,下几个星期在成都,挺忙的,所以大部分时候就拜托负责人二 hh 来指导我。 hh 看起来特别年轻,目前主要负责管理数据,比如迁移、切流,在六月到七月的时候我跟着他将部分数据从老 backend 迁到了新 backend,我负责对索引进行过滤,期间遇到了一系列有意思的坑,例如负责提供数据的同事数据给错了,总之是一段很有意思的经历。根据 hh 的微信昵称搜到了他的 github 地址,发现他以前是在新浪的。负责人三是 lsb ,是我的一面面试官,在实习期间跟他的交集不多,不过在食堂吃饭的时候聊过几次天,他以前在腾讯干过,而且以前喜欢健身。其实我当初见到他时就感觉他跟我本科健身房里一个练力量举的同学长的很像,不但是是外貌还有身材。在八月的时候我参与到了另一个项目中,xl 设计好方案之后交由 cqs 负责,不论是 cqs 、qzg 还是 xzp ,我第一眼见都像是大学校园里的普通大学生,但是他们居然都有娃了,因为他们在午餐的时候和 lsb 愉快地交流着带娃经历,在一旁的我听的一愣一愣的。wwj 是一个比较有意思的人,天天讲些无厘头的笑话,而且,他走路的背影和我一本科室友一模一样,除了脸不一样外,身材、走路的姿势,发型几乎都一模一样。lwd 是五月刚加入的,经验比较丰富,估计是跟负责人一个级别的,组里其它的同事都含他 d 叔。lwd 来公司特别的早,而走的特别的晚,据他说是不想带娃。sll 是今年加入的校招生,海外研究生毕业,篮球打的特别地好。就我工区而言,给我的感觉跟大学里差不多,一大群大学生加一小群老师。

参与的第一个项目是一个数据迁移项目,由 hh 负责,项目背景简单来说是将一部分数据从老 backend 挪到新 backend。存储 backend 会给出 object 对应的一个 backendkey,我需要做的就是将老 backend 的 backendkey 筛出来,以 objectkey,backendkey 的格式存到 csv 文件中,将其传到一个 bucket A 中,负责迁移的同事读取这个信息,来完成数据的搬迁。而数据源是由另一个同事提供,也是以 csv 的格式存在一个 bucket B 中,其中也包含有 objectkey 和 backendkey 的信息。过滤的逻辑特别简单,从 B 中获取 csv,解析 csv,过滤 csv,将过滤结果写到 csv 中,将 csv 传到 bucket A 中。思路非常简单,但是为了做好,需要考虑几点:

  • 加速处理速度:毋庸置疑,goroutine workerpool 模式

  • 对处理过程进行抽象:除了过滤外,还有统计数据量的需求,可以将这部分业务逻辑抽象成接口,与控制逻辑解耦。Handle 里如果是过滤那么就是写 csv 的逻辑,如果是统计那么就是原子计数的逻辑。Finish 里如果是过滤那么就是 Flush + Upload 的逻辑,如果是统计那么就是将结果写到本地文件的逻辑。这样子的好处是在后面二次数据确认的时候,移除写 csv 逻辑仅仅只需要删除 Handler 数据组中写 csv 的 handler 即可。

1
2
3
4
5
6
7
8
9
type Handler interface {
Filter(data []string) (bool, error)
Handle(data []string) error
Finish() error
}

func Init() ([]Handler, error) {
// ....
}
  • 减少锁竞争:上述 Handler 会被并发调用,Filter 是无状态的,所以并发安全,Finish 只会在处理完之后才调用,所以也是并发安全的,但是 Handle 里,由于存在写文件以或计数的逻辑,需要做一定的处理。一开始是直接上锁的,但是测试运行时发现系统负载上不去,遂决定,将写逻辑在 Handler 初始化的时候额外开一个 goroutine ,而 Handle 里就是向 channel 里送数据就行了,而统计就是用原子运算。这样子负载一下子就上去了

  • 关于 csv 的处理:Golang 中有处理 csv 的包,但是为了实现两个特性,我又额外封装了两个处理 csv 的结构体,分别是重复读去重和分割写。前者是为了处理网络出错重试的场景,因为之前已经处理了 csv 中的一部分行,再重试的时候需要跳过这部分已处理的,实现起来很简单,就是记录一下处理到第几行即可。后者是为了实现一个需求,上传到 bucket A 中的 csv 最好是 600 MB 一份的。实现起来也很简单,依旧暴露和原始 csv 处理函数相同的接口,只不过在写的时候会先判断一下当前写的文件大小是否大于一个阈值,比如 600MB,如果大于了,那么就 flush 当前文件,然后再新建一个文件开始写,文件名采用后缀数字自增即可。详细可以参考我写的这篇博文:Go 处理 CSV 的两个场景 | Schwarzeni’s Blog

  • 错误处理:这里分为两步,

    • 首先,我程序的错误主要是网络错误,那么处理方案就是重试,参考其它项目已有的重试策略,采用最大值指数回退,从 (0, 最大值] 中取随机数的策略。其中遇到了一个写代码不严谨的地方,我以为 2s * 2s = 4s ,所以就用 time.Second * time.Second * 2 * 2 ,随后,重试的次数多了,程序就卡住了。原因是,time.Second1000 *1000 * 1000 ,那么再来一个平方直接爆炸。当时我以为其它地方写了死锁查了半天,连 strace 都用上了,结果发现确实 futex 调用,最后定位到 time.Sleep ,太坑了。

    • 其次,如果超出重试次数了,那么就是失败了,我记录错误的方式是写日志,记录 csv 对应的 objectkey,最后再跑一遍程序,专门处理这些之前失败的 csv。

    • 最后,出错的次数有点多,大量的 unexpected eof 错误,我想一般在内网环境应该不会出现大量的超时啊。重新 review 代码,发现在 GetObject 的时候,我没有设置 context 的 timeout,而默认超时未 5s ,但是对于某些大文件,5s 无法完成处理,所以就被自动掐断了。于是加大超时时间至 10min,unexpected eof 错误再也没出现过。

  • 需求对接:与代码无关,和同事协作遇到的问题

    • 先说结果,提供的数据出现 backendkey 缺失的问题,之前对接的时候说过如果为空那么就算是错误数据,直接忽略,从而导致大量的数据确实,幸好负责迁移的同事发现数据量不对,遂发现此数据源错误。之后应该尝试先实验性处理一部分数据,统计各个情况的数据量,因为数据源不一定可信。

    • 先说结果,统计过滤的数据不对,以为是自己的代码写错了,但是最后发现是统计的数据不对。需要统计的是过滤出来的数据量和大小,但是我一开始统计的是写的 csv 的文件的数量和大小。之后在对接的时候应该请提需求的人先举一些例子,便于理解需求。

    • 提供数据的同事在文档中已经写明了 objectkey 是经过 url encoded 的,但是负责迁移的同事没有注意,处理的时候没有 decode ,导致一部分数据丢失。之后在提需求的时候,对于这些重要的事项,需要在醒目处用红字标出,并反复提醒对接的同事。

在面试百度的时候,百度面试官曾问过我,如果给我三四台机器,那么你这个程序应该如何改造以加快处理速度,这一点我之前确实没有思考过,当时就参考 k8s 的模式答了一下。现在想来,我的这个程序即使是跑满负载 64 (64核心机器),最快处理完也要花费十几个小时,那么如果拆成四台机器并行执行,那么三个小时就搞定了。可以把我的程序拆成两个模块,任务分发和任务处理,任务分发模块负责获取所有需要处理的 csv,然后将结果分发至不同节点上的任务处理程序,任务处理程序执行完后将成功或者失败的结果再发还给任务分发模块。分发算法采用最简单的轮询即可,或者广播所有处理模块,挑一个任务最少或系统负载最低的节点分发任务,当然还需要处理节点挂掉的情况,这里就不再展开。

参与的第二个项目是一个同构灾备项目,由 xl 牵头,cqs 负责,我在其中负责某阶段双读主备桶的功能,需要在 head 和 get object 请求时,从主桶和备桶中选取一个最新的 object 返回。其实一开始是让我负责别的模块的,但是最后发现已经有现成的工具来实现,所以就给我安排了目前的任务。这个项目跟之前的项目不同的时候,为了实现这个双读主备桶的功能,需要对会部署到线上的程序代码进行修改,由于我是实习生,将来也不会在本组转正,所以写的代码必须是正确美观易删除的,代码必须是易于维护的,所以我花了大量的时间思考应该如何实现这部分的代码逻辑。

必须提的一点是,对象存储服务分为 api 模块,backend 模块,object meta 模块,而我修改的是 api 模块,可以看做是普通 Web 服务项目的 api 路由层,由于存在多个 backend ,所以在 api 模块中对不同的 backend 做了封装和抽象,对 api 路由层给出了抽象的接口,例如 Backend.GetObject ,实现是 UnionBackend.GetObject ,在这个里面会选择合适的 xxxxBackend.GetObject 来读取数据。

为了尽可能减小对代码的侵入修改,我决定在 api 路由层进行修改,而不是在底层的 xxxxBackend.GetObject 中修改,最主要的原因是,存在多个 backend ,一一去修改太麻烦,维护性差。但是在 api 路由层实现有一个巨大的问题是,为了判断主备桶哪个 object 新,需要读取 object meta 模块,但是在底层 xxxxBackend.GetObject 也会读取一次,就会造成重复读取的性能浪费。但是在和 xl 和 cqs 讨论之后,觉得双读主备桶这一阶段的持续时间不会很长,所以也就同意了我的想法。

为了将对主干代码的侵入性降到最小,我决定采用函数式编程和装饰器模式,例如:

1
2
3
4
func ApiGetObject(ctx context.Context, bucketName, objectKey string) {
// 此为抽象接口
err := backend.GetObject(bucketName objectKey)
}
1
2
3
4
5
6
7
func ApiGetObject(ctx context.Context, bucketName, objectKey string) {
err := ProxyReadWrapper(/* 其他参数... */,func() error {
return backend.GetObject(bucketName, objectKey)
}, func(drBkt string) error {
return backend.GetObject(drBkt, objectKey)
})
}

这样双读主备的逻辑都收敛到 ProxyReadWrapper 了,实现代码的逻辑解耦,同时,由于对主干代码修改不多,如果出问题或者想移除这个特性,需要修改的地方也极少,而且,由于读主读备这两个函数逻辑是由外部传入的,但是也便于单元测试,因为测试的时候可以模拟对象读取,将读取对象的函数内容改为设置 flag,最后判断一下 flag 是否设置正确即可。

具体选主还是选备的逻辑这里就不再赘述了,没啥技术含量。有一个点是,或者 object 的元数据是需要查询 object meta 模块的,发的是 rpc 请求,我本地跑这个单元测试是没有环境的。于是乎,我也对这个 object meta 模块抽象了一个接口作为 ProxyReadWrapper 函数的一个参数,这样,在测试的时候,我自己写一个 mock client 即可,不发网络请求,根据我的需求返回指定数据即可。

这样设计,不但可以实现对主干代码修改的侵入性降到最小,而且 ProxyReadWrapper 是纯的控制逻辑,通过接口抽象和函数参数实现解耦,减少单元测试的测试环境依赖,简化单元测试的运行。

在 merge request 被接受后,cqs 希望我把这部分的功能测试也补全。公司的功能测试框架是用 python 编写的,那么就需要用 python 来编写代码。功能测试本质上就是模拟各个情况的请求,也就是用 client 向 api 模块发请求,比较返回结果和预期是否一致,有点像黑盒测试。我这里的情况有些多,比如,主有备无,主没备无,主有备有主新,主有备有备新,主无备无,然后需要测 get 和 head ,数量翻倍。对 api 的测试流程大同小异,先根据场景向主备桶传数据构造相应的场景,然后模拟 get 和 head 请求,之后比较结果,最后删除之前传的对象。

复制粘贴重复的代码逻辑也行,但是这样实在是太丑陋了,所以我还是想借鉴之前实现 ProxyReadWraper 的思路,将构造场景和测试想分离,设计如下,主要的代码都在 Base 中,用于构造场景以及清理环境,代码多,所以略去了一些代码,而 HeadGet 里就一点点代码,主要就是负责发请求和判断结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Base():
def test_data_not_in_drbkt_but_in_main(self, fn):
# upload object
# some code ....
fn(obj_key, main_md5, dr_md5)
# delete object
# some code ....

# 此处略去其它场景

class Head(Base):
def run_test(self):
self.test_data_not_in_drbkt_but_in_main(self.assert_test_data_not_in_drbkt_but_in_main)

def assert_test_data_not_in_drbkt_but_in_main(self, obj_key, main_md5,dr_md5):
resp = self.main_client.head_object(obj_key)
self.assert_md5(resp, main_md5)

class Get(Base):
def run_test(self):
self.test_data_not_in_drbkt_but_in_main(self.assert_test_data_not_in_drbkt_but_in_main)

def assert_test_data_not_in_drbkt_but_in_main(self, obj_key, main_md5,dr_md5):
resp = self.main_client.get_object(obj_key)
self.assert_md5(resp, main_md5)

在跑功能测试的时候又遇到了一个坑,由于这个特性是在指定时刻开启和关闭的,所以需要给桶动态添加和删除配置信息,这部分模块的代码也做了一些修改,但是问题是,功能测试环境的 api 模块会自动更新,但是管理桶的模块不会,还是老的版本!所以我的功能测试一直都没有跑过,最后终于排查到是相关模块没有自动升级导致的。

离职的前一晚 hh 请我吃了一顿晚饭,也跟我讲起了以往的一些经历,经验丰富,技术也很厉害。其实没有转正的主要原因还是因为 base 是深圳,距离南京有点远,否则的话我确实可能就直接转正了,团队氛围非常不错。这绝对是一次难忘的经历。


我的合租室友

我租住的是那种按照房间出租的合租房,共用洗手间厨房。我租的合租房有四个房间,其中一个室友zj有一次敲门询问洗衣机中的衣服是否是我的,我在给出否定的答复后,主动自我介绍了一下,现在是研二,是来字节跳动暑期实习的,没想到他听了之后跟我说,他也是在字节跳动实习的,并且是从今年一月份就开始了。他也是研二,就读于华中科技大学的软件工程专业,在抖音国际版做前端。由于经历和身份相似,于是在后面也经常到彼此的房间里聊天,有几次聊到了凌晨一两点,主要就是聊一聊个人的经历以及对于技术的一些看法,周末也经常一起出去吃饭。在七月份的时候他离职去了杭州蚂蚁金服实习。哦对了,他喜欢玩原神 QAQ。

我和其它的室友并没有产生过多的交集,但是他们的素质都还是可以的,并没有什么不良的嗜好以及不好的习惯,洗澡的时间以及使用洗衣机的时间都可以岔开来,相处都还算比较愉快的。


秋招、星际酒馆、许某翔

阿里云二面挂了,复盘的时候我感觉我失利的最大原因之一是,面试官在问我八股文的时候,我多次切出去看了我之前做的笔记,这应该算是作弊行为。其实面试官问我的那几个问题我都记得八九不离十,但是为了保险起见当时还是去看了笔记,为了让回答更完整正确。浏览器监控鼠标跳出页面失焦的防作弊方案有一个巨大的缺陷就是无法检查浏览器运行环境是否为虚拟机,因为目前浏览器并没有相关的 api 接口供 js 查询,所以这是一个防君子不防小人的方案,无奈我当时没注意到这点,因为我以为阿里二面应该还是电话面,没有提前做好准备,面试之前的五分钟才知道是浏览器视频面试,有点慌张,除了没有准备虚拟机外,在面试的过程中也有点紧张,连实习期间做的项目都没有描述好。

这个作弊行为应该会作为一个污点永远地留在我的面评上,估计后面永远都和阿里无缘了吧。其实将心比心,如果我来招聘,发现面试者存在看小抄的行为,我也一定是会把这个人挂掉的,但是挂掉不是因为他作弊,而是他作弊的行为被我发现了。不论是面试还是别的什么工作,目标都是在合法合规的前提下出色高效地完成任务,何谓合法合规?如果面试者有本事让作弊的行为不被面试官发现,能流畅自然地对面试官提出的问题进行作答,我觉得这完全没有问题。就我个人目前的理解而言,对于一个面试者八股文的考察,并不是考察他的记忆背诵能力多么地强,而是考察他对于这个知识是否有过了解,如果在面试之前他就对这个知识点做了笔记,同时在面试过程中能迅速定位到该知识所在的笔记位置,然后流畅自然地说出答案,我觉得这个完全没有问题,但是如果在面试官问出问题后,面试者搜寻笔记花了十几秒,然后机械地读答案,那肯定是不行的,面试官不是傻子,这种程度的作弊是能发现的。

面试八股文做小抄这种投机取巧的行为确实不太好防,除非是线下面试或者是有系统级别的防作弊措施,否则防不胜防。除了八股文外,还有面试必考的算法编程题。大部分公司现在都喜欢让面试者做几道 leetcode 上的题目,所以有一些人就会大量地刷题,明面上说是为了提升做题的感觉、积累做题的经验,但是谁又不想在面试的时候碰到自己做过的原题呢?面试时对于面试官给出的算法编程题,如果是之前做过的,那么就需要假装认真审题、思考,一点点地编码,甚至可以故意先给出一个时间/空间复杂度稍高的解,给面试官一种自己没有见过这道题目的假象,而不能见到自己做过的拿来就默写。因为我觉得,编码题默写就像是作弊一样,因为编码题考察的是面试者对基础数据结构和编程技巧的运用以及逻辑思维能力,而不是考察谁背题背的多。但是如果你默写题目的行为能不被发现,那么对于面试官来说,他就考察到了前者,而不是后者。

其实对于八股文和算法题的考察也大都是出于无奈,如果面试者有着较优秀的项目经历,并且在介绍项目时能将八股文的知识融合其中,那么我觉得面试官也懒得专门去问八股文,算法题写一道快排或者堆排就可以了。

所以,合法合规的本质是在了解规则且不被人发现的情况下不择手段地以最小的成本达成目的,小人装模作样地做了一辈子老好人,那么在别人的眼里他就是老好人。人无完人,做人要学会在不同的人面前塑造起不同的人设。不可否则存在那种表里如一的谦谦君子,但是非常抱歉,我做不到。对于那些浪费时间疯狂内卷的事情,比如八股文和算法题,我宁愿用小人的手段以最少的时间达成我的目的,同时不被他人发现。但是对于重要的事情还是需要认真对待的,对于基础知识的学习,主要学习资源应该是那些经典的书籍,而不是三十天从入门到精通系列;对于代码的编写应该是在能满足工期的前提下精益求精不写 trick code,写好单测功测,对自己写过的每一行代码负责;对于需要帮助的人,就应该在不影响或者轻微影响自己利益的前提下尽最大可能给予帮助。

其实在参加的其它秋招面试中,我也没有看小抄,可能是因为太想进阿里云,所以才想不择手段地做一些保险,没想到没考虑周全。这就像是卡牌游戏打到决赛圈,失误把战力最高的卡牌卖了。

星际酒馆在八月一在星际争霸二游戏大厅推出就立刻火遍了星际圈,每把八个人把把秒排,据说网易暴雪已经在联系该地图的作者了。星际酒馆的灵感来源于暴雪的另一款游戏炉石酒馆,但是我对后者不太了解,仅仅知道它是一个卡牌游戏。星际酒馆也类似,一局有八个人,每个人选择的角色都有不同的技能。每一轮会随机发给你一些卡牌,你可以用晶体矿购买,也可以用晶体矿刷新卡牌,对某些卡牌进行排列组合后会产生一些 combo 效果。也可以用晶体矿来升级你的酒馆,随着酒馆等级的提高,你所抽到的部分卡牌的等级也会提高。气矿可以用来为你的卡牌升级技能。卡牌对应的都是星际争霸二中的单位,当发牌组牌结束后,你就会和剩下的七个人中的一个对战,对战的单位就是你持有的卡牌所对应的单位,你的血量为一百,如果输了会扣血,当血量将为零则为失败。

由于不同的卡牌组合会产生不同的 combo,所以在抽卡环节就会希望得到相应的卡牌,但是系统发卡是随机的,所以经常会出现希望得到卡牌 A 但是怎么刷新也刷不到的情况,导致卡牌不好提前出局。也会出现运气极好想要什么卡牌就能来什么卡牌的情况,但是一般情况下,至少对于我来说,这种情况很少出现。

我已经打了快两百把星际酒馆了,越玩越觉得我玩星际酒馆的过程就跟我秋招的过程差不多,甚至说,跟人生差不多。星际酒馆里有一种经典的场景:卡牌 A、B、C 可以组成一个 combo ,目前持有卡牌 A、B ,有晶体矿 10 且当前发的卡牌中没有 C ,升级酒馆需要花费晶体矿 8,购买 C 需要晶体矿 3,重新发牌需要晶体矿 1,卖掉一张卡牌可以得到晶体矿 1。目前有两种选择:

  1. 直接花费 8 晶体矿升级酒馆,之后可以刷新两次

  2. 刷新酒馆最多 7 次,一条路走到黑,直到找到 C

选择一,那么就代表放弃了本轮发牌组牌环节,仅仅升级了酒馆,但是可以因为战力不够在此轮被对手打死扣血。但是可能能在下一轮抽到 C 或者更好的卡牌。

选择二,那么就把希望寄托于多次刷新可以刷出 C,如果能刷到,那么一切都好说,如果刷不到,那么结果跟选择一的结果差不多,但是更坏的是,下一轮酒馆等级还是维持现状。

当初面阿里的时候,由于流程比较慢,已经到八月中旬了才开始面试,当初我希望全力以赴一把就能过,所以就没有投别的单位,实习单位的转正也放弃了,但是没想到二面翻车,虽然被另一个部门捞了但是面完一面流程也结束了,估计是一个第一次二面的面评很差,毕竟作弊可能被发现了。到此时已经是九月了,好多大厂的 hc 估计都差不多没了,而且因为那一次二面对我的打击太大了后面也没心情面试了。这就是之前选择二最差的情况。

现在玩星际酒馆遇到这种选择,我就能想起秋招的经历,而且很奇怪的是,如果我选择二,那么大多数情况下都刷不到想要的卡牌,但是选择一需要很大的运气,因为升级酒馆也不能保证能遇到更好的牌。

星际酒馆中,你只能持有七张卡牌(还是九张?记不太清了),而对于一个人来说,你能拥有的身份、技能、资源也是有限的,你不知道人生会给你发什么样子的卡牌,而且人生发给你的卡牌也有限。一级酒馆的时候一次只会刷新四张卡牌,但是到六级酒馆时一次会刷新七张卡牌(还是五张?记不太清了),也就是说,随着你阅历、知识的增加,你说能接触到的机会就会越来越多。把握住你目前持有的卡牌,购入有潜力的卡牌,对于已经没有用的卡牌也要果断卖掉,对已有的卡牌进行合理的排列组合,以达到利益最大化。

其实之前提到的,在不同人的面前立不同的人设,让我想起了最近听的一期宋宇报刊选读,讲的是初中生小林被室友许某翔修改了中考志愿,而且在之前做室友的时候许某翔找各种接口找小林的麻烦。许某翔在老师和同学的眼里是学习勤奋成绩优秀的学生,而且平时非常高冷沉默寡言,对周围的同学讨论的内容都不屑一顾,其中应该就包含游戏。后来许某翔跟小林因为游戏上认识而成为了好友,但是由于小林对周围的同学透露了许某翔玩游戏的事情,许某翔对小林的态度才急转直下的。我感觉,许某翔在老师同学心目中塑造的人设因为小林的原因被破坏了,而人设一旦在别人的心中崩塌就很难再次塑造。如果他下次再对周围的同学不屑一顾,那么别人就会议论说瞧不起谁呢回家还不是天天打游戏。

正如小林破坏了许某翔在同学老师心中的人设一样,因为我自己的疏忽,我也破坏了我之前想给面试官立的人设。人要脸树要皮,那次二面失利后我自己自闭了好久,但是由于是因为我自己的原因导致的人设破坏,我也怪不了别人,但是许某翔是因为小林的原因导致的人设破坏,而且他们还是室友,估计看小林越看越不顺眼,同时初三正是叛逆期,难免做出一些出格的事情。我这里并不是给施暴者辩护和洗白,只是因为相似的遭遇,将心比心了一番,对他那时的心境也能体会一二。


日常

超级喜欢一家面条馆卖的茄子牛肉盖浇面,吃了好多次了。

在忙碌的一周结束了,周末确实没劲学习了,只想躺平。

又把星际争霸二三部曲战役打通了一遍。

早晨跑步体验不错,七八点出去跑步,然后后来洗个澡,九点多出发去公司吃早餐,十点到单位上班。

不小心被公司电脑摔坏了,离职了时候赔了两千。不过据 IT 说,按照以前的定损规则,可能要赔四千多。

在外面吃,麦当劳居然是比较便宜的选择。当然,麻辣烫和面条价格也算可以,二十上下。在离职后退租前的两三天每天点不同店家的汉堡外卖,价格都是在五十上下。

在六月的时候深圳也出现了疫情扩散的现象,我那个室友所在的办公楼被封了一晚,他当晚昨晚核酸在公司睡了一晚第二天才回来的。本来他们组还要去北京团建,结果也泡汤了。对于我的影响是,小区的一个小门封了,导致我上班多花十分钟,同时去隔壁的沃尔玛和麦当劳也多花六七分钟,本来三分钟就能走到的。

花了一千五换了自己用的 MacBook Pro 的电池,感觉好贵啊。换完电池又发现摄像头坏了,如果想换摄像头的话只能换屏幕,无奈买了一个外接的。

——- 2021.09.17 深圳宝安机场