校招笔试这种东西,不做一次完整准备就直接上考场,心态是很容易崩的。计算与存储系统研发工程师这道岗位的笔试题,尤其讲究对操作系统、存储、分布式系统基础概念的深度理解。2018年百度校招的第二批题我印象很深——它不考死记硬背,而是给你一个具体场景,让你像一个已经在系统组干了两年的人那样去分析问题。这篇文章把这类笔试涉及的核心知识点、典型题型以及我的备考思路完整梳理一遍,适合正在准备系统方向校招的同学,也适合想系统补一补存储基础的在职工程师。
1. 先搞清楚这个岗位在考什么
1.1 计算与存储系统研发工程师到底做什么
很多同学看到“计算与存储系统”这个岗位名,第一反应是“这跟后端开发有什么区别”,实际上差别很大。计算系统关注的是程序怎么被高效执行,比如CPU怎么调度、内存怎么管理、任务怎么并行;存储系统关注的是数据怎么可靠地落盘、怎么快速读取、怎么在多台机器之间保持一致。两者放在同一个岗位里,是因为现代数据中心里这些能力已经完全交融了。
举个例子,数据库的缓冲池就是典型场景:既要考虑内存页怎么管理(计算侧),又要考虑脏页怎么刷回磁盘(存储侧),还要考虑崩溃后怎么恢复(存储侧的正确性)。再比如搜索引擎的倒排索引加载,既要省内存又要快读盘。所以这类岗位的笔试不会只考简单的“什么是LRU”,而是会把问题嵌到一个系统场景里,让你从整体权衡。
我当时准备这份笔试时的感受是:它其实在筛选一个基础特别扎实、并且对系统全链路有感知的人。你知道一段数据从CPU寄存器到磁盘扇区之间经历了什么,你知道每一层缓存、缓冲区、队列的作用,你才可能应付这类题目。
1.2 笔试为什么偏偏考这些基础
第二批题的定位本身就说明了一点:这已经不是“你懂不懂编程”的筛选,而是“你能不能真的设计好系统”的筛选。所以考的大多是计算机组成原理、操作系统、数据库、分布式系统的基础题,但出法比较刁钻。
比如一道内存管理题,它不会问“页表是什么”,而是给你一个32位地址空间、二级页表结构,然后问某条虚拟地址经过哪些步骤变成物理地址,中间可能出现哪些异常。这种题目表面是基础,实际是把基础串成一条完整的链路。你如果只背了“虚拟地址由页号和偏移量组成”一句话,推导起来就会卡壳。
还有一类比较常见的,是结合热点话题出题,比如分布式存储中的一致性、日志复制、故障恢复。因为这类系统研发工程师日常就要处理这些工程问题,笔试只是把日常抽象成一种考察方式。理解了这一点,你就不难明白为什么我后面会反复强调:别只背结论,要把推导过程过一遍。
2. 第二批笔试题的高频考点地图
2.1 考点分布与优先级
从我当时刷题和面试的反馈来看,这批试卷的考点大概能分成四个板块。我列了一张优先级表,你在复习时可以直接参考它的侧重。
| 板块 | 典型考点 | 对应专业课 | 优先级 |
|---|---|---|---|
| 操作系统 | 虚拟内存、页表、页面置换、进程调度、死锁 | 操作系统 | 极高 |
| 存储系统 | 文件系统inode、RAID、磁盘调度、日志恢复、缓冲池 | 操作系统/数据库 | 极高 |
| 网络与分布式 | TCP、一致性协议、分布式事务、副本策略、CAP | 计算机网络/分布式系统 | 高 |
| 数据结构算法 | LRU实现、跳表、布隆过滤器、并发数据结构 | 算法与数据结构 | 高 |
这里有个容易被忽略的地方:存储系统虽然名字里带“存储”,但它和数据库原理的耦合度非常高。比如基于LSM-Tree的存储引擎、WAL日志、B+树索引,这些在应届生的操作系统课程里通常没有,却是企业系统研发的日常。如果你完全没接触过,建议提前补一下,不要等到考场上遇到才后悔。
2.2 一道看得见“考核逻辑”的选择题分析
为了让你直观感受这类笔试题的风格,我拿一类常见的选择题来拆解:单项选择题,给出一堆关于RAID的描述,让你选出正确的选项。比如“RAID 5最多允许两块盘同时故障而不丢数据”“RAID 10既能提高性能又能容错”“RAID 0至少需要三块盘”“RAID 1的利用率是50%”这类选项。
正确答案是第二个和第四个。RAID 5只允许一块盘故障,RAID 0最少两块盘。这种题表面是考RAID等级,实际是在考你有没有真正理解冗余和性能之间的取舍。你要是只背了“RAID 5是分布式奇偶校验”,没有算过它在四块盘时的利用率是75%,一旦选项换一种问法就很容易翻车。
我的建议是把RAID每个等级的容量利用率、最小盘数、能容忍几块盘故障、随机读写的性能特点,全部推导一遍。这些数值之间是有逻辑关系的,理解了奇偶校验怎么分布的,就不会把“坏两块盘”记成RAID 5。
3. 核心题型深度拆解:原理、计算与答题套路
3.1 页表与地址转换:一定要手推一遍
页表相关的题几乎是这类笔试必考的,而且常考常新。常见题干是:32位虚拟地址空间,页面大小4KB,页表项大小4B,采用二级页表,页目录索引和页表索引各占多少位?此时给出一个虚拟地址0x12345678,分析访问它需要经过哪几步。
这类题解题的关键是先定位页内偏移。4KB的页面,页内偏移需要12位。32位减去12位,剩下20位作为页目录索引和页表索引。因为每个页表页刚好能放下1024个页表项,也就是10位索引,所以页目录索引10位、页表索引10位、页内偏移12位。
32位虚拟地址 = 10位页目录索引 + 10位页表索引 + 12位页内偏移 页目录大小 = 1024页表项 × 4B = 4KB把0x12345678转成二进制,从低位往高位拆,低12位是页内偏移,再往上10位是页表索引,剩10位是页目录索引。然后回答:先查页目录基址寄存器,用页目录索引找到页目录项,得到页表页的物理地址;再用页表索引查到页表项,得到物理页框号;最后把页框号和页内偏移拼接,得到最终物理地址。
这里容易漏的是异常处理:页表项如果为“不在内存中”,就会触发缺页中断,由操作系统从磁盘换入页面;如果是保护位不对,会触发段错误。答题时把这几个分支带一句,得分率会明显上升。
3.2 页面置换与缓存淘汰:手算缺页是基本功
页面置换算法也是出题大户,常考FIFO、LRU、Clock、Optimal。很多人觉得概念背得很熟,一旦让手算缺页次数就开始乱套。我建议你找一道经典题练一遍:页面访问序列为1,2,3,4,1,2,5,1,2,3,4,5,内存分配3个页框,分别用FIFO和LRU计算缺页次数。
这个序列我手算过很多遍。FIFO按队列顺序淘汰最早进入的页面,最终缺页9次;LRU按“最久未被访问”淘汰,最终缺页10次;Optimal结果是7次。这里面有两个意想不到的结论:一是LRU不一定比FIFO好,二是FIFO在这个例子里意外地优于LRU。它说明页面置换算法没有绝对优劣,只能配合访问模式看统计行为。
笔试答题时建议把每一步的页框变化写成表格,或者至少把被淘汰的页面标记出来。因为阅卷时,计算过程比最终结果更重要。如果你只写一个“缺页10次”却不解释,改卷人很难给分。另外,缓存淘汰也常以“手写LRU”的代码题出现,考点是哈希表加双向链表,访问和淘汰都是O(1)。这个实现很经典,建议考前当场默写一遍。
页框变化(LRU,3个页框): 访问1 -> 缺页,页框: [1] 访问2 -> 缺页,页框: [1, 2] 访问3 -> 缺页,页框: [1, 2, 3] 访问4 -> 缺页,淘汰1,页框: [4, 2, 3] ...3.3 磁盘调度与RAID:容量和寻道时间都要会算
磁盘这块,最常见的是FCFS、SSTF、SCAN、C-SCAN四种调度算法。题目一般会给当前磁头位置和一组请求序列,让你分别计算总寻道距离。比如磁头在50,请求队列是82、170、24、98、140、36,按FCFS算就是每次取绝对值差求和。
SCAN算法要注意一个陷阱:磁头的移动方向是题里明确给出的。如果方向是从小到大,那么先把当前位置右侧所有请求都服务完,再掉头处理左侧的请求。很多人把方向忽略,直接按“先就近”处理,就把SCAN做成了SSTF,一步错步步错。答题时建议画一条数轴,把磁头位置和请求点标出来,再按方向把路径画出来,这样每一步的移动距离都清清楚楚。
RAID的计算则相对固定。四块1TB的盘,RAID0容量是4TB、容错0;RAID1容量是2TB、坏一块盘没问题;RAID5容量是3TB、只允许坏一块;RAID10容量是2TB、允许每组坏一块。笔试考RAID时,经常会把“几块盘坏掉后数据是否安全”和“利用率是多少”放在一起出,务必看清题干问的是利用率还是可用容量。
3.4 文件系统与inode:别停留在“知道有inode”
文件系统的题,如果只出概念选择题,基本是送分题,但第二批笔试往往会上一点强度。比如让你解释软链接和硬链接的区别,或者分析一个文件系统在写入文件时,inode、数据块、目录项分别什么时候更新。
inode存的是文件元数据和数据块指针,不含文件名;硬链接是多个目录项指向同一个inode,软链接是新建一个文件,内容存放目标路径。这些大家都知道,但题目一旦问“删除硬链接后文件数据是否还在”,很多人就犹豫了。答案是在链接数为0时才真正释放。你心里要清楚,inode里维护着一个链接计数,每创建一个硬链接就加一,删除一个就减一。
还有一类题结合日志文件系统,问写文件时崩溃会造成什么后果。日志文件系统先把“将要做的修改”写入日志,再实际修改元数据和数据块,所以崩溃后可以通过日志回放恢复一致性。答这类题时,把“先写日志再落盘”的顺序讲清楚,就已经拿到了主要得分点。
3.5 分布式一致性:从2PC到Raft考点
分布式相关题目在这类试卷里的比重越来越高,因为存储系统研发几乎绕不开副本一致性问题。最常见的是两阶段提交(2PC)和Raft协议。
2PC的考点往往集中在“协调者故障会怎样”。第一阶段协调者给所有参与者发Prepare,第二阶段发Commit/Abort。问题在于如果协调者在第二阶段开始前挂了,参与者已经回复了“可以提交”,此时参与者只能阻塞等待。题目如果问怎么解决,方向通常是引入超时和恢复日志,让协调者故障后根据日志决定是继续提交还是回滚。答题时能说出“2PC存在阻塞问题”就很关键。
Raft的题更偏向流程理解:一个日志条目是怎么被复制到多数节点后才提交的。核心是Leader接收客户端请求,把日志条目复制给Follower,超过半数确认后提交。如果出现网络分区,只有包含最新日志的节点才能当选Leader。这个协议逻辑不太复杂,但建议你把选举过程、日志复制、提交条件各写一遍,比反复看书有效得多。
4. 系统设计题的高分答题框架
4.1 存储系统设计题的类型
这类笔试题一般不会出得特别宏大,常见的是“设计一个简单的KV存储引擎”“设计数据库缓冲池”“设计一个分布式文件系统”。覆盖面广,但都有固定的思考路径。
拿“设计一个KV存储引擎”举例,它要求的不只是写一个HashMap,而是要考虑持久化、崩溃恢复、并发访问、数据量大于内存怎么办。一种常见方案是采用WAL日志加LSM-Tree结构:写入先追加日志,再写入内存表,内存表达到阈值后刷成SSTable文件,后台定期做合并压缩。读操作先查内存表,再查布隆过滤器确认数据是否可能在某个SSTable中,最后按层查找。
答这类题的时候不要一上来就堆组件,先交代清楚设计目标。你要说明这个KV引擎是写多读少还是读多写少,是否需要支持范围查询,是单机还是分布式。目标不同,技术选型完全不同。
4.2 一套可以复用的答题框架
我平时做题用的是五步框架:定场景、画模块、讲数据流、说一致性和故障、评估权衡。这套框架在笔试题里特别管用。
第一步,用两三句话说明系统要解决什么问题,写操作是多少QPS,读操作是多少QPS,数据总量多大。第二步,画出模块划分,比如客户端、协调者、数据节点、元数据节点。第三步,完整说明一个读写请求如何从头到尾走一遍,比如写入请求先写日志、再更新内存、定时落盘。第四步,思考进程崩溃、磁盘故障、网络分区时会怎样,需要什么机制保证不丢数据和最终一致。第五步,说明你的设计在什么场景下有效,什么场景下有瓶颈,怎么扩展。
这个框架最值钱的地方在于,它逼着你在答案里同时覆盖“功能”和“可靠性”。很多应届生只回答功能,不考虑故障,这在系统研发岗的笔试中是很吃亏的。你可以在答题时先按框架铺垫,然后针对一个核心环节做深度说明,比如专门展开LSM-Tree的合并策略,这比蜻蜓点水地谈一堆名词要好得多。
5. 实际操作中容易翻车的几个细节
5.1 计算题不画图,思路再对也白搭
页表计算和磁盘调度,如果不是把过程和中间结果写出来,很容易被阅卷人判错。我见过很多同学,心里清楚答案是多少,但只写了个最终数字,过程完全没有。这类题目大多数是按步骤给分,你写出的每一步就是在给自己攒分。
建议在草稿纸上先画内存地址的位分布,把页目录索引、页表索引、偏移量分别标出来。磁盘调度就画一条横轴数轴,标上磁头当前的位置和所有请求位置,然后顺着移动方向连线。画完图之后,计算过程会清晰很多,做错也能一眼发现是哪一步跳错了。
5.2 逻辑题漏条件,比不会做更可惜
有一类题目看起来很像是“概念题”,其实是逻辑题。比如问“RAID 5最少需要多少块盘”“一致性哈希里虚拟节点的作用是什么”“为什么WAL能防止数据丢失”。这类题目往往会在题干里藏条件,比如“块大小为4KB,页表项4B,页表项每页只能存放整数个”。
漏条件最容易发生在时间紧张的时候。我的习惯是拿到题目先圈出题干里的数字和限定词:位宽、页大小、项大小、方向、容量、故障阈值。圈完之后再动笔,错误率会低很多。特别是那些“不正确的是”“不包含的是”,不要因为一眼看到正确项就急着选,反向问法每年都能坑一批人。
5.3 时间分配建议
第二批试卷的题量通常不小,我当时的策略是:选择题快速扫过,遇到需要算的标记好,先跳过;问答题和设计题优先做,因为分值高且需要组织语言;最后回头补算选择题。这个方法不一定适合所有人,但对你识别哪些题是“送分题”很有帮助。
还有一个比较现实的经验:手写代码题不要直接写在最终答案区。先在白纸上把函数签名和主要数据结构写出来,再誊写过去。因为笔试题空白区域有限,写错了涂改非常难看,而且影响后面的作答心态。
6. 备考路线与复习建议
6.1 主干课程与参考书
如果你还在校,想冲刺这类岗位,最值得投入的课程是操作系统和数据库原理,其次是计算机网络。操作系统里要重点吃透虚拟内存、文件系统、进程调度和死锁;数据库里要重点看索引结构、事务日志、故障恢复。
书单方面,我比较推荐两本经典:一本是《深入理解计算机系统》(CSAPP),它把数据表示、内存、链接和系统I/O串成一条线,对建立全链路理解帮助非常大;另一本是《操作系统导论》(OSTEP),它讲虚拟化、并发和持久化,很多存储系统的核心概念都能在里头找到根源。如果你有余力,再找一本数据库实现相关的书,比如讲LSM-Tree和B+树实现的,对设计题尤其有帮助。
6.2 刷题的正确姿势
很多人刷题只是在OJ上刷算法题,但这岗位的笔试重点不在算法竞赛题上,而是偏系统基础和场景题。我建议刷三类题目:操作系统的经典计算题、存储系统的原理题、分布式系统的场景题,然后配适当的数据结构应用题。
第一类就是前面说过的页表计算、页面置换、磁盘调度、死锁检测;第二类围绕文件系统、RAID、日志恢复、缓冲池来出判断和设计;第三类会让你分析“某个存储系统在节点故障时怎么做副本补偿”。刷的时候不要只看答案,要把每道题按“考点是什么—推导过程是什么—如果换一个参数会怎样”来做笔记。换一个参数重新算一遍,是检验你真正理解没有的最好办法。
6.3 最后再分享一点个人体会
准备这类笔试,我最深的体会是:别把目标定成“把这套题做对”,而要把目标定成“能用这套题验证自己对系统基础知识的掌握”。计算与存储系统这个方向的知识点很多,但内部逻辑非常连通。你弄懂了一个地址怎么翻译,就会更容易理解页缓存为什么能加速访问;你理解了WAL为什么先写日志,就能理解很多分布式存储系统为什么也沿用同样的思路。
如果这份考卷你做得不顺手,别急着气馁,它本来就比“背一背就能过”的考试难一些。把错题按操作系统、存储、分布式三个方向归类,找对应的教材章节重新啃一遍,再做同类题验证,大概一个多月就能有非常明显的提升。等到你真正进入系统研发岗位之后回头看,这批笔试的每一道题,其实都能在工作中的某个环节找到对应。好好准备,值得。