其实读高中时就知道人生大概只有3万多天，那个时候还算了下1万天的时候大概是多少岁，当时算出来是27岁多，就想着还真的有蛮远的哈哈哈，没想到这一天已经在今天抵达。两年前公司入职培训的时候，认识了一个生日只差一天的朋友，当时想的真的是很巧啊。于是后面每年就变成了我先祝她生日快乐，然后她祝我生日快乐。昨天大年初一的时候，在清一色的拜年祝福朋友圈里，看到了她也来地球1万天了，心想这个日子还是挺吉利的，那么我就是明天了[偷笑]。关于今天的话，其实也就是待在春节假期中待在奶奶家的普通一天。中午来了我爸的两个初中同学来拜年，听着他们聊公司业务聊了半天就悄悄跑去睡觉了。晚上的话在家给爸妈测了血压，爸爸的血压控制的还可以，妈妈则是正常水平，我的话高压只有110哈哈哈，看来休息时压力还是小很多啊，之前体检经常测出120和130的。要说最近几年做的最正确的一个决定，就是没有待在学校里读博了。之前一个端午节在奶奶家的时候，奶奶问我：怎么不跟着你们导师读个博士，和舅舅一样在大学里当教授呢？我说：现在读博士的时间很长，说不定要4-5年，出来后也要和别人在学校里竞聘岗位，说不定到30多了连个编制也没有。奶奶惊叹：读了博士也没有编制啊，那还是算了。（说完后我们全家都笑了哈哈哈哈）其实之前有段时间还有读博这个想法的，但是跟着同学一起去实习之后彻底打消了这个念头，因为企业里面工作的反馈真的很适合我，能明显感受到自己的成长。不像做研究一样，费很久时间还不能确定一个结果。其实那个时候我们导师拿到优青跳槽到了清华，那边的团队其实很好，他也问了我们同门有没有想去读的。其实对于想读的同学来说，要拒绝这样一个机会是很难的，毕竟那里的光环很大，要是和高中生说这个事情，估计做梦都要笑醒。没想到我们4个同学都委婉含蓄地拒绝了他哈哈哈。毕竟我们知道去了之后也是得自学成才，读研真的读累了。而且当时也在实验室里看到了不少负面案例，比如我们大师兄读了8年才毕业，身体还积累了不少病[允悲]，好在最后毕业了换了个好点的学校当老师。到企业里上班虽然也有比较蛋疼的时候，但是休息时间是完全属于自己的，基本不用想着上班的事。不像在学校里，总是待在一个环境氛围里。还记得那个时候发的微博，吐槽的占比占了很多，一些小的事情都容易让人自闭。现在想想这些坏情绪也许是当时疫情，和老朋友的交往受限，各方面负面因素叠加导致的。好在，现在是生活得越来越开心了[偷笑]又写了不少了哈哈哈，那就祝自己活出真我，在未来的两万天不设边界地探索世界和人生
自从2018年第一次来到校本部绿树成荫的民主楼前时，我的生活便与中南紧紧交织在了一起。要说到与中南的缘分，我想最早可以追溯到读高中的时候。当时的我们还在讨论着以后要去哪所城市读哪所大学。我当时心想，一定要读一个离家不远而且还不错的大学，正好省会长沙就有很多所，可是国防科大分数太高，湖南大学就在岳麓山下太过熟悉，还有个中南大学听上去名声比较响亮，要不就选它吧。于是，中南便成为了我高中时期埋藏于心底的奋斗目标。尽管由于种种原因，我在高考时与中南失之交臂。幸运的是，我凭借着保研的机会再次来到了中南的怀抱，开始了短暂而又漫长的读研时光。2018年的时候，计算机学院还叫信息学院，我也还在读大四。未来的导师建议我提前过来熟悉环境，于是每周都要到赶到中南的实验室过来学习。那个时候的地铁还没有3/4号线，从长沙另一端奔波而来的我尝试过了各种交通路线，最后发现还是换乘立珊专线比较快，无论是多么复杂的交通情况，司机们总是能飞速驶过，直达终点站。尽管很早以前就知道，中南不仅校区很多而且校园面积也很大，但是我不知道的是每一个校区都有这么大。每次从校本部门口的中南大学站经过清水路和升华大道走到升华楼前，都需要耗费二十分钟左右。在经历了漫长的奔波之后，抵达实验室的我只能勉强打起精神开始一天的科研探索。熟悉了情况之后我才发现，几乎每个人都有电动车，而且在路上经常能看见电动车上坐着一对情侣，然后从我身边呼啸而过，也许这就是中南最常见的秀恩爱方式。所幸实验室的同学告诉我，校门口旁边有学校运营的观光车，只用1块钱便能从校门口坐到学校里面来。这样，我就可以一边欣赏校园风光，一边慢悠悠地抵达实验室。还记得研一的时候，一切都是那么新鲜，新认识的老师、同学、室友和师兄师姐，慢慢地加入了我们的生活，在平凡的日子中增添了许多趣味。读研的生活和本科还是区别很大，本科中最看重的课程成绩在现在无足轻重。下课铃响之后，也不能和同学想去哪就去哪玩，而是得回到实验室开始一天的实验。当时的我们虽然说科研水平不算高，但是每个人都有着冲击顶级会议的梦想。除了学术研究以外，老师们还与很多公司开展了项目合作。于是我们每天都得在许多并行的任务中快速切换，恨不得分出三个身来各自负责一部分。实验室的楼道里，有着一个出水速度很快的饮水机，只需要接上电线和自来水管，它就能够以过滤的方式源源不断的生产纯净水。饮水机的旁边便是一个圆形的玻璃桌，其周围还环绕着几个塑料靠椅。每天过去打水的时候，简单的和同学聊一下日常生活，便是我们一天当中来之不易的休闲时间。在饮水机旁边的房间里还有着一个乒乓球桌，每当晚上吃完饭后，小小的空间里总是人声鼎沸，挤满了过来切磋球技的同学们。当时有两个师兄水平很高，我们每次和他们打几个球就被虐杀了，只剩下他们两个在球桌上高手过招。快乐的时光总是过得很快，很快就到了放寒假的时候。还记得当时已经快到了小年，学校里的同学已经寥寥无几，食堂也只剩下最后一个五食堂还在营业。依稀记得当时已经传出来了一些疫情的消息，但是大家都没有太过重视。直到实验室的同学坐高铁回家路过武汉的时候，发现从武汉上车的人全部都戴着口罩。后知后觉的我们听到了这个消息之后，终于意识到这次疫情很有可能改变我们未来一段时间的生活。在家里度过的漫长时间里，开学的时间一再被推迟，直到最后才确定了只有毕业生这个学期才返校，其他的年级全部改为线上课程。那时我终于才发现，原来我们在中南度过的每个平凡的日常，都是一连串发生的奇迹。平时吃腻了的三五食堂，车水马龙的升华大道，还有和蔼可亲的宿管阿姨，都这样悄然无息的远离了我们的生活。经过了整整一个学期的线上教学之后，我们终于回到了朝思暮想的中南，看见许久不见的老师和同学们，大家地脸上都情不自禁地露出了灿烂的笑容。到了研二之后，所有的课程都已经结束，我们每天再也不用在校园中来回奔跑，而是开始重复略为乏味的实验室生活。研二除了完成最为重要的开题报告以外，我还担任了我们老师C++课程的助教。助教的任务其实也还算简单，主要就是在实验课的时候帮大二的同学答疑解惑，其次就是收集作业，以及检查课程设计。以前读本科时，一直都不知道为什么老师会这么厉害，一下就能看出我们作业中的问题。直到当了助教之后，我才知道这一切都是经验的积累。两个班的同学虽然都在一起学习，但是大家的编程水平还是参差不齐。看到一些投机取巧的同学解释不清楚实验报告时的样子，我总会回想到我们本科时也是这样试图蒙混过关的。这个时候除了在心中偷笑一下，我也会告诉他们下不为例，除了上课的时间之外，课后也要花些时间学习。转眼间，就来到了研三，许多同学暑假都去公司参加了实习，以便为这个学期开始的秋招做好准备。实习和秋招的过程，便是我们与社会接触最为广泛的一段时间。尽管疫情还没有离我们远去，但是2021年的经济发展还算顺利，大部分同学都找到了自己喜欢的工作，也有些同学决定继续读博深造，无论大家的选择如何，我都希望他们能够前程似锦。回过神来，一下就到了待在中南的最后一个学期，我们三年的研究最后都要汇聚在一篇学位论文中。撰写论文的时候我才发现，校本部最为热闹的时候是晚上10点。这个时候，不仅许多同学骑着电动车从实验室返回宿舍，操场上也满是正在慢跑的同学。正是这些起早贪黑的身影，不断鞭策着我要抓紧时间完成论文。写论文的过程尽管十分困难，但是完成之后的成就感却是无与伦比的。老师也告诉我们，如果以后不读博士的话，硕士论文大概是我们人生中最为重要的一篇著作了。为此，我们不得不精益求精地完成论文的每一个段落。在初中的某一节英语课上，英语老师问及到大家未来的梦想是什么，轮到我时我不假思索地站起来就说到：“I want to be a computer scientist”，当时可能也只是随口一说，但是没想到我未来却与计算机专业结下了这么深厚的缘分。在科学前沿漫步了三年之后，尽管离我当初成为计算机科学家的理想还挺遥远，但是我有幸在各种项目中受到了系统的科研训练，并且深入体会到了做一流研究的不易，可以说在中南的这三年便是离我理想最近的时刻。无论未来离中南有多远，我想我与中南的缘分一定会永远的延续下去。2022/05/31
参考文献这个东西说重要也重要，说不重要也不重要，但它却是每个评审老师都会看的地方。简简单单一看，就能知道这篇论文的作者有没有读文献，读的文献质量如何，以及有没有认真整理文献。如果说参考文献的档次可以因人而异，但是参考文献的格式却每个人都是一样的，一般情况下都以GB/T 7714-2015为准，但是也有少数地方不一样。三年前还在读本科的我，被博士师兄安排得明明白白，每周都在给他的大论文整理参考文献。当时不知道为什么他对每个细枝末节都关注得那么细，现在到了自己论文快盲审的时候才知道，评审老师可以很容易发现参考文献里面的细节问题。只是当初的技术手段过于落后，拿着Endnote一个个导入文献的元数据，每次修改之后都得手动把文献发给其他同学。样式文件也没有找到最完整的，最后还得自己去编辑样式文件[允悲]。要是用现在的Mendeley/JabRef管理Bibtex，然后再配上Github同步，那真的不知道有多方便，只不过这套方法就不太适合用Word的同学了。由于当时的历练，导致我比较熟悉参考文献这方面，每次很快就能发现别人的格式问题。之前给师兄的课题组讲过几次报告，还帮他给学生培训了几次。最近也是，帮很多同学都检查了一下。最搞笑的是有个同学的参考文献居然不是按国标来的，我当时就说你这个论文送出去肯定得被老师挂掉[笑cry]，吓得他赶紧认真修改了一天。因为下周就要送审，我下午就去实验室重新画了几个图。刚准备回去的时候，隔壁课题组的同学就拿着论文走了过来问我参考文献对不对，然后我就拿着他的论文给他找出来了一堆错误[允悲]。他们组的大师兄也是的，让他们引用100篇左右的文献，实际上对于硕士生而言，根本不可能读过那么多文献，到最后只能是按关键词瞎引用，而且引用的越多越容易出错。到最后我让他回去删一点文献，到时候师兄问起来就说学院要求不引用自己没看过的文献（事实上也应该如此）。在修改的过程中，另外一个同学看到了这一景象，笑着喊了我一声Q老师哈哈哈。这让我回想起来了研一的时候，坐在我现在这个位置的柳师兄，也是经常旁边坐着个同学，然后师兄就在那指导他的实验。虽然说我既谈不上指导，关注的也不是实验部分，但是这还是有一种似曾相识的感觉哈哈哈哈。也许另一个世界线的我，确实可能已经在当老师的路上了[二哈]。不知不觉就写了那么多，如果有朋友最近也要论文送审了，那么也可以把参考文献发给我帮你看看，说不定就找出来了一堆错误哈哈哈哈。
本来想等着公司的秋招礼盒送到后再写这个总结的，但是快放假了还没有等到，就只好先写写了。今年秋招我没有像一些同学一样海投，一共只投了5个公司，面了4个，最后拿到了2个公司的offer，此外还收到2个创业公司的邀请没有去。下面就分别写一下我投这几个公司的经历吧。英特尔：最开始实习的时候其实比较想去英特尔，因为英特尔的工作环境和福利待遇都比较好（每天下午5点准时下班），其技术实力更不用说，而且我们实验室有个博士师兄之前在那实习过1年。4月份的时候面其他公司一直没面过，于是就想请他帮忙内推一下。当时他说给英特尔那边推荐没问题，但是还是建议我去互联网公司实习，因为英特尔的节奏比较慢，学到的东西可能比较少。于是我就按照他的建议暂时没有投英特尔，接着面试互联网公司。后面到了9月份的时候，英特尔的秋招正式启动了，我当时是想着等英文简历准备好再投，没想到一下就快截止了。最后是我同学问我投了没，我才火速找了内推投过去。实习的时候公司有很多大佬都是从英特尔出来的，在英特尔实习过的同事也说那边工作起来很舒服，这就更增加了我对英特尔的好感。笔试前听说英特尔的笔试偏难而且只有一次机会，我想着凭借着我长期的积累应该没多大问题。没想到运气不太好，笔试系统和我的Win7不是很兼容，题目中许多字都显示不出来。没有办法，面对着6个选项的单选题我也只能瞎猜了。到最后没有悬念，应该是笔试成绩不太好，一直没有收到面试邀请。微软：微软也一直属于我很想去的公司名单里面，微软的实力不仅在工业界数一数二，而且在学术界也有着微软研究院这样的顶级科研机构，特别是做我们系统方向的，如果说上海交大排第一，那么后面一定是微软亚洲研究院了。微软的招聘系统比较神奇，一般内推过去的简历都会被自动识别，但是他们全英文的系统识别得不是很好，很多内容都需要自己补进去。刚开始填的时候没什么问题，没想到把本科学校填进去后找不到对应的匹配项，然后看到下面一个复选框“我的学校不在列表中”，当时我就心想这下凉了，估计我们学校以前都没有人投过微软。在最顺利的一次笔试之后，我一直没等到微软的面试通知，果然凉了哈哈哈。因为当时想着投对口一点的岗位，就选的是微软上海做云计算的部门，后来了解到他们的招聘需求比较少，所以招聘要求会高一些。后来到了10月底的时候，微软开始补招，打开一看果然是微软苏州那边做桌面软件的部门，没有办法也只好试一下了。不过总体看下来，微软的面试体验是所有公司里面最好的，面试官说话都很温文尔雅，面试的时候也不会给太大压力，都是循序渐进地引导面试者回答。前面三面都还算顺利，到最后一面的时候，大部分公司都是HR出来面试，和面试者聊聊天了解一下基本情况。结果微软的最后一面是经理面，不仅要聊HR要聊的，还问了些技术。因为面试官问的技术我平时用的比较少，答得不太好，不到30分钟就结束了面试。我当时其实就有点意识到可能过不了了，过了几天登上招聘系统一看，果然流程就挂掉了。虽然说微软苏州我不是很想去，只是想体验一下外企面试的感觉，不过没面过还是有点遗憾的。所以说，微软毕竟还是顶级软件公司，想进去还是不那么容易的啊。华为：其实最开始没打算投华为，只不过架不住华为HR的电话轰炸，只好投了他们的那个部门。后来想着反正投的投了，还不如投对口的部门，没想到后面的经历却是一波三折。可能是投的部门招人比较少，这次华为的面试难度估计是秋招里面最高的了，前两面上来就是做题，第一面题勉强做出来了，第二面题虽然不难但是不太熟悉，几乎没做出来。二面结束后HR说面试官们的意见不统一，后面又加了一面。这一面就不得了了，HR找了个大佬过来面试，我把我读研期间所有的积累都快讲完了才停下来。最后还是没有通过，可能面试官们觉得我编程能力偏弱了一点。不过也没事，华为的工作强度也不是一般人能承受的了的，还是身体健康第一。腾讯：腾讯其实是我面试的最多的公司了，之前在找实习时就面试了3个部门，这次秋招我还没投就又面了2个部门。第一个部门大概是7月底面的，面试时自己感觉回答的还可以，但是可能还是有所不足吧，就没有过。第二个是我同学实习的部门，面试前稍微多准备了一下，就顺利通过了第一面。第二面时虽然没有所有问题都答得很好，但总归是大部分都答出来了，勉强通过。HR面就不用说了，简单的走一下流程，表达自己想来的心态就顺利通过了。后面也是顺利收到录用意向书和正式offer。阿里：暑假的时候一直在阿里实习，所以就只需要走转正流程就可以了。转正的话相对于秋招还是容易一些，不过也需要看所在部门的名额是否充足，不然有可能会转正失败。转正流程主要就是答辩，主要介绍一下自己实习期间的工作和感悟。答辩的时候有4个人在场，分别是师兄、主管、二级主管和HRG，起主要决定作用的是二级主管和HRG。一般情况下实习只要认真做，主管还是会想留下你的。答辩时我自己讲了20分钟左右，然后就是提问环节。主管问了几个简单的问题，二级主管则问得比较深，HRG则偏向于考察性格和职业规划等偏软的部分。因为在学校里开组会讲报告也讲得比较多，这种答辩对我来说并不难。答辩完第二天主管开会时就告诉我应该没什么问题，过了一个月左右差不多就收到了录用意向书，转正成功。正式offer来得稍晚，大概和腾讯差不多都是在10月底左右收到的。创业公司：在秋招快结束的时候，邮箱里意外的收到了Pony.AI和Covariant.AI两家创业公司的面试邀请邮件，都是看到我Github上面两百多个star的Dota2 AI项目过来的。不过仔细看了一下这两个公司提供的都是社招岗，而且创业公司的发展前景不确定，对于应届生来说并不是一个很好的选择，于是就婉拒了他们。不过也可能就此错过了公司上市一夜暴富的机会哈哈哈哈。最后两家公司开的薪资其实都差不多，还是选了自己比较熟悉的阿里云。这个部门的同事和领导都算不错，而且因为这个部门主要是对内业务，工作压力相对来说是比较轻的，希望以后不要享受太多福报了哈哈哈。总的来说，这次秋招的结果还是可以的，虽然说没能成功进入外企躺平，但是也算找到了一个比较对口的工作。现在的打算就是先去那发展几年再看看吧，我实在是不擅长规划很远的事情。现代社会的信息流通速度实在是太快了，一份纽约时报所的信息量比17世纪一个普通英国人一辈子的信息量还要多。无穷无尽的信息导致我们总是生活在与他人的比较之中，不仅我们的眼光不断变高，而且对自己的认知也发生了偏差。我的父母其实都很尊重和支持我的选择，他们并不会给我什么压力，但是我有时还是会在不经意间通过各种渠道看到特别优秀的人，这带来的同辈压力其实还是存在的。不过在经历这次秋招找到工作之后，我再也不用担心这种压力了，因为自己确实已经在就业市场中证明了自己。以至于在学校里一段时间内遇到什么不开心的事情，我都会想着反正自己有好工作啦，其他事情运气差一点也没关系，总不可能什么好处都被我自己占了哈哈哈哈。在秋招的过程中，其实要谢谢帮我内推的各位大佬以及互联网上无私分享面试经验的博主，没有他们的帮助我可能很难找到这么好的工作，如果大家有什么想问的话也欢迎提问哈哈哈。最后很感谢朋友们能耐心地看到这里，就祝大家未来的发展能够顺风顺水蒸蒸日上吧！
我们学院每年都要办一个学术年会，要求我们在读期间必须投稿一次。前两年我都没来得及投，想着以后肯定有机会好好准备，结果今年实习刚回来也是没什么时间准备，最后只好把本科写的一篇论文交上去凑数。因为这个会议会选出一些不错的论文上去作报告，然后被评为优秀论文的可以直接满足毕业条件。我们老师估计就是看到这个，所以把我们没发论文的都推荐过去作报告了。其实我本科做的那玩意不怎么样，但是为了今天能混过去，昨天还是花了一点时间认真做了ppt，此外还顺便恶补了一下该领域的最新进展，方便到时候能讲得好一点。今天下午到了会场之后，一般作报告都不会紧张的我还是有点慌，毕竟好久没做这方面的工作了。好在前面几个同学的报告水平也就一般般，我又逐渐恢复了信心。上去讲的时候我就发挥了这么多年来积累的演讲能力，把一堆新技术包装在了我过去普通的工作上，没想到还把下面的同学讲愣了。最后老师提问题的时候，我也是见招拆招，把他们忽悠过去了。而且最搞笑的是，下面有个老师和我本科时的毕设老师长得很像，特别是他那稀疏的头发哈哈哈。下来之后我仔细思考了一下，其实不是我做得怎么样，而是他们的技术实在是太落后了，讲的好多东西都是本科做毕设的时候就知道了的。但是当时觉得人工智能方向还是竞争太激烈了，后来读研的时候就没有继续做，而是进到了一个更大的坑里面。不知道如果接着做那个方向会怎样，可能也会有一些不一样的坑没被我发现吧。难得的是下午这个活动结束的比较早，而且正好现场也有几个同学在那听，于是活动弄完之后就和隔壁课题组的同学去吃饭了。虽然说很多新同学都不认识，但是感觉吃饭的时候还是有很多聊的，他们也愿意听我们讲一些实验室的八卦。自己课题组的同学天天坐一起，能聊的东西其实不多，而且距离太近总是会有一些矛盾，很难往好朋友的方向发展。隔壁课题组有个老哥最开始其实和我是坐在一起的，后来即使他搬到另一个屋子去了，我们的关系也还是很好。他最开始说大家其实都是同事关系，我最开始还不理解，但是到现在我才终于弄明白了。读研时的同学和本科时确实不一样，平时一起在实验室工作，距离虽然更近，会涉及到一些合作，但是更多的是会有利益冲突。如果最开始就想着能当朋友，其实会有些失落。如果都当做同事来相处的话，那么很多事情就都无所谓了。这点在我实习完之后其实体会得更加深厚了。总之我想说的就是，现在这个阶段确实不容易结交新朋友，老朋友们又很少相聚，没有朋友一起玩的时候还是要发展一点爱好啊哈哈。
(ATC'19) Unification of Temporary Storage in the NodeKernel Architecture
在任务之间有效地交换临时数据对于许多数据处理框架和应用程序的端到端性能至关重要。不幸的是，临时数据的多样性造成的存储需求往往落在传统存储平台(如文件系统或键值存储)之间。本文提出了一种新的分布式存储架构NodeKernel，它通过将文件系统和键值语义融合到通用存储内核中，同时利用现代网络和存储硬件来实现高性能和低成本，在存储系统设计空间中提供了一个新的落脚点。NodeKernel提供了分层命名，高可伸缩性和接近裸机的性能，可提供临时数据特有的各种数据大小和访问模式。Crail是我们的NodeKernel架构的具体实现，该架构使用RDMA网络和分层DRAM / NVMe-Flash存储。我们证明了将临时数据存储在Crail中可将NoSQL工作负载性能提高多达4.8倍，将Spark应用程序性能提高了3.4倍。此外，通过在NVMe Flash和DRAM存储层之间存储数据，与仅DRAM的存储系统相比，Crail可以将存储成本降低多达8倍。1.简介有效地管理临时数据是提高集群计算工作负载性能的关键。例如，应用程序框架经常缓存输入数据或共享中间数据，既在作业内（例如，在map-reduce作业中shuffle数据），又在作业之间（例如，机器学习训练工作流中的预处理图像）。在无服务器计算中，为了在任务的不同阶段之间交换数据，临时数据存储也越来越重要[17]。有效存储临时数据具有挑战性，因为其特征通常位于现有存储平台（例如分布式文件系统和键值存储）的设计点之间。例如，map-reduce作业中的随机数据可能由大量文件组成，这些文件按层次结构组织，大小变化很大，被随机写入，然后顺序读取。文件系统（例如HDFS）提供了方便的分层命名空间并有效地存储了大数据集以进行顺序访问，而分布式键值存储则针对可伸缩访问大量小对象进行了优化。类似地，基于DRAM的键值存储（例如Redis）提供了所需的低延迟，但是持久性存储平台（例如S3）更适合以低成本实现高容量。总体而言，我们发现现有存储平台无法满足分布式数据处理工作负载中临时数据存储和共享的所有不同要求。在本文中，我们介绍NodeKernel，这是一种从头开始设计的新分布式存储体系结构，可支持快速有效地存储临时数据。作为其最显着的特性，NodeKernel体系结构融合了文件系统和键值语义，同时利用现代的网络和存储硬件来实现高性能。NodeKernel基于两个关键的观察。首先，长期存储平台提供的许多功能（例如耐用性和容错性）在存储临时数据时并不重要。我们观察到在这种情况下，文件系统和键值存储的软件体系结构开始看起来惊人地相似。基本区别在于，文件系统需要额外的间接级别才能将文件流中的偏移量映射到分布式存储资源，而键值存储将整个键值对映射到存储资源。第二个观察结果是，低延迟网络硬件和多CPU多核服务器通过在几ms的延迟中启用可扩展的RPC通信，从而在分布式环境中显着降低了这种间接成本。基于这些见解，我们通过将文件系统和键值语义实现为通用存储内核之上的薄层，来开发NodeKernel架构。存储内核对统一名称空间中称为“Nodes”的不透明数据对象进行操作。应用程序可以在节点中存储任意大小的数据，在分层名称空间中排列节点，并在需要时通过专用节点类型获得文件系统或键值语义。例如，键值节点和表节点允许并发创建具有相同名称的节点，并提供最后提交为准的语义。另一方面，文件和目录节点允许在存储层次结构中的给定级别上高效枚举数据集。通过将通用存储内核和自定义节点类型中的功能分开，NodeKernel使应用程序可以使用单个平台来存储可能需要不同语义的数据，同时通常为各种数据大小和访问模式提供良好的性能。NodeKernel在设计时明确考虑了现代硬件。经过严格考虑后的分离，存储内核由为低延迟网络硬件量身定制的轻量级和可伸缩的元数据平面以及可访问多层网络连接的存储资源的高效数据平面组成。精简的元数据平面能够在低开销下提供最关键的功能。数据平面运行一个轻量级的软件堆栈，并利用现代的网络和存储硬件来实现对任意大小数据集的快速访问，同时还提高了成本效率。例如，取决于性能和成本要求，附加到“Nodes”的数据可以固定到特定的存储技术层，也可以从一个存储层溢出到另一个存储层。Crail是我们对NodeKernel体系结构的具体实现，它使用RDMA网络和分别基于DRAM和NVMe SSD的两个存储层。我们使用原始存储微基准以及NoSQL YCSB基准测试和不同的Spark工作负载，在配备Intel Optane NVMe SSD的100Gb/s RoCE集群上评估了Crail。我们的结果表明，Crail在最佳位置运行时，可以与当前最新文件系统和键值存储的性能相匹配，并且在文件系统和最佳位置之外进行数据访问时，其性能要比现有系统高出3.4倍。键值存储。此外，Crail通过在DRAM之外使用NVMe Flash，创造了降低成本和获得灵活性的新机会，并且几乎没有性能损失。例如，我们在Spark中使用Crail存储shuffle数据时调整DRAM和Flash之间的比率，而作业的运行时间增加很小。总而言之，本文做出了以下贡献：•我们提出了NodeKernel，这是一种融合了文件系统和键值语义的新存储架构，可以最好地满足数据处理工作负载中临时数据存储的需求。 •我们介绍了Crail，它是使用RDMA，DRAM和NVMe Flash的NodeKernel架构的具体实现。•我们证明了将临时数据存储在Crail中可以减少运行时间并减少数据处理工作负载的成本。例如，Crail将NoSQL工作负载的性能提高了4.8倍。集成到Spark的shuffle和broadcast服务中后，Crail可以将应用程序性能提高3.4倍，并将成本降低8倍。Crail是一个开源Apache项目[2，3]，其代码可从项目网站下载，也可直接从GitHub的https://github.com/apache/incubator-crail下载。此外，本论文中使用的所有基准都是开源的。2.背景和动机临时数据代表了分析框架中一大类重要的处理中数据。例如，Zhang等人的报告指出，在Facebook执行的超过50％的Spark作业包含至少一项shuffle操作，从而产生了大量的临时数据[37]。我们将临时数据定义为在处理过程中创建，处理或使用的所有所有应用程序数据，不包括原始输入和最终输出数据。具体来说，我们确定了三种不同的临时数据类别：作业内，作业间和缓存的输入/输出数据集。当执行诸如页面排名或SQL查询之类的单个作业时，将在框架内生成作业内临时数据。常见的示例是在Spark，Hadoop或Flink等框架中的shuffle或broadcast操作期间生成的数据集。通常，此类数据是由同一作业生成和使用的，这限制了数据的生存期。作业间临时数据是多作业流水线中的中间结果。例如，在典型的机器学习流水线中[35]有许多预处理和后处理作业，其中一个作业的输出成为另一作业的输入。最后，缓存的输入/输出数据的示例大部分是只读数据集，这些数据集被拉到缓存中以进行快速重复处理。例如，用户可能会在短时间内在同一个表（或视图）上运行许多SQL查询。在这种情况下，输入表的副本可能会缓存在快速存储介质上。为不同类型的临时数据构建有效的存储平台需要仔细考虑应用程序需求，数据特征和硬件机会。在以下部分中，我们将讨论对临时存储平台的若干要求，并提供当前最新解决方案的概述。2.1需求与挑战大小，API和抽象多样性：数据处理工作负载中的临时数据可能会因数据大小而有很大差异。在图1中，我们显示了在执行以下任务过程中生成的临时数据大小分布（CDF）：（a）在Twitter graph上执行PageRank；（b）在TPC-DS数据集上进行SQL查询；（c）在稀疏矩阵数据集上执行Cocoa机器学习[24]。如图所示，每个任务的数据大小范围从几个字节（用于机器学习）到GB（用于TPC-DS）。由于历史原因，通常使用不同的存储系统来处理此频谱的两端。分布式键值存储区（例如，RAMCloud，memcached等）具有对象API，并进行了优化，可有效存储较小的值以进行快速随机查找[20，33]。相反，诸如HDFS或Ceph之类的文件系统可以通过对数据集进行分区并维护查找索引来有效地存储大型数据集（GB）。此外文件系统的抽象，可附加文件，可枚举的层次名称空间以及用于I/O的流字节接口为将临时数据集（如all-to-all-shuffle）轻松映射到底层存储提供了额外的支持。临时存储平台应该能够有效地存储较小和较大的值，并在单个系统中具有文件和键值抽象的统一优势。性能：临时数据通常位于数据处理的关键路径中，因此必须快速访问临时数据。与大小一样，访问模式也相差很大。例如，由于没有全局顺序，所以shuffle数据经常是随机写入的[11]，而SQL表则是在大型顺序扫描中读取的[32]。因此，用于临时数据的存储平台必须满足的一个要求是，对于任何访问模式，它都应该能够在整个数据大小范围内表现良好。幸运的是，在过去的十年中，I/O设备发展迅速，可支持具有高带宽（100 Gbps），超低延迟（小于10 usc）和数百万IOPS。为了满足数据处理需求，这些设备现在部署在云（AWS，Azure）中，并在数据处理框架内部使用。因此，理想的临时存储系统应该能够在现代网络和存储硬件上高效运行，同时提供接近裸机的性能。超越内存存储：数据处理工作负载所生成或消耗的临时数据总量可能很大。例如，在图1中显示了临时数据对象大小CDF的工作负载之间，数据的总容量相差100 GB（输入数据集大小的10-100％，未在图中显示）。在提供良好的数据访问性能的同时，很难有效地存储大量数据。例如，从性能的角度来看，最好将所有数据存储在DRAM中，但是这样做通常会太昂贵。值得庆幸的是，在过去的几年中，出现了不同的媒体类型，例如NAND闪存和PCM存储，以不同的成本，性能和能耗价格来存储数据。因此，一个有效的临时数据存储平台应集成多种存储技术，这些技术可提供不同的性能成本权衡，并允许应用程序在权衡空间的不同点之间进行选择。表1给出了不同存储技术在价格和性能方面的比较。没有必要的需求：我们观察到在临时数据存储的特定情况下，许多传统存储功能（例如持久性和容错性）不是优先选项。例如，由于临时数据的生存期短，因此持久的重要性较低。虽然容错通常对于短期数据很有用，但是对于临时数据存储，容错仍然不是一个高优先级。如今，容错通常是在计算框架级别以粗粒度方式实现的。例如，在重新启动任务导致数据丢失的情况下，Spark [36]和Ray [25]使用沿袭跟踪来重新计算数据。2.2现有方法的局限性关于上一节中列出的设计目标，我们将回顾当前的最新存储系统。我们将讨论的系统分为以下三类。键值存储：Memcached [4]和Redis [5]是设计用于将数据存储在DRAM中的两种最流行的键值存储。经过网络优化的KV，例如MICA [21]，Hedd [14]，FaRM [12]，KVDirect [19]和RAMCloud [26]使用RDMA操作来提供高性能的数据访问，但是无法轻松地将数据存储集成到除DRAM之外的其他层上 。Redis进行了扩展以将数据溢出到Flash，但是键索引仍存储在DRAM中，因此受到DRAM容量的限制。针对存储优化的KV（例如Aerospike [30]或BlueCache [34]）使用NAND闪存进行存储。因此，它们的性能受限于Flash的性能，并且它们并未针对下一代NVM存储设备（如Optane）进行优化（请参阅第5.1节）。其他系统，例如HiKV [33]，具有混合DRAM-Flash索引，但仅针对单个节点部署，因此将它们的应用程序限制在更广泛的操作范围内，例如改组。此外，这些KV存储的设计是针对数百字节至最大MB的非常小的数据集量身定制的，因此将它们的操作窗口限制为这些对象大小。分布式数据存储：Ceph-over-Accelio [8]和Octopus [23]等存储系统是用于快速网络和NVM设备的高性能分布式文件系统。但是，由于专注于提供容错的，持久的存储，它们在小对象上的性能很差（请参见第5节）。最近提出的Regions系统提供了对远程内存的文件抽象[6]。但是，与其他内存存储系统一样，Regions不是存储大型数据集的经济有效的解决方案。诸如Alluxio [1]和Pocket [17]之类的系统提供对多种存储技术类型的支持。但是，Alluxio无法提供高端硬件的性能，而是旨在构建本地缓存。Pocket拥有我们的目标，即专用于存储临时数据的存储系统，其设计与NodeKernel架构相似。但是，Pocket的重点是在云中的商品硬件上提供高效且灵活的临时存储，而NodeKernel是为低延迟高带宽网络和存储硬件而设计的。此外，Pocket仅具有基于对象的I/O接口，非常适合在无服务器工作负载中进行数据共享。相比之下，NodeKernel的统一API提供了诸如“append”和“bag”之类的语义，以支持在不同的工作负载中存储各种临时数据。临时数据特定操作：许多工作可加速数据处理工作负载中的特定存储操作。例如，Riffle [37]是一种优化的shuffle服务器，旨在减少与大型扇出相关的开销。Sailfish [27]是一个引入I-files的框架，这些I-files是经过随机优化的数据容器。ThemisMR [28]还旨在优化shuffle并针对小型机架规模的部署。通常，这些系统的目标是针对map-reduce型工作负载优化基于磁盘的，面向文件的shuffle数据管理。尚不清楚它们的设计如何支持其他通信模式，例如broadcast和multicast，或如何集成不同的存储类型以针对不同的访问模式进行优化。并行数据库[7,22]对数据库操作符使用RDMA优化的Shuffle操作。但是，这些工作是高度特定于数据库，不能自然地扩展到其他数据处理工作负载或其他形式的临时数据。3.NodeKernel体系结构我们介绍了NodeKernel，这是一种新的存储体系结构，旨在满足数据处理工作负载中临时数据存储的各种复杂需求。NodeKernel通过融合存储语义来解决这一挑战，这些语义在文件系统和键值存储中是可以单独使用的，例如分层命名，可伸缩性，多个存储层，数据集的快速枚举以及对小数据和大数据大小的支持（图2）。NodeKernel体系结构遵循以下三个设计原则：1.将高级存储语义提取到通用存储内核之上的薄层中。2.将数据管理问题分为轻量级的元数据平面和为现代网络和存储硬件优化的“哑”数据平面。3.利用多种存储技术来高效存储大型数据集。在第4节中描述Crail（NodeKernel架构的具体实现）之前，我们将在下面更详细地讨论每种设计原则。3.1 存储内核和节点类型在NodeKernel体系结构中，更高级别的存储语义在共享存储内核之上实现为薄层（或更确切地说，作为专用数据类型），并导出不透明数据“节点”的分层命名空间。节点是抽象类型Node的对象，如以下代码片段所示。内核负责代表节点分配存储资源，操纵分层名称空间以及实现诸如read，append和update之类的基本数据访问操作。应用程序与存储内核交互，以在层次结构中的给定位置创建数据节点，将任意大小的数据附加到节点，查找节点，并从节点获取关联的数据集。节点使用编码存储层次结构中位置的路径名来标识，类似于文件系统中的文件和目录。应用程序不会直接创建原始Node对象，而是会创建提供特殊功能的派生类型的对象。这些所谓的自定义类型通过两种方式扩展Node来实现更高级别的存储语义。首先，自定义类型为抽象操作addChild和removeChild提供实现。每当在存储层次结构中插入新节点或从中删除新节点时，内核就会调用这些操作。其次，自定义数据类型提供了特殊的数据访问操作，这些操作使用Node中可用的read和append实现。NodeKernel定义了五种自定义节点类型，每种类型都提供了稍微不同的语义和操作：（1）File和KeyValue：这两种节点类型都通过在Node中公开相应的操作来提供read和append接口。但是，这两种类型在创建和插入新节点的过程中提供了不同的语义，这些语义是通过addChild和removeChild的实现来控制的。对于文件节点，针对给定路径名的第一个创建操作成功，而对相同路径名的后续创建操作失败。对于KeyValue节点，将在表示现有节点的路径名上进行后续的创建操作，从而替换现有节点。如第5节所述，KeyValue节点对于在NoSQL工作负载中缓存输入数据集非常有用，允许并发更新数据，而File节点更适合在Spark工作负载中缓存只读输入数据。（2）Directory和Table：这些节点类型分别是File和KeyValue的容器。Directory和Table节点将其所有子项的名称成分存储为数据的一部分（使用Node中可用的append和update操作来实现）。例如，路径名称为“/a/b”的目录的数据段存储了两个路径名称为“/a/b/c”和“/a/b/c2”的文件，其中包括两个名称成分“c1” 和“c2”。 Directory和Table节点均提供操作以枚举其所有子节点的名称（使用Node中可用的read来实现）。可以选择将表配置为“不可枚举”，在这种情况下，不存储名称组件，枚举返回空集。在不可枚举的表中创建KeyValue节点通常更快，因为它省去了更新名称组件的步骤（如第4.1节所述）。 （3）Bag：Bag节点类型旨在支持有效地顺序读取分布在许多数据节点上的数据。 Bag的行为类似于目录，因此它充当File节点的容器。应用程序在Bag中创建和写入文件的方式与在Directory中创建和写入文件的方式相同。但是，在读取Bag时，Bag像单个文件一样出现在应用程序中。使用Bag的读取操作，应用程序可以依次读取Bag中的全部文件。通常，由于在文件边界处更有效的元数据访问，因此Bag的读取操作比单独读取每个File节点提供更好的性能。正如我们将在第5节中看到的那样，Spark应用程序可以使用Bags存储转换数据，从而允许简化任务有效地获取由map任务编写的数据。 NodeKernel中的Bag类型在精神上与Hurricane中的bag类似，这是最近在抑制数据处理工作负载中的偏斜上的一项工作[9]。NodeKernel限制了可以堆叠节点类型的方式。例如，KeyValue节点只能附加到Table节点，而File节点只能附加到Bag和Directory节点。此外，Directory可以任意嵌套，而bag和table实现平面名称空间。允许的节点类型组合与临时数据存储的特定用例匹配。同时，防止节点类型的任意组合可确保体系结构不会过度设计且易于实现。例如，Bag要用于存储按单位存储桶组织的随机数据，因此，启用任意嵌套的Bag似乎是不必要的。当时，对扁平Bag的读取操作比对任意嵌套的Bag的读取操作更容易实现。为什么要提供一个统一的存储名称空间？ 通过在通用存储内核和一组自定义数据类型中拆分功能，NodeKernel实现了两件事。首先，它允许由单个存储平台管理需要不同语义的不同类型的临时数据。例如，稍后我们将看到，Spark应用程序可以使用Crail来存储broadcast和shuffle数据，以及缓存RDD。其次，将核心数据访问与存储语义脱钩，使应用程序可以根据所需的语义（键/值与文件）而不是根据数据的大小或访问模式来选择特定的节点类型。如第2.1节所述，即使在单个工作负载内，临时数据通常也会在数据大小和访问模式方面有所不同，从而难以将数据有效地存储在文件系统或键值存储之类的存储平台中。相比之下，NodeKernel中的节点类型没有大小限制，并且可以为不同的访问模式提供有效的数据访问，我们将在后面的第5节中看到。3.2 系统架构图2说明了NodeKernel系统架构。在数据管理级别，NodeKernel的体系结构类似于HDFS或GFS之类的分布式文件系统的体系结构，该体系结构由跨集群部署的一组元数据和存储服务器组成。附加到存储层次结构中“Node”的数据在客户端以流的形式出现，但内部数据是由一系列块组成的。块是指存储在其中一台存储服务器中的固定字节序列。元数据服务器维护分层存储名称空间以及块元数据，即存储块和存储服务器之间的映射。存储服务器在启动时会分配大量存储块，并向其中一个元数据服务器注册。元数据服务器维护一个空闲列表，其中包含未分配给特定节点的块，并在数据写入和追加期间将块从空闲列表移至每个节点列表。在访问数据时，客户端首先联系元数据服务器之一，并请求相应块的元数据。然后，基于此信息，客户端将与给定的存储服务器联系以读取或写入数据。Node抽象数据类型导出两个抽象操作，addChild和removeChild，将由第3.1节中描述的派生类型实现。每当创建或删除新节点时，这些操作就会在元数据服务器上执行。节点进一步导出功能来操纵数据，例如read，append和update。这些功能被实现为客户端库的一部分，并且需要与元数据和存储服务器进行交互。最后，基本的元数据操作（如getPath或size）也由客户端库（如果可能）返回缓存的值来实现。部署目标：NodeKernel以临时数据为目标，这些数据是短期的并且易于重新生成。此外，NodeKernel的目标是由少量计算机架组成的中小型部署。考虑到目标部署和要存储的数据的性质，NodeKernel优先考虑性能而不是容错能力。但是，如果认为有必要，可以添加其他的容错机制，例如复制，擦除编码等。缺少这些功能不会在根本上影响设计。性能挑战：NodeKernel的主要挑战是提出一种能够满足第2.1节中讨论的全部需求的系统体系结构。特别是，尽管提供了方便的分层名称空间，该体系结构仍应该像键值存储一样可伸缩。此外，该体系结构应支持低延迟键值样式访问以及高带宽文件系统（如数据访问）。接下来，我们讨论如何在NodeKernel体系结构中满足这些要求。3.2.1低延迟元数据操作将文件系统和键值语义融合到单个存储内核中，首先需要快速的元数据访问。在第2.1节中，我们观察到分布式文件系统的软件体系结构与键值存储的根本区别仅在于将文件流中的偏移量映射到存储资源（例如存储服务器上的块）所需的额外元数据操作。因此，将元数据操作的开销保持在较低水平将使NodeKernel的体系结构适合小型数据集上的键值形式的操作，同时还可以提高大型数据访问的效率。如今，现代的低延迟网络硬件能够以几ms的延迟实现RPC通信[13，16]。NodeKernel具有轻量级的元数据平面，可与低延迟网络硬件很好地匹配。首先，将元数据平面缩减为仅提供由六个关键RPC操作组成的最关键功能：create以创建新节点，lookup以检索节点的元数据，remove以删除现有节点，move将节点移动到存储层次结构中的其他位置，map以将节点数据流中的逻辑偏移量映射到存储资源，register以供存储服务器在元数据服务器中注册存储资源。所有这些操作都具有较低的计算和I/O强度，并且可以在高性能网络结构上以几ms的延迟来实现。我们特意将诸如enumeration之类的数据密集型元数据操作移至数据平面，以避免产生干扰（请参阅第3.1和4节）。3.2.2元数据分区NodeKernel的可伸缩性在很大程度上取决于其元数据RPC子系统的吞吐量和可伸缩性。最近的工作表明，一方面可以使用高性能网络硬件将RPC系统的规模扩展至每秒上百万次操作[15]；另一方面，还可以在商品硬件上使用高效的软件堆栈[13]。NodeKernel中的轻量级RPC接口旨在实现高吞吐量，并且使用单个元数据服务器每秒可驱动多达1000万个元数据操作，如第5节中稍后所述。到目前为止，在我们所有的部署中，我们从未有过单个元数据服务器将达到其极限的情况。实际上，在我们最大的128个节点部署中，元数据每秒可达到450万次操作，仅大约达到单个元数据服务器可支持的一半。但是，对于仅一个元数据服务器不足的情况，NodeKernel允许在多个服务器上划分元数据空间。从而，顶级根名称空间在元数据服务器的有序列表中进行了哈希分区。使用元数据分区，可以假设足够大的顶级扇出水平地水平缩放元数据平面。基于顶级名称空间进行静态分区的一个缺点是，取决于子树的大小和子树内的活动，负载可能会在元数据服务器之间不均匀地分布。一种替代方法是在更细粒度的级别进行动态分区。例如，过去的工作提出了一种针对文件系统的分区方案，其中在目录级别实施元数据分区，并且可以选择在大型目录过大时按需拆分大型目录，并将拆分内容分布在多个元数据服务器上[29]。尽管这种方法创建了更均匀的负载分配，但由于在路径查找和遍历期间需要多次RPC调用，因此付出了可观的性能代价。给定NodeKernel中5-10 µs的性能目标，我们最终决定采用更简单的分区方案，其中节点路径始终位于元数据服务器本地。3.2.3硬件加速的存储NodeKernel的数据平面旨在与现代网络和存储硬件完美配合。目的是使存储接口保持简单，以避免过多的软件开销，并允许在硬件中实现尽可能多的数据访问功能。NodeKernel中的客户端通过两个接口与本地和远程存储服务器进行交互：read(blockid, offset, length, buffer)以从块中获取一定数量的字节，以及write(blockid, offset, buffer, length)以进行写入存储在块中的缓冲区中的数据。在第4节的后面，我们将展示Crail中的read和write操作几乎可以完全转移到DRAM和Flash的网络和存储硬件上。还要注意，存储接口通过在字节级别（而不是块级别）定义访问粒度，显式支持字节可调整的存储硬件。3.2.4分层存储NodeKernel采用简单的分层存储设计来容纳无法以经济高效的方式存储在DRAM中的大型数据集。存储服务器分为不同的类别（请参见图2），通常是每种存储技术（DRAM，NVMe SSD，HDD等）的类别。存储服务器是一个逻辑实体，即一台物理或虚拟机可以托管多个不同类型的存储服务器。例如，一种常见的部署是每个主机运行两台存储服务器，一台导出一些本地DRAM，一台导出本地NVMe SSD上的存储空间。原则上，存储类是用户定义的存储服务器集。一台存储服务器始终完全属于一个存储类。在我们的评估中，我们配置了两种存储类别，一种用于DRAM服务器，另一种用于NVMe SSD服务器。传统的存储分层方法是随着更快的存储类别填满，将数据迁移到更具成本效益的存储类别。由于数据的生命周期短，我们发现该策略对临时数据无效。相反，我们选择了一种更简单的方法，即根据用户定义的顺序填充存储类（通常先是DRAM，然后是闪存和硬盘），而无需在层之间迁移数据。特别是在数据写入操作期间，元数据服务器首先尝试分配DRAM块，只有在整个集群中没有更高优先级的存储块可用时，才转向较低优先级的存储层。4. CrailCrail是NodeKernel体系结构的具体实现。我们用大约1万行Java和C ++代码实现了Crail。表2显示了Crail的应用程序界面。Crail中的顶级数据类型是CrailStore。应用程序使用CrailStore来create，lookup和delete数据节点，或将数据节点move到存储层次结构中的其他位置。使用类似于文件系统的路径名来标识节点。使用create创建新节点时，应用程序可以为要存储的数据选择首选的存储类（表2中的参数“ sc”/storage class）。我们也将存储类首选项称为存储亲和力，因为它允许用户为特定的一组数据指定对一组特定的存储服务器或存储介质的亲和力。Crail实现了第3.1节中讨论的完整节点类型集。请注意，Crail中的所有操作都是非阻塞且异步的（返回future对象）。Crail的异步API与用于现代网络和存储硬件的异步软件接口非常匹配。实际上，几乎所有Crail的高级操作都可以直接映射到一组非阻塞的异步网络和存储操作。通过无效的futures或异常（表2中未显示）来传达Crail API调用的失败。例如，尝试lookup不存在的节点将导致无效的future (nullpointer)，而尝试从超出节点容量的节点read数据将导致异常。Crail既可以作为共享存储服务使用，也可以按用户或按应用程序部署的形式进行操作。但是，Crail的当前实现没有提供任何工具来虚拟化，保护和隔离多个租户。4.1 元数据平面Crail元数据服务器在内存中表示以下内容：（a）存储层次结构，（b）空闲块集，以及（c）块对“节点”的分配。具体而言，每个元数据服务器维护每个存储类的可用块列表。存储类根据用户定义的优先级进行排序。如第3.2节所述，如果在写操作期间当前写位置尚未指向已分配的块，则客户端将通过调用元数据map RPC操作来请求一个新的新块。元数据服务器根据所选的存储关联性（表2中的“ sc”）选择一个空闲块。如果所选存储类别中没有空闲块，则元数据服务器将尝试从优先级列表中的下一个存储类别分配一个块。在存储类中选择块时，元数据服务器在给定存储类中的所有存储服务器上使用循环，以确保在集群中均匀地分发数据。如果任何存储类中没有可用块，则客户端的写操作将失败。在3.2.1节中讨论的Crail跨元数据服务器阵列对元数据进行分区，这意味着，每个元数据服务器负责存储层次结构的分区。每个服务器都使用DaRPC [31]实现为轻量级RPC服务，该服务是基于RDMA发送/接收的异步低延迟RPC库。为了实现高吞吐量，客户端连接在不同的CPU内核之间进行了分区。每个内核在单个流程上下文中就地管理客户端连接的子集，以避免上下文切换开销。RPC缓冲区的所有内存均分配给与负责特定连接的给定CPU内核关联的NUMA节点本地。图3说明了Crail中元数据处理的不同方面。Enumeration：在第3.1节中，我们讨论了容器节点(Table, Directory, Bag）如何维护所有子节点的名称列表作为其数据的一部分。该设计背后的理由是，它允许我们在数据平面中有效地实现容器枚举。我们已经看到一些用例，其中在Spark中存储Spark shuffle数据会在Bag中生成近十万个File节点。在元数据服务器上实现枚举将导致客户端和元数据服务器之间的大量数据传输，并且在许多情况下将需要进行多轮RPC枚举所有节点。Crail用于容器节点的文件格式由固定大小的记录组成，这些记录由子项的名称组成部分和有效标志组成。创建新节点后，元数据服务器会在数组内分配一个唯一的偏移量，客户端将根据该偏移量写入相应的记录。在删除操作期间，在元数据服务器删除了节点条目之后，客户端将清除相应记录的有效标志（通过将记录中的有效位清零）。请注意，容器数据中的节点记录仅被视为补充信息。元数据服务器始终充当验证节点是否存在的权限。因此，与删除操作同时运行的枚举操作可能会导致以下情况：目录文件中的节点记录仍然有效，但是该节点在元数据服务器上的元数据状态已被删除。在这种情况下，该节点被视为已删除，并且不允许进行任何读或写操作。4.2 数据平面Crail实现了两种存储类别，一种用于DRAM，另一种用于基于NVMe的SSD。存储类的实现包括一个服务器部分，该服务器部分导出一个存储资源（请参阅第3.2.3节），以及一个客户端部分，用于实现有效的数据访问。RDMA存储类：RDMA存储类中的存储服务器将RDMA注册内存的较大区域导出到元数据服务器。基于RDMA的存储块的元数据包含必要的RDMA凭证，例如地址，长度和标记，并允许客户端使用RDMA单边读/写直接读取或写入存储块。NVMe-over-Fabrics存储类：NVMe-over-Fabrics（NVMf）是NVMe标准的最新扩展，该标准允许通过支持RDMA的网络访问远程NVMe设备。它消除了沿I/O路径到远程设备的不必要的协议转换，从而将NVMe的多个成对队列设计直接暴露给客户端。与RDMA一样，队列对可以直接映射到应用程序上下文中，以避免内核开销。Crail NVMf存储服务器通过连接到控制器并将其凭据（如NVMe合格名称，大小等）报告到元数据服务器，充当NVMf控制器的控制平面。借助元数据服务器提供的凭据，客户端可以直接连接到NVMf控制器并执行块读取和写入操作。4.3故障语义和持久性Crail当前未实现容错机制（请参见第3.2节），因此无法防止机器或硬件故障。在存储服务器崩溃时，相应的数据块会丢失。在元数据服务器崩溃时，相应的元数据分区会丢失。元数据服务器根据保持活动消息从活动服务器列表中删除不可访问的存储服务器，并确保在块分配期间仅考虑活动服务器。Crail提供了可选的机制来持久存储DRAM中存储的数据，关闭Crail部署并从先前的持久状态开始Crail部署。持久性是通过元数据服务器上的操作日志记录以及在存储服务器上使用内存映射的持久性存储来实现的。4.4数据访问剖析图4说明了Crail客户端如何代表应用程序从File或KeyValue节点读取数据的情况下与存储服务器和元数据服务器进行交互。应用程序首先调用lookup来检索节点句柄，从而使Crail通过RPC从元数据服务器中获取必要的元数据。元数据包含有关节点的信息，例如数据大小和第一个块的位置。成功执行lookup调用后，应用程序发出read操作以从节点读取一定数量的字节。请求的字节数可能少于一个块。在那种情况下，单个RDMA或NVMf操作将足以完成请求。如果所请求的字节数产生了多个块（如本例所示），则Crail立即为第一个块发出数据传输，同时并行请求下一个块的元数据。在正常情况下-由于客户端和元数据服务器之间的基于RDMA的低延迟协议-元数据请求将在当前块传输之前完成，并确保继续进行数据传输，而客户端不必等待丢失的元数据信息。5. 评估在我们的评估中，我们评估Crail是否满足第2.1节中讨论的临时存储平台的要求。具体来说，我们回答以下问题：1.Crail及其额外的间接层的统一抽象对于高性能设备上的各种数据大小是否性能良好？ （第5.1节）2.将更高级别的工作负载（及其临时数据访问）映射到Crail有多简单？ （第5.2节）3.用于数据处理框架的混合介质存储系统的性能和成本收益有多大？ （第5.3节）集群配置：我们使用八个x64节点的集群，以及两个Intel®Xeon®CPU E5-2690 v1 @ 2.90GHz CPU，96GB DDR3 DRAM和100 Gbit/s Mellanox ConnectX-5 RoCE RDMA 网卡。对于客户端服务器微基准测试，服务器配置了4个Intel Optanane 900P SSD，但IOPS实验除外，在IOPS实验中，每个服务器仅使用2个Optane驱动器。对于较大的集群实验，所有8个节点均配备4个Samsung 960 Pro SSD。这些节点运行Linux内核版本为4.10.0-33-generic的Ubuntu 16.04.3 LTS（Xenial Xerus）和Java 8。5.1 微基准测试小型和中型数据：我们首先要评估Crail存储小型和中型值的数据，这种情况通常是键值存储很好地满足的用例。因此，我们将Crail的性能（延迟和IOPS）与两个最先进的开源键值存储（RAMCloud（用于DRAM存储）和Aerospike（用于NVM Optane））进行了比较。图5显示了不同数据大小的get和put操作的性能。在Crail中，通过使用create API调用（请参见表2）创建KeyValue节点，然后执行append操作来实现put操作。get操作是使用lookup调用实现的，然后在KeyValue节点上执行read，与图4中所示的情况类似。对于小型数据（4字节），Crail在DRAM存储方面的性能比RAMCloud稍差（12 µs vs 6 µs），但在Optane NVM上的表现优于Aerospike，幅度为2-4x（Crail为23-40 µs，Aerospike为100 µs）。Aerospike和Crail之间的差异来自其I/O执行方面的差异。Aerospike使用同步I/O和多个I/O线程，这会导致占用时间并在同步功能中花费大量执行时间[18]。Crail使用异步I/O并在一个上下文中执行I/O请求，从而完全避免了上下文切换和同步。考虑到RAMCloud是为小型数据而优化的系统，Crail和RAMCloud之间的延迟差异是可以接受的。对于中等大小的数据（64KB-1MB），Crail优于RAM-Cloud和Aerospike，幅度为2-6.8倍。例如，一个1MB的put耗时约为590 µs，而Aerospike则为4 ms。这些性能提升来自有效使用RDMA单边操作（用于DRAM和NVM），它消除了客户端和服务器端的数据副本，并通常减少了在put/get操作期间执行的代码路径。尽管Crail本机支持任意大小的数据集（通过将块分布在多个存储服务器上），但是在RAMCloud或Aerospike这样的系统中存储如此大的值是困难的或被禁止的。例如，Aerospike将单个键/值对限制为1MB。RAMCloud没有严格的大小限制，但是无法存储大于4MB的值。为了完整起见，图5还显示了超大数据16MB和128MB的put/get延迟。如我们所见，在Crail的DRAM层中存储128MB值大约需要12ms，而在Crail的Optane层中存储相同的数据集大约需要20ms。在Crail中存储如此大的数据值完全取决于吞吐量。因此，远程DRAM延迟受100 Gb/s网络带宽的限制，而NVM延迟则由存储设备的带宽确定。4个Optane驱动器的总带宽约为10-12 GB/s。因此，存储在Crail的NVM层中的大型数据集的数据访问带宽为80-87 Gb/s。IOPS缩放：到目前为止，我们已经讨论了卸载延迟。图6显示了加载的Crail系统针对不同介质类型的256字节值的延迟性能测试结果。在此设置中，我们将客户端的数量从1个增加到64个。客户端在16台物理计算机上运行，并在紧密的循环中发出put/获取操作。在此设置中，我们仅使用一台存储服务器和一台元数据服务器，它们被配置为服务于DRAM，Optane NVM或闪存。图6的第一行显示了队列深度为1的情况，这意味着每个客户端在运行中始终只有一个操作。如图所示，Crail可以提供稳定的延迟，并达到合理的高吞吐量。对于DRAM，高达4M IOPS的获取等待时间（图6右上方）保持在12-15 µs，这时元数据服务器成为瓶颈。我们对多个元数据服务器进行了相同的实验，并验证了系统吞吐量是线性扩展的（如上图7所示）。对于Optane NVM配置，等待时间一直保持20 µs，直到接近1M IOPS，这非常接近设备限制。闪存延迟较高，但三星驱动器也具有较高的吞吐量限制。实际上，队列深度为1的64个客户端无法使Samsung设备饱和。为了产生更高的负载，我们测量了每个客户端始终进行四个操作的情况下的吞吐量和延迟（队列深度4，图6中的底行）。如图所示，队列深度4通常达到较高的吞吐量，直到达到硬件极限，设备队列过载（例如，对于NVM Optane）和等待时间长。例如，在延迟呈指数增长之前，Crail在4.2M IOPS（DRAM）上获得30.1 µs的延迟（图6右下），对于110万IOPS（Optane）而言为60.7 µs，对于640.3K IOPS（闪存）的延迟为99.86 µs。放置情况类似，尽管通常性能较低。元数据性能：在图7（顶部）中，我们对用于在Crail中检索节点元数据的简单查找元数据操作的性能进行了基准测试，并将其与在Octopus中类似的元数据操作getattr的性能进行了比较[23]（ 在DRAM中运行的经过RDMA优化的NVM文件系统）。这里有两个主要的观察。首先，对于单个名称节点，Crail的性能比Octopus高1.7-5.9倍。Crail中的单个namenode峰值约为9.3M lookup/s。其次，Crail可以非常有效地将单个名称节点的性能扩展到多个名称节点的设置。系统可以为2个和4个名称节点配置提供每秒高达16.7M和27.4M的查询速度。访问大型数据集：与在Octopus和Alluxio中进行文件读取操作相比，图7（底部）显示了在Crail中读取File节点的大型数据集时测得的带宽（y轴）。图7中的x轴表示客户端在读取操作期间正在使用的应用程序缓冲区的大小。Crail具有高效的数据和元数据平面，并且查找RPC和数据获取的重叠也很快达到了网络带宽限制，即使是相对较小的缓冲区大小（刚好超过1kB）。由于数据复制和网络堆栈实施效率低下，Alluxio的性能受到CPU的限制。Octopus的性能优于Alluxio，对于大型缓冲区（接近1MB），其线速度逐渐达到98 Gb/s。请注意，图7中的Crail峰值带宽（98 Gb/s）优于图5中的峰值带宽（87 Gb/s），因为对于KV实验，每个KeyValue节点都是打开，读取和关闭的，而对于文件 实验将这些访问权摊销。总结：在本节中，我们证明了Crail可以有效地存储大型数据值，同时提供比其他针对特定数据范围进行了优化的最新系统更可比或更高的性能。5.2 系统级基准测试5.2.1 NoSQL工作负载Yahoo! 云服务基准（YCSB）是一个开放标准，旨在比较NoSQL数据库的性能[10]。它带有五种强调不同属性的工作负载，例如 工作负载A的更新繁重，而工作负载C的读取繁重。我们选择工作负载B，将Crail与RAMCloud和Aerospike进行比较。工作负载B具有95％的读取和5％的更新操作，并且使用Zipfian分布选择记录。所有系统都在单个命名节点/数据节点配置中运行。本实验的目的是评估Crail的延迟配置文件，以了解超出上一节中介绍的微基准的实际工作负载。图8（顶部）显示了使用单个客户端默认设置10条字段（每条记录100字节，每KV对1K）的工作负载B的读取和更新延迟分布。图的左侧显示读取性能，右侧显示更新性能。如该默认设置所示，最大数量的读取操作观察到的延迟分别为14 µs（95％和99％的百分数分别为37µs和84 µs）和26 µs（95％和99％的百分数分别为47µs和81µs） 分别用于DRAM和Optane。Optane上的Crail的平均延迟为38 µs，因此仅比DRAM上的Crail慢15 µs。另一方面，带有Optane的Aerospike的平均延迟为108.7 µs，比Crail平均延迟（38.03 µs）差了2.84倍。将Crail的DRAM性能与RAMCloud进行比较，可以看出RAM-Cloud的速度比Crail快。但是，随着我们在图8（底部）中移到更大的10个字段（每个10KB，每个KV对100KB）的较大值，Crail分别比Aerospike和RAMCloud高出2.6-4.8倍。总结：我们使用YCSB基准进行的实验表明，（a）Crail可以成功地将原始DRAM / NVMe的性能优势转化为工作负载级别的收益，并且（b）Crail可以有效地处理小型和大型数据，而RAMCloud和Aerospike可以在特定的操作范围内发挥最佳性能 。5.2.2 Spark集成我们介绍了Spark（最流行的数据处理引擎之一）对Crail的评估。Spark通过一系列map-reduce的步骤来执行工作负载，同时在每个步骤中共享性能关键数据。Spark数据处理流水线中有多个点可以生成临时数据集。在本节中，我们将显示专门针对以下两个方面的性能指标：shuffle和broadcast。这两个子系统都可以轻松地实现为Spark的插件模块。broadcast：通过将broadcast数据存储为不可枚举的Table中的KeyValue节点，我们使用Crail实现了broadcast。broadcast写入器创建一个新的KeyValue节点，将broadcast数据附加到该节点，然后将节点“名称”传递给读取器。分布在Spark执行程序内部多台机器上的读取器对“名称”进行查找，并从Crail读取数据。图10a显示了结果。x轴显示在Spark作业中不同broadcast读取器观察到的延迟，而y轴显示读取器的百分比。垂直实线表示基线时延为12 µs，这在我们的微基准测试中得到了证明。如图所示，大多数Crail broadcast读取器观察到的延迟非常接近最小可能的延迟。少数读取器观察到低于12 µs的等待时间，因为其中一些读取器位于存储值的同一台物理机器上。对于这些节点，即使它们仍然使用本地网络接口读取数据，也没有发生实际的网络传输，因此，它们的读取性能不受网络的限制。总而言之，Crail broadcast性能比默认的Spark实施好1-2个数量级。shuffle：在Spark中，shuffle写入器在映射阶段会不断生成shuffle数据，因为它会处理输入数据集并将数据分类到不同的存储桶中，这些存储桶随后将由reducer读取。由于大型的扇入和扇出访问模式，我们使用Crail Bag节点实现了shuffle。每个reducer有一个Bag节点，每个shuffle写入器将数据追加到一组私有File节点的数组中，每个bag每个写入器一个File节点。映射阶段之后，每个读取使用Bag节点类型中可用的优化读取接口读取其关联的bag（请参阅第3.1节）。我们生成了大量数据（512 GB），并使用Spark源代码中提供的GroupBy基准触发了shuffle操作。图10b和10c显示了各种配置的性能（x轴上的运行时）和观察到的网络吞吐量（y轴）。值1、4或8表示分配给每个Spark执行器的内核数。对这两个图进行快速比较可以看出，Crail加速的Spark观察到更高的网络吞吐量（对于相应的内核数），因此，运行时间更好（1个内核5倍，4个内核2.5倍和8个内核2倍）。总结：在本节中，我们证明了Crail能够通过利用不同的节点类型（KeyValue和Bag）成功地针对较小的值（例如broadcast）和较大的值（例如shuffle）加速Spark中的临时数据访问。5.3 混合DRAM / NVM配置的效率在评估的最后一部分，我们将量化Crail的分层数据平面如何帮助实现性能和成本目标。我们考虑Terasort工作负载，它是Spark上最I/O密集型应用程序之一。我们分两个阶段将Terasort实现为外部范围-分区排序算法。第一阶段将传入的键值对（10个字节的键和90个字节的值）映射到外部存储桶中。然后，将这些存储桶按单独的reduce任务进行shuffle和分类。为了进行评估，我们使用了先前开发的加速shuffle和broadcast插件。在图9中，我们探索了使用NVM而不是DRAM在200 GB Spark排序工作负载中存储随机数据的性能/成本折衷。为此，我们为Crail配置了DRAM和Flash存储层不同的存储限制。x轴表示在DRAM与Flash中存储的总shuffle数据中的哪一部分。请注意，在本实验中，我们使用的是基于Samsung Flash的SSD，而不是Optane设备。10/90的配置意味着10％的数据保存在DRAM中，而90％的数据保存在闪存中。该图还显示了完全在DRAM（使用tmpfs作为存储后端）中完全在其默认shuffle引擎上运行的原始版本Spark（该图的第一条）的性能。这里有两个主要观察结果。首先，与原始版本Spark相比，将Crail用于shuffle后端已经将运行时间减少了3.4倍。这种性能提升可归因于Crail中高效使用高性能网络和存储硬件。例如，在reduce阶段，我们测得了70 Gb/s/台机器的所有网络吞吐量。其次，随着我们减少Crail中DRAM的比例，转而使用Flash，Spark会优雅地自动将随机数据泄漏到Flash层中。在所有随机数据都存储在Flash中的极端配置中，性能下降到46.49秒（提高了48％），而总存储成本则从1,000美元减少到126美元（存储200 GB数据）减少了8倍。根据表1中的数字）。数据从DRAM到闪存的逐渐溢出是透明发生的。即使在全闪存配置中，Crail集成的Spark Terasort的性能也只有完全内置DRAM的Vanilla Spark性能的一半。这些结果验证了我们在Crail中所做的设计选择，这些选择允许以交易性能换取存储成本。总结：在本节中，我们证明了在Spark中使用Crail（i）会由于其高效的I/O路径而带来更好的性能；（ii）由于采用了DRAM-NVMe混合架构，因此降低了存储成本，并提高了性能。6. 结论在数据处理工作负载中有效地存储和访问临时数据对于性能至关重要，但由于复杂的存储需求介于文件系统或键值存储之类的现有存储系统之间，因此仍然具有挑战性。我们介绍了NodeKernel，这是一种新颖的存储体系结构，它通过将分层命名与可伸缩性和出色的性能相结合，为各种临时数据所特有的各种数据大小和访问模式提供了新的存储设计空间。NodeKernel体系结构受现代网络和存储硬件机会的推动，这使我们能够减少开销，而这种开销过去使这种设计变得不切实际。我们表明，将临时数据存储在Crail中，这是我们利用RDMA网络和NVMe存储对NodeKernel架构进行的具体实现，可以将NoSQL工作负载提高4.8倍，将Spark应用程序性能提高3.4倍。与仅使用DRAM的存储系统相比，Crail使用NVMe Flash可以进一步将存储成本降低多达8倍。Reference：[1]Patrick Stuedi, Animesh Trivedi, Jonas Pfefferle, Ana Klimovic, Adrian Schuepbach, and Bernard Metzler. 2019. Unification of Temporary Storage in the NodeKernel Architecture. In Proceedings of the 2019 USENIX Annual Technical Conference, 767–781.
才摸到了一点科研的门路就要去找实习啦，突然感觉到人生是如此的匆忙。论文也没有做出来，简历也没有准备好，真不知道应该怎么办[允悲]。虽然刚来的时候我就提醒自己，很快就又要毕业了，但是没有想到时间过得这么快。粗略一算，相比成熟的实验室，我们好像少读了三个学期。大四时课题组都没成立，自己拿着几本书瞎看，自然是没有学会什么东西。研一下又因为历史事件发生只能待在家里，一跃变成了wow高手[笑cry]。徐教授人确实很好，大四时让我学会了文献检索与管理，专利写作，LaTeX，Visio等重要的科研工具，但是我实在是没有什么可写[允悲]。这几天捣鼓了一下LaTeX的一个会议模板，把之前开题报告里面的一些东西翻译了之后填了上去。看到生成后仅有两页的论文，内心也是十分开心，毕竟上面清晰地署着自己的名字。不过这学期总体说来还是挺开心，这大致要归功于老友记，以前一直有人推荐这部美剧给我，但是从来就没有把它完整下好过。这次寒假整了个迅雷会员一下把10季都下好后，我就开始了另一种消磨时光的方式。不得不说，即使是上世纪末拍摄的美剧，直到今天还能对我们产生许多影响，我也更加深刻地认识到了友谊的珍贵，从此不再为实验室的一些人际关系上的破事而烦恼。心情好了之后，很多事情也顺利多了，对一些事情不敏感的话反而能够更好地处理。还有一件值得一提的是我在语雀上面整了一个课题组的网站，等到以后我们组发达了之后再告诉大家吧，虽然可能也发达不起来了，因为我们老师就要慢慢走了。不过对我也没太大影响了，毕竟在他彻底离开前我已经毕业了[doge]。但是有几个小伙子要炸了，他们选的是本博连读计划，大四即博一，这大四还没读完导师就没了，唉好惨。不得不说，年轻人还是浮躁，沉不住心下来做研究。如果我们老师一直坚持待在这的话，我们组说不定会把他培养成杰青哈哈。至于为啥要弄个网站呢，因为我看很多牛逼的实验室都有主页，我们啥都没有总感觉太落后了，做做基础设施建设应该有益于长远的发展。今天看见组里同学发的周报，我突然意识到我好像影响到了许多人的科研方向，这都源于上学期我做的的几个大忽悠报告，把他们都忽悠了。要是以后发不出论文可别怪我这个方向不好啊哈哈，那不然我真的觉得自己有罪了。上学期老师安排了我们带几个本科生做毕设，然后有两个是保送到我们实验室的，老师希望他们大四时就能开始做研究，以后就能成果多多。我想如果老师你不准备离开的话，那他们这一届的培养环境一定会很好，毕竟已经有我们在前面铺路牺牲了。虽然说我带的那个同学很菜，实验也不怎么会做，写个调研报告大都是从我的开题报告里面复制的，唯一强的一点就是灵感挺多。不过我也不能过度批评她什么，毕竟我也不是老师嘛，更何况才大四，以后可以慢慢发展。至于实习怎么搞，还是明天做份简历出来，问问徐教授能不能帮我推荐去哪个公司看看了[doge]
在我还没开学的时候我弟就说他要来长沙玩，原因是他们还有很久才开学，而且开学了之后很难出学校，于是昨天他就坐火车到我们这来了。上午我们去爬了岳麓山，这次选了条爱晚亭左边的路上山，竟然比以前轻松很多。以前每次都是走的右边，那个路不仅陡而且很长[允悲]。这次从左边一会就上到了麓山寺，在里面又看见了这些晒着太阳一动不动的乌龟。岳麓山其实我上去过很多次，这次特别选了一条不一样的路，从东方红广场上去然后从我们学校下来。还记得上个学期我们几个同学吃饱了没事干，从我们学校后面直接上山，那真的爬得累死了。今天从这条路下来倒是很快。下午吃饭的时候和我几个老哥会合后大家一起去了烈士公园，因为现在3号线直接可以到公园东门，于是我们就直接从学校边上坐了过去。但是下了车后没人知道东门怎么进，只好先查了下地图才找到路。进公园后发现小朋友特别多，大多都是和父母一起出来玩的，即使是在什么都没有的普通草坪上，他们也能互相追赶着玩得特别开心。除了出来陪孩子的爸爸妈妈，也有很多结伴出行的年轻人。在天气晴朗的下午，最惬意的事情大概就是几个人坐在草地上玩桌游了。可惜我们不仅人不够更没有带桌游。公园里还有很多人在拍照，我们一般称这种人为带艺术家，无论是拍风景还是拍人像，其实都是艺术创作的过程。路边有很多盛开的梅花，在宣告着春天的到来。如果带艺术家萌萌姐来了的话，她肯定会对着这些花展开一系列艺术创作。说到艺术创作，我又想到了我尘封已久的微单，以前拿着它到处拍都很有意思，现在既没有时间又没有场景能够让它发挥自己的作用。这次开学都没带来，干脆把它放在家里了。绕着湖走了很远，看见右边有一座桥很好看便走过去参观一下。刚刚整理照片的时候才发现这张图拍得特别好，我其实也不知道这个角度为啥好，只看见有几个人都挤在那个地方拍桥，我也就过去拍了一张[喵喵]。看完桥之后呢沿着湖走了很久，发现在湖里划船确实是一件很有意思的事，但是感觉排队的人太多，于是决定等会人少了点再玩。其实这个公园我们小时候都经常来，只有和爸妈一起来长沙，必定少不了来这儿玩各种好玩的游乐设施。上次来的时候没有找到这些东西在哪个位置，这次终于找到了。但是突然发现很多游乐设施要么太幼稚，要么太无趣。几个稍微刺激一点的项目，比如说过山车和摩天轮居然都没有开放，也不知道是为啥。走了一圈发现真的没有什么好玩的，可是我们都记得小时候这里可全都是好玩的啊，也许是我们过了那个年纪了吧[允悲]，童年真的是一去不复返啊。到了最后，还是排队去买了票划船。船有很多种，最大的区别就是脚踏和电动。我们一天走了很远，心想还是电动舒服一点，只需要控制方向就可以了，可是到我买票的时候只剩下脚踏船了。大家觉得脚踏也还行，于是直接订了一艘。刚上船没多久，师傅就喊着我们靠岸，说旁边另外一艘船靠岸了让我们玩。原来是我们几个老哥块头都比较大，选一艘稳一点的船安全一些，走之前师傅还强调中途不要换座位，不然可能船会不平衡。其实这个船踩起来并没有那么累，反而挺好玩的，我们两个坐在后面的就开始了愉快的开船之旅。湖面上最舒服的就是迎面吹来的春风，让人好不自在。不过踩久了还是有点累，我们便在尽力保持平衡的情况下，交换了一下座位，大家轮流着踩。开始其实订了一个小时的船，但是感觉湖上的时间过得很快，没过多久就快到时间了，我们就只好停下和岸边游客互相注视的行为，把船还了回去。划船真的很好玩，而且心情也非常放松，下次一定还来。
前几周回了母校，大概已经一年没来了。12月份还是迟了一点，图书馆前的枫叶大多已经落下，人流也比往年稀少很多。学校里发生了很多变化，最让我羡慕的是图书馆下面新开的书店。以前想买书的话要么在网上买，要么只能去市里的书店。书店里面不光有很多陈列整齐的书，还有很多古典风格的座位。里面的同学很多，但却丝毫感受不到吵闹。看着这样美好的环境，我也忍不住买了两本小说，支持一下这家新开的书店。要是这家书店早一点开，我几位喜欢看书的朋友们估计会一直泡在这。学校里好吃的店子也有很多，当时身处这么好的环境反而没有珍惜，天天就在各个食堂吃来吃去，不过我们的食堂也是很好吃，丰泽三楼的孜然牛肉可香啦。这次我们先去东湖那边我以前经常和老哥吃饭的家常菜馆，我都不认识老板长啥样了，老哥居然还记得清清楚楚。吃完饭后又走到了红旗，吃了份豆花和一些小吃。晚上则在滨湖那条路上解决了晚餐。一般要重游故地的话，要么是有什么景色值得怀念，要么是有什么朋友值得牵挂。去年刚毕业的时候，没有回家就被老师喊到了这边的实验室。一时半会不能适应，经常跑回母校看看。那个时候不仅有留下来读研的朋友，尚未毕业的朋友，还有许多二战的同学，每次回来玩都是满载而归。如果他们都毕业了的话，估计就只只能拜访本科的老师了哈哈哈。大一的时候图书馆前面的枫树刚种下，稀稀拉拉的没有什么好看的。但是我的同学们还是乐意在这玩耍和拍风景。枫树慢慢地成长，到了大三的时候枫树已经长得枝繁叶茂，两排枫树包围的小道上落满了枫叶。不知道是被长沙哪个媒体报道之后，我们学校突然就变成了长沙的著名景点。每到周末学校就停满了车，很多家长带着孩子就坐在草坪上玩耍。只要碰见人问路，想都不要想就可以朝图书馆前面指。游客们好像还给那条路起了个名字，但是叫啥我实在忘了[允悲]。随着游客挤满了学校，我们就给学校起了个名字：马坡岭人民公园[二哈]。后来为了避免学校内的车太多，以前完全开放的学校在几个路口都设置了闸门，收点停车费。不过只花10块钱的停车费就能欣赏这么多美景，那当然是很值的。如果愿意坐公交车过来，那甚至不用花一分门票钱。以前每次想去枫树那拍照的日子，总是错过没什么游客的最佳拍摄时间。现在手机里最好的一张，还是大四的时候随手拍的。明年的话，一定要提前过去欣赏11月的红枫[doge]，以及3月的樱花。
圣诞节没什么活动，给大家讲点好笑的吧哈哈哈。上周刚刚开题完，但是还有一堆报告要写，而且实验也没怎么做。我在实验室实在是待着无聊，于是想着去徐教授那蹭饭吃。为什么我觉得有饭蹭呢，因为今天周五他们组开组会，如果会开得够晚，徐教授偶尔会请客。光看这一点概率确实不高，但是今天还请了别的学校老师过来作报告，而且今天是圣诞，这么多因素叠加在一起，怎么说应该能蹭到饭吃了。于是我就跟着他们组的老哥前往了湖大。到了湖大，看见了许久没见到的唐教授，虽然她可能不记得我是谁了哈哈。去年暑假的时候，她还没入职，就一直坐在我后面，当时还不清楚她是谁，后来才知道她博士都毕业了。第一个报告是一个师妹讲的，师妹的报告讲得还行，就是感觉区块链的文章都是大同小异，没有场景需求强行创造一个场景，然后针对虚假的场景设计了一个协议，没什么意思。问了下师兄，他说工业界里面没有什么人用区块链保障安全，因为这个效率实在是太低了，一般用用公私钥就够了。下面一个报告可就精彩了，报告人郭老师，一个普普通通的17届本科生，居然是他们学校的研究员[笑cry]还是产业化公司的架构师，太强了。反正一个小时的报告把数据安全和隐私保护的各个方面都讲到了。报告讲完后，几位老师又聊了快一个小时，眼看着就六点了，怎么说都应该大家一起共度圣诞佳节了吧。没想到徐教授首先拍了下郭老师的肩膀，说道：“哎呀，今天晚上还有点事，下次一定请你吃饭啊”。郭老师回道：“没事，正好我今天也有事，下次再聊”。唐教授默默地拿着本子瞬间也告辞了。最后只剩下我们一群同学在那呆若木鸡，原地愣住。怎么办？干饭人饭还是要干的，只好几个人一起到他们学院对面的鸡公煲小吃了一顿。席间谈到郭老师并不是普通本科生，而是博士中途退学了。好家伙，怪不得他说他们公司要像苹果公司学习，这不是乔布斯吗哈哈哈哈。回来的时候几个人都说，没想到徐教授今天不请客，太稀奇了。本人虽然有所遗憾，但觉得这还是比食堂好吃多了，十分满意。老哥还给我说，徐教授请客还好，唐教授就更有意思了。上次在电梯里碰见她，她就说正好大家一起吃个饭。老哥们心想这下又可以大快朵颐一餐了，没想到唐教授带着他们两个沿着学校主干道走了几百米，拐角进入了一个快餐店，给他们说：“这个饭店13块钱的盒饭超好吃的，请你们尝尝鲜”[二哈]。普普通通的唐教授，虽然博士毕业一年多，但还是不改学生本色啊，生活方式一直都是那么年轻哈哈哈哈。