
早些年我还在做基础架构的时候给团队招人就经常拿百度这套计算与存储方向的笔试题当参考。很多人一看计算与存储系统这几个字就发怵觉得这是系统底层方向离业务太远。实际上恰恰相反计算和存储是计算机系统的两根支柱任何一个有追求的技术团队都离不开能把这两样东西琢磨透的人。这套2019校招第三批的笔试题乍看是考察基础知识实际上是在筛一类特定的思维习惯——你能不能把一个请求从点击到最后落盘的全过程拆开看清楚每一层发生了什么以及每一层为什么会那样设计。这篇文章我就把这套题掰开揉碎从考题说开去延伸到背后的原理和复习方法。不管你是正在准备校招还是工作几年想回头补补体系知识都值得花十分钟看看。1. 这套题背后的岗位画像计算与存储方向到底在考什么先说个容易让人误解的点。很多同学以为计算与存储系统研发工程师考的是数据库、Redis、MySQL这些东西其实不是。这个岗位的核心是计算机系统本身——从CPU怎么取指执行到数据怎么在内存和磁盘之间流动再到分布式场景下多副本怎么保持一致。它考的是你对计算和存储这两个词背后整个技术栈的理解深度。我试着还原了一下这套题的考查范围主要集中在四个层面计算体系结构层CPU流水线、Cache命中率、指令执行过程、中断与DMA、字节序这些计算机组成原理的核心内容操作系统层虚拟内存、页表原理、进程调度、内存分配策略关注的是操作系统如何管理计算和存储资源存储系统层机械硬盘和SSD的原理差异、RAID各等级特性、磁盘调度算法、缓存替换策略分布式存储层数据副本一致性、CAP理论的实际权衡、分布式缓存与存储的关系这样设计题目是有道理的。一个做计算与存储系统研发的工程师日常打交道的就是这些玩意儿。比如做数据库内核的CPU的Cache命中率直接影响哈希索引的性能做分布式存储的不理解磁盘的随机写和顺序写差异就设计不出像样的LSM-Tree做KV存储的不懂虚拟内存和页缓存机制性能调优就是空中楼阁。所以这套题表面上是笔试实际上是在给候选人画像你能否从硬件层到软件层把一条完整的数据通路讲清楚。我在实际带人过程中发现一个规律凡是能把CPU如何执行一段代码和数据如何落盘这两条链路讲清楚的人上手做系统底层项目都特别快。原因很简单系统的性能瓶颈最后都会落在这两条链路上。这套题的考点设计恰好是顺着这个逻辑来的。2. 计算系统题目拆解命中率、流水线、I/O方式背后的原理账2.1 Cache命中率的计算题不只是公式是缓存设计的思想源头这套题里关于Cache的部分核心考题基本是给定访问序列让考生计算不同映射方式下的命中率。我记着有类似这样一道一个Cache共有8块采用LRU替换策略给出一个访问序列分别计算全相联映射和直接映射下的命中次数。这种题难度不大但它的思想价值远远超出笔试本身。很多人背会了公式就过去了没想过它为什么重要。我来解释一下直接映射就是给每个内存块固定了一个可存放的Cache行硬件实现最简单但冲突率高全相联映射是任意内存块可以放到任意Cache行冲突率低但硬件成本高因为每次访问要并行比较所有行的标签组相联是折中方案把Cache行分组内存块映射到固定组内任意行。实话说这个逻辑和我们在分布式存储里设计分片索引的思维是完全相通的。比如一致性哈希里的虚拟节点技术本质上就是通过增加映射的灵活性来降低冲突带来的热点问题。理解了Cache的映射机制再去看那些分布式缓存方案很多设计意图是一眼就能看穿的。在准备这类题时我建议你把三种映射方式的命中率变化趋势画在一张图上用同一个访问序列去对比。你会发现直接映射的命中率曲线会有明显的周期波动这正是因为冲突率高而全相联映射则比较平滑。这个规律本身就是面试官想听的深入理解。顺便说一句LRU里有个细节容易忽略——访存序列中连续访问同一个地址LRU不会重复淘汰它所以连续命中一次后它就会变成最新访问。很多人在手算LRU时出错要么是把访问已经存在于Cache中的行也当成了替换操作要么是算完新装入的行后忘了更新所有行的LRU顺序。记住LRU的本质是淘汰最久没被访问的不是淘汰最早进来的。2.2 指令流水线题超标量、冒险处理是上层性能优化的原型这套题里CPU流水线相关的考点通常会给一个五级流水线取指、译码、执行、访存、写回然后考察流水线冒险的处理方式。结构冒险、数据冒险、控制冒险这三个名词基本是必考的。其中数据冒险最常见也最容易出题。经典场景就是两条连续指令存在RAW依赖比如第一条指令的运算结果要作为第二条指令的操作数。解决办法有几种一是插入气泡stall简单但损失性能二是操作数前转forwarding把结果直接从一个流水线阶段旁路到另一个阶段不需要等写回三是在编译期做指令调度重新排列指令顺序降低冒险概率。有意思的是这种等待数据的困境在大型分布式系统里也能看到影子。一条SQL查询到了执行引擎下游算子需要上游算子的部分结果才能开始计算但为了效率我们不能傻等全部数据到位于是有了流水线执行pipelining模式。这跟CPU里的操作数前转本质上是一种思想尽量让数据在产生后能快速流向下一个消费方而不是落盘或者写回寄存器后再重新读取。如果题目更进一步考察超标量流水线那就要理解IPC每周期执行指令数的概念。理论上超标量可以做到IPC大于1但实际因为各种冒险平均IPC往往只有1点几。我在做性能分析时候经常用这个视角看业务系统的吞吐量——你在代码层面做了多少并行化最后真正跑出并发效率的往往受制于共享资源的冲突率这和CPU超标量的情况是同构的。2.3 中断与DMA的对比I/O效率的分水岭关于I/O方式这套题里围绕程序查询方式、中断方式、DMA方式出过不少选择题。程序查询方式是CPU死等中断方式是设备完成后通知CPUDMA方式则是数据搬运全部由DMA控制器完成只有全部搬运完成后才中断CPU一次。核心考点在于理解各自的CPU占用率。程序查询方式CPU全程被占用中断方式每次传输一个数据都需要CPU介入一次CPU介入成本高DMA方式按块传输CPU只需要在开始和结束参与。我当年做存储系统时的实际经验是判断基准一个高性能SSD的4K随机读IOPS可以到几十万甚至上百万如果用中断方式每次I/O都打断CPU一次那么CPU光是处理中断就要被吃干榨净不用干别的了。所以现代存储设备基本都依赖DMA加中断聚合的方式——也就是把大量完成的中断合并到一次处理里Netat无、中断合并interrupt coalescing技术就是这么来的。这里建议你把三种I/O各画一个时间轴标注CPU在哪段时间是空闲可干别的这样理解会更直观。网上很多讲这个知识的图配合题目做一遍基本就掌握了。3. 存储系统题目拆解层次结构、寻址逻辑与一致性方案的博弈3.1 存储层次金字塔为什么寄存器最快但磁盘才能存得住数据计算与存储系统笔试里几乎必考存储层次结构寄存器、Cache、内存、SSD、机械硬盘从快到慢、从小到大的金字塔排布。题目通常是让排序访问速度或容量但这个考点背后的系统设计思想才更关键。存储层次设计的核心逻辑是用容量换取速度的错觉。CPU寄存器只有几百字节但速度是纳秒级机械硬盘容量可以达到数TB但延迟是毫秒级。这之间差了几个数量级。计算机系统通过把热数据放在快介质上、冷数据放在慢介质上配合预取、缓存、写回等策略让整个系统对外表现出的性能接近最快的那层而容量接近最大的那层。这个思路在任何存储系统设计里都是通用的。比如分布式文件系统里元数据放内存、热数据放SSD、冷数据放到大容量HDD甚至对象存储配合数据分层策略就是存储金字塔在分布式场景下的翻版。搞懂了这个层次逻辑你对为什么Redis这么快为什么加了缓存系统还是慢这类问题会有一个更本质的理解。我碰过不少做业务的同学遇到性能问题第一反应就是加缓存。但加了缓存之后需要考虑缓存和底层存储的同步延迟、缓存穿透、缓存击穿这些衍生问题。实际上这些问题的根源都在于你打破了存储层次原有的数据流动规则。理解存储金字塔的每一层应该承载什么类型的数据负责什么访问模式才能做出合理的缓存设计。3.2 虚拟内存与页表题多级页表和缺页中断的常见误区虚拟内存几乎年年考核心就是页表机制。经典考题包括给定虚拟地址和页大小计算页号与页内偏移考察多级页表为什么存在缺页中断的处理过程以及页表项里各个标志位有效位、脏位、访问位的作用。这里的常见误区是把虚拟内存等同于使用硬盘当内存用。这个理解过于狭窄。虚拟内存的本质是为每个进程提供一个独立的虚拟地址空间让它们互不干扰同时让物理内存的分配变得灵活。换页机制只是虚拟内存带来的能力之一——当物理内存不够时可以把不常用的页换出到磁盘这就是所谓交换空间。一套页表题如果出得深入会结合TLB。TLB就是页表的Cache用于缓存虚拟地址到物理地址的映射结果。考试里可能会问为什么引入TLB之后进程切换时TLB要失效flush因为否则不同进程的相同虚拟地址会映射到错误的物理地址。现代CPU用ASID地址空间标识符来避免频繁刷新TLB这也是考点之一。我建议备考时你用Linux的/proc/self/pagemap接口实际看一下进程页表映射情况再配合time命令观察page fault的次数。这种动手实验远比光啃书有用也能让你省下不少死记硬背的力气因为为什么需要多级页表为什么需要TLB这类问题在你亲手看到缺页统计数字变大时会有直击要害的理解。3.3 磁盘与RAID题目随机写和顺序写的天壤之别存储笔试里磁盘相关题目必然围绕机械硬盘的寻道时间、旋转延迟、传输时间展开。核心是要明白机械硬盘随机I/O和顺序I/O的性能差距可以达到两到三个数量级。随机读写每次都要移动磁头到目标磁道再等待盘片旋转到目标扇区顺序读写则可以省去绝大部分寻道和旋转延迟。RAID题主要是考察不同等级的特性和冗余方式。RAID 0是条带化性能最好但毫无冗余一块盘坏全盘数据受影响RAID 1是镜像性能也可以但磁盘利用率只有50%RAID 5是分布式奇偶校验允许坏一块盘利用率较高RAID 6允许坏两块盘。考题经常结合随机写性能来问RAID 5写惩罚的问题。这里有个所有人都知道但很多人没有真正理解的细节RAID 5的每一次写操作实际上都对应至少四次底层I/O——读旧数据、读旧奇偶校验、写新数据、写新奇偶校验。这在SSD时代是个大问题因为SSD有写寿命限制。于是出现了RAID 5的替代方案比如RAID 10先镜像再条带化或者各种纠删码方案。面试时如果能主动提到RAID 5写惩罚这个点能明显加分。在SSD场景下磁盘调度算法虽然不再像机械硬盘那样重要但顺序写友好的思想却被继承了下来。LSM-Tree的核心理念就是把随机写转化为顺序写通过延迟合并来提升写性能。这可以说是机械硬盘时代遗留的智慧在存储新硬件时代的延续。理解了这层演变你再看各种KV存储引擎的设计文档会觉得顺畅很多。3.4 一致性题目CAP不是三选二而是面对分区时的取舍分布式存储方向必考一致性。题目经常是当一个分布式系统发生网络分区时你是选择可用性还是选择一致性于是很多同学就回答CAP理论说三者只能选两个所以选CP或者AP。这个回答方向没错但不够深入面试官想听到的是更细致的分析。CAP理论的准确表述是在网络分区发生时你只能在一致性和可用性之间二选一而在没有分区的时候你可以同时拥有两者。所以CAP真正考察的不是三选二而是当P发生时你如何做权衡。更进一步一致性本身的强弱程度还可以细分强一致性linearizability、顺序一致性、因果一致性、最终一致性。一台分布式数据库可以选择在正常时提供强一致读在分区时降级为最终一致也可以在副本间用Raft或者Paxos协议同步日志保证主从切换后数据不丢失。这些细致的设计权衡才是考察重点。这块建议结合具体的开源系统来复习。比如Etcd和ZooKeeper是CP系统牺牲了分区时的可用性换取了写入的强一致而Cassandra和DynamoDB是AP系统分区时允许旧数据被读到但保证了可用性CockroachDB则在架构层面通过Raft实现强一致但在跨地域部署时也增加了延迟代价。每看一个系统问自己一句它为什么这样取舍它的应用场景是什么带着问题复习收获会大得多。4. 拉开差距的硬骨头那些需要原理推导的综合性问题4.1 无锁队列与内存序并发存储引擎的微观基石这套题高端一点的卷子里偶尔会出现无锁并发相关的题目比如实现一个多生产者多消费者的无锁队列。这类题从笔试延伸到面试后通常会带你分析ABA问题以及compare-and-swapCAS配合内存序的使用。ABA问题之所以经典是因为它非常反直觉——一个值从A变成B又变回ACAS操作就会被欺骗认为它从未改变过。在无锁队列的场景里如果线程在读取head指针后、执行CAS前另一个线程把队列节点完成了出队入队循环导致head指针值不变那么当前线程的CAS会成功但指向的节点可能已经不属于这个队列了。解决办法是用带版本号的指针比如Java里的AtomicStampedReference或者用双字CAS。内存序的问题是同等重要的。在ARM和PowerPC等弱内存序架构上指令不会自动按代码顺序对其他核心可见所以无锁代码必须用acquire/release或者full barrier来保证顺序。这类题目考的就是你写并发代码时有没有跨CPU核心的可见性意识。我见过不少候选人在笔试里能把CAS的API背得很熟但一旦问为什么需要内存屏障或者x86上为什么通常不需要显式处理这个问题就卡壳。这里的关键在于x86是强内存序所以很多人平时开发感受不到问题一旦迁移到ARM服务器就踩坑。建议你在复习时去搜索memory model相关的图解资料或者在实际代码里用ThreadSanitizer跑一跑体会一下数据竞争的可怕。4.2 伪共享False Sharing多核性能杀手伪共享是高性能计算和存储引擎设计里的典型问题笔试可能会结合多线程编程出一道性能分析题。它是这样的CPU的缓存一致性是以缓存行通常64字节为单位的。如果两个线程分别操作两个不同的变量而这两个变量碰巧落在同一个缓存行里那么任何一方修改自己那个变量都会导致整个缓存行失效另一方需要重新从内存加载。这种我不碰你的数据但因为我们住在同一个屋檐下不得不互相牵连的花费就是伪共享。它最讨厌的地方在于代码是正确的但性能急剧下降而且很难肉眼发现。一个经典考题是用多线程对一个长整型数组进行累加各线程操作自己负责的那一段理论上应该线性扩展但实际性能却特别差。原因就是相邻线程的数据可能落在同一个缓存行上导致缓存行在多个核之间频繁传递。解决方案也简单粗暴——把每个线程的数据按64字节对齐填充或者用ThreadLocal的方式分配在不同缓存行上。我当年在生产环境排查过一个队列性能问题加锁方式都已经是无锁的了但吞吐就是上不去。后来用perf一看发现是存队列头尾指针的结构体正好在同一个缓存行里。生产者更新尾指针导致消费者核心上的缓存行失效消费者更新头指针又反向影响生产者。把头尾指针分别对齐填充到两个缓存行后吞吐直接翻倍。这种问题用任何用户态工具都很难靠逻辑定位一定要有cacheline的概念做引导。4.3 字节序与序列化跨端数据交换的爱恨纠葛字节序大端/小端这类题目多半是选择题或简答题。大端是高位字节存低地址小端是低位字节存低地址。x86和ARM处理器默认都是小端网络协议标准则定义为大端。考题常见的坑是给定一个十六进制数比如0x12345678在小端机器上的内存布局问它在网络字节序转换后的值是什么。这里的关键不是死记谁大谁小而是理解序列化和反序列化时按字节拆装的过程。存储系统同样离不开字节序。比如你在一台小端机器上写入了一个整数的二进制表示如果直接把这个文件拷贝到大端机器上解析得到的结果就是错的。所以跨平台的文件格式、网络协议、消息中间件都会明确指定字节序规范并在编解码层做统一转换。像Protocol Buffers、FlatBuffers这类序列化库内部就处理了这些问题。理解了字节序你才能理解为什么很多公司约定所有内部字节序统一用大端或者统一用协议栈指定的序。4.4 写放大与磨损均衡SSD存储引擎的设计约束这套题涉及SSD的考点通常不会太多但一旦出现往往就是区分度题。SSD以页通常4KB为最小读写单位以块通常几MB为最小擦除单位。想修改一个页里的少量数据不能直接在该页上改写需要把整个块读出来修改对应页内容再擦除整个块最后把数据写回。这个过程中实际的写入量远大于应用请求的写入量多出来的部分就是写放大。写放大直接影响SSD寿命和性能。考题一般让算一个场景下的写放大系数然后问怎么降低。降低写放大的常见方法包括用日志结构log-structured方式写数据把随机写变成顺序追加写做垃圾回收时采用贪心策略优先回收无效页最多的块预留一部分空间over-provisioning减少GC时的数据搬移。这些思想在分布式存储引擎里被大量应用比如LevelDB/RocksDB的compaction机制就是在管理写放大和读放大之间的平衡。备考这个部分时建议你画一张页-块-擦除的状态图把有效页、无效页、空闲页标出来手动模拟几轮GC过程写放大系数就自然理解了。这是那种图一画就懂的知识点单纯看文字描述反而容易绕晕。5. 备考路径与考场策略用这套真题重新校准复习方向5.1 按数据通路建立知识体系而不是死记八股回到我这篇文章开头说的那句话计算与存储系统考的是对数据通路的理解。基于这个判断复习的时候我建议你别按课本目录一章一章啃而是设计一条主线把知识点主动挂上去。主线可以是这样的一条用户请求从产生到存储依次经过了哪些硬件和软件环节。在最前端是CPU——你需要理解指令流水线、Cache、TLB然后是操作系统——进程调度、虚拟内存、文件系统再往下是存储设备——SSD和机械硬盘的工作原理如果系统是分布式的还要加上网络和一致性的部分。你每次学到一个新知识点都往这条链路上对应位置挂一下问自己它在这条链路上承担什么职责自己往下游和上游分别依赖什么。这样复习的收获是你对每个知识点的记忆不是孤立的而是结构化的。到了考场上哪怕遇到没见过的新题型也能顺着这条链路找到切入点而不是完全懵掉。这比做大量题的效率高得多。5.2 经典教材和资料怎么选不求多但求能读透市面上的计算机系统类教材很多但真正适合这个岗位笔试的并不用贪多。我推荐准备三本核心就够了计算机组成原理任选一本经典的覆盖CPU流水线、存储层次、Cache、DMA这套题的硬核基础都在这里操作系统概念或者现代操作系统重点看虚拟内存、页面置换、进程调度和文件系统相关章节分布式系统概念与设计或者深入理解分布式系统重点看一致性模型、容错、CAP、存储相关章节如果学有余力再配合一些论文级别的资料。比如Google的GFS和BigTable论文、Amazon的Dynamo论文都是分布式存储方向非常经典的参考文献。把这些材料读透一个层次笔试中遇到开放性问题时你就能从工程实践的视角来分析而不是只能给出课本结论。5.3 考场上的时间分配和答题策略先把必拿分拿下这套题的时间压力和很多笔试一样题量不小。我建议答题时先把快速判断题和选择题解决这些大多是概念记忆型不需要太多推导花的时间应该控制在总时间的三分之一以内。然后是计算题比如Cache命中率、地址换算、RAID写惩罚、写放大系数这部分需要细心计算也是最容易拿分的地方。算完最好快速复查一下单位换算和公式代入过程比如KB还是KB块大小是4KB还是4K扇区这类单位错误非常常见。最后留出充足时间给综合题和简答题。这类题分值不小而且考察的是思维深度。答题时不要只写结论要把推导过程、权衡分析写出来。比如问题问设计一个分布式存储系统你最关注什么你应该从功能需求、性能需求、可靠性需求几个层面展开并具体说明你是用什么机制满足这些需求的。这种结构化答题即使回答的机制不是最优的也能看出你有系统的思考方式。5.4 复盘真题的姿势从知道答案到能讲明白原理做完一套真题后复盘比做题本身更重要。我见过太多人是做完对个答案就完了下次遇到同类题还是错。复盘的正确姿势是这样的每道题无论对错都问自己三个问题——这道题考的是什么知识点如果我把答案讲给别人听能不能让他听懂这个知识点在真实系统里对应什么组件尤其是第三个问题。比如Cache命中率那道题它对应的真实系统组件是多级Cache、是CPU访问存储层次时的预取策略虚拟内存那道题对应的真实系统组件是操作系统的缺页异常处理、是数据库的缓冲池管理。如果你能建立这种题目知识点到工程组件的映射那么这套题的价值就不仅仅是应付一场笔试而是真正为你后面的职业生涯打底子。在实际带新人的过程里我发现凡是能坚持这样复盘的人通常三到六个月就能独当一面因为他们的知识体系不是背出来的是真的长在脑子里的。最后再分享一个小技巧。字符串和数字这类数据的内存布局题你要是总记不住大小端就自己动手写一段代码在C语言里打印一个多字节整数的逐字节值观察它在x86和ARM机器上的差异。一次实操胜过十次背诵。准备计算与存储方向的笔试终极目标不是背下一堆题解而是建立一套属于你自己的硬件软件全链路思维模型。有了这个模型不管是笔试还是面试你都不会慌。