尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

百度2019校招存储系统笔试题深度解析:从缓存到分布式一致性

百度2019校招存储系统笔试题深度解析:从缓存到分布式一致性 作为一个在存储行业摸爬滚打了快十年、也深度参与过校招面试的老兵看到“百度2019校招计算与存储系统研发工程师笔试题第三批”这个标题第一反应是亲切第二反应是感慨。亲切是因为这套题基本定调了国内互联网大厂存储岗笔试的“方法论”感慨是因为时至今日这套题里的很多考点依然是面试候选人口中的“拦路虎”。这套题区别于普通后端开发笔试题的地方在于它不考你背了多少框架API也不考你刷了多少道LeetCode而是把重心压在你对“数据路径”的理解上。Cache、IO栈、一致性协议、负载均衡、文件系统布局……每一个题目背后都是一套真实运行在百度搜索引擎、百度网盘、AI训练集群里的系统。对当年参加笔试的同学来说这套题是“劝退指南”但对真正想做存储的人而言它更像一张“藏宝图”。这篇博文我会把当年这套笔试题涉及的考点做一次系统性的拆解复盘。不是简单告诉你答案而是把每一个考点背后的设计逻辑、工程落地方式、以及我当时踩过的坑都摊开讲清楚。相信无论你是正在准备校招还是刚转入这个方向想建立知识体系都能从中捞到不少干货。1. 这套题到底在考什么整体考点架构拆解先说一个容易让人误解的点。很多同学一看到“计算与存储系统研发工程师”就以为这是考纯分布式系统理论或者纯粹考Linux内核。实际上2019年第三批这套题考的是“一条数据从用户态到磁盘再从磁盘返回用户态的完整生命周期里每一个环节可能出现的工程问题”。1.1 核心需求解析存储岗位需要什么样的人百度这个岗位隶属于基础架构体系服务的对象是搜索、Feed、AI训练这些对时延和带宽极度敏感的业务。因此这个岗位最底层的要求是候选人必须具备从“上层业务语言”翻译到“底层硬件语言”的能力。什么叫“上层业务语言”比如“把用户最近浏览的文章存下来”、“根据用户ID快速拉取他的好友列表”。什么叫“底层硬件语言”比如“这串数据要以什么格式落在SATA SSD的哪个Block上”、“为了保持一致性需要在内存里维护一个怎样的索引结构”。2019年第三批笔试题所有的题目设计几乎都在疯狂试探这一点——你懂不懂数据在真实硬件上的流动路径。它不会直接问“请简述LSM-Tree”而是会给你一个写放大极高的场景问你怎么调优。这种出题思路就是巴不得你把教科书上那些零零散散的组件全部串起来变成一条可以落地的知识线。1.2 试卷结构映射哪些是送分题哪些是分水岭从当年流出的题目回忆来看这套题大致可以分为四块基础题存储相关的理论知识比如页缓存、脏页回写、局部性原理等。这部分只要认真上过操作系统课基本都能答出一二。进阶题缓存一致性、并发控制、内存屏障。这部分开始刷掉一大批只会背概念的候选人因为他们写不出“在什么场景下可能出现什么具体问题”的推演过程。系统设计题给定一个小场景比如“大量小文件写入”让你设计存储方案。这里考察的不再是知识而是工程权衡能力。算法/数据结构题不会太难但通常会结合实际存储场景比如求一组数据的中位数但数据量是TB级内存放不下。我自己在帮部门做校招面试复盘时发现很多候选人是在“进阶题”和“系统设计题”之间被刷掉的。他们的通病是知道概念但不知道概念之间的关联。比如知道LRU和LFU但不知道在有Write-back Cache的存储系统里LRU的淘汰策略需要额外考虑“脏页”的比例。所以这篇博文里我会把这几个板块串起来讲而不是按题号一条条念。2. 存储系统核心题深度解析那些绕不开的“缓存与IO”存储系统笔试里最常出现也最容易被轻视的就是缓存相关题目。2019年这套题里缓存题目占比不低但它考得很“刁钻”不是让你对比LRU和LFU而是让你在真实场景里选择合适的淘汰策略。2.1 缓存淘汰策略的工程选型为什么LRU不是万能的先问大家一个问题如果给你的网盘客户端设计一个磁盘缓存你会用LRU吗很多人的第一反应是“会”。但如果你真的在百度做网盘客户端使用LRU大概率会被线上问题折磨疯。原因有三点。第一LRU只考虑了“最近被访问”这一个维度但存储系统里的数据还有“重建成本”这个维度。比如一份文件元数据重建成本极低重新扫描目录就行但一份用户照片的缩略图重建成本就很高需要重新解码原图。如果LRU先淘汰了缩略图用户翻旧相册时系统就要花几百毫秒重新生成体验直接崩。第二LRU对顺序读不友好。假设用户正在连续播放大视频文件视频编码块会源源不断进入缓存把前面用户反复点击的目录项全部挤出缓存。等到用户返回主界面时目录加载又从磁盘走了。第三LRU没有“脏页”意识。在Write-back缓存模式下被淘汰的Cache如果恰好是脏数据需要触发一次落盘操作这个淘汰动作的耗时是普通淘汰的几十倍。如果你的淘汰算法不区分干净页和脏页系统就会在某个瞬间出现严重的IO毛刺。所以当年这套题里如果要你回答“缓存淘汰策略选型”千万不要只答LRU。更好的回答是条件允许时使用LRU-K统计最近K次访问间隔、或2QTwo Queue同时为脏页预留比例比如强制保留20%的缓存空间给待回写脏页并且对“索引类小IO”和“数据类大IO”建立不同的缓存池。能答到这个层次基本就过关了。2.2 IO路径上的“隐形坑”从write()到磁盘到底经历了什么另一类高频考点是让你画出“一次写入操作的完整路径”然后问你在关键节点上可能出现什么问题。以Linux为例一次常规的write()调用数据先进入Page Cache然后内核根据脏页比率dirty_ratio/dirty_expire_centisecs决定何时触发回写。在回写过程中IO调度器如mq-deadline或bfq会对请求进行合并和排序然后进入驱动层最终映射到NVMe SSD或SATA盘。这里最容易丢分的点是“刷盘fsync到底在刷什么”。很多同学以为fsync就是把数据写到磁盘。实际上fsync的本质是将Page Cache中的这个文件相关的脏页以及文件系统元数据如inode、目录项全部刷新到存储设备并等待设备返回“写入完成”信号。但“写入完成”在很多SSD上只是意味着数据进入了设备内部的DRAM缓存断电后仍然可能丢失。所以真正可靠的数据持久化还需要依赖设备的掉电保护机制或者使用带超级电容的NVMe盘。当年这道题如果在笔试里出现能答到“IO屏障”和“掉电保护”层面的同学不多。大家普遍停留在“写入Page Cache→异步刷盘”的认知上。但如果你能主动补一句“这个路径上还有文件系统日志JBD2的提交点文件系统崩溃恢复时需要从Journal里重放未完成的事务”面试官对你的印象会立刻不一样。3. 分布式系统与一致性笔试中的“分水岭”计算与存储系统研发工程师躲不开分布式系统。2019年第三批笔试题在一致性协议、副本同步、负载均衡等方面的考查可以说是非常典型。3.1 Raft与Paxos的选择什么时候用强一致什么时候用最终一致这是笔试论述题里的常客也是很多候选人爱“背课文”的地方。照本宣科地罗列Raft和Paxos的流程已经没有任何区分度了考官想听的是你对“一致性等级”的理解。咱们可以把一致性理解成一个“光谱”。最左边是线性一致性Linearizability就好比只有一个单机数据库所有操作排队执行代价是可用性受限最右边是弱一致/最终一致代价是可能读到旧数据但系统吞吐量极高。那么问题来了存储系统怎么选我当年的经验总结是三个字——“看场景”。如果是元数据服务比如百度的BFSBaidu File System里的NameNode或者自研KV里的路由表建议使用强一致Raft。因为元数据一旦出错整个集群的数据布局都会出问题宁可牺牲一点延迟也要保证绝对一致。如果是存储节点之间的数据冗余比如两个副本的同步可以使用强一致加异步复制的混合策略。主副本写入成功后立即响应客户端后台异步把数据推给从副本。但这有个小问题——主副本宕机时未同步的数据会丢。所以需要引入“多数派”写至少保证超过一半节点有数据时才响应。如果是搜索内部的索引副本经常采用最终一致。因为索引数据天然容忍秒级延迟用户可以接受“刚发的帖子过几秒才被搜到”。笔试题里如果问你“Raft的Leader选举有哪些坑”有一个小点值得提候选人的选举超时时间不能设置成一致的。如果三节点超时都是500ms它们会同时发起选举票数分散一直无法选出Leader。正确做法是给每个节点的选举超时加一个随机扰动比如500ms ± 100ms。这个细节在《In Search of an Understandable Consensus Algorithm》论文里有提到但在实际工程中依然有很多人踩坑。3.2 一致性哈希的“数据倾斜”与虚拟节点老生常谈但总有人抛锚分布式存储的经典考点之一就是一致性哈希。但笔试里很少直接问“什么是一致性哈希”而是会问你“为什么经典的一致性哈希会导致数据倾斜如何解决”。我见过很多候选人一上来就答“加入虚拟节点”。这话没错但太薄了面试官基本不会满意。你需要往下再挖一层虚拟节点解决的是什么问题本质上是解决节点在哈希环上分布不均导致的“哈希热点”。当集群里只有4台物理机时直接算IP的哈希未必能均匀布在环上数据分布就会呈现出“同一台机器吃掉了40%流量”的极端情况。引入虚拟节点之后每台物理机会在环上生成比如128个虚拟节点。这样即使某台机器故障下线它的流量也会分散到多台物理机上而不是全部压到它的“顺时针后继节点”。但虚拟节点本身也会带来一个新问题——查找路由表的开销变大了。因此工程上常用的方案是在每台物理机内部维护一张“虚拟节点映射表”表里记录每个哈希区间对应的实际物理机ID。当节点状态发生变化时不需要全量重新哈希只需要更新映射表受影响区间的指针。这一层逻辑如果能写清楚这道题基本拿满分。当年我在记这个知识点时用了一个“排队打饭”的类比一致性哈希就是一群人先在一个大转盘上占位置虚拟节点就是每个人拿好几个小圆片占位置哪个小圆片离你最近你就去哪个窗口打饭。但小圆片多了你必须有一个“地图”才能快速找到最近的窗口——这张地图就是虚拟节点映射表。4. 操作系统与调度考的是“并发下的数据安全”存储系统是操作系统的一个“极端应用”。2019年笔试题里操作系统题目也不少典型的如并发控制、锁、内存屏障、中断延迟。很多同学觉得这部分简单但恰恰是容易“飘”的地方。4.1 自旋锁与互斥锁的适用边界别把store系统搞成“死锁现场”题目如果问“自旋锁和互斥锁的区别”大多数人都能答出来自旋锁忙等待、互斥锁睡眠切换。但问题在于存储系统的代码路径里什么时候用自旋锁什么时候用互斥锁边界在哪里我给你一个特别典型的场景Block Cache数据块缓存里的热点Block被多个线程并发读取。这个Block的查询路径非常短从哈希链表中找到索引节点然后引用计数加1整个过程只有几十条指令。如果你用互斥锁一旦锁被占住后来的线程就会被挂起然后触发一次线程切换和唤醒。这个切换的代价大概2~5微秒比锁本身的临界区大概几百纳秒还大极端情况下甚至导致CPU空转。所以这种场景应该用自旋锁Spinlock等锁的线程不放弃CPU原地打圈。但自旋锁也有一个致命弱点——如果锁持有时间过长比如超过一次上下文切换的时间那么占用CPU资源做无意义的空转反而会拖垮系统。所以当临界区里有磁盘IO、网络IO、内存分配等耗时操作时一定要换成互斥锁。我遇到过一个线上事故系统刚上线时一切正常跑了一周后高峰期出现严重的CPU “软锁死”警告。后来发现代码里把一条本应放在互斥锁保护范围内的元数据磁盘更新操作错误地放在了自旋锁的临界区里导致大量线程在自旋等待时CPU时间片全被浪费在无意义循环上。这个问题但凡笔试里做过“选锁”这个考点并在工作中养成“先估算临界区耗时”的习惯就能完全避免。4.2 内存屏障与无锁编程为什么会出现“明明加了锁还是出错”存储引擎为了追求性能经常使用无锁数据结构。但这带来一个非常隐蔽的问题由于编译器和CPU的乱序执行优化代码里的“先后顺序”并不等于“实际执行顺序”。举一个经典例子一个生产者线程写数据先把数据准备好然后设置一个flag表示“数据已就绪”。消费者线程看到flag后直接去读数据。在ARM或者PowerPC架构上如果没有内存屏障消费者线程完全有可能在读到flag为真之后读取到的数据仍然还是旧值。因为CPU可能将生产者“写数据”和“写flag”两条指令做了重排。Linux内核里解决这个问题的经典做法是smp_wmb()和smp_rmb()这对屏障。生产者在写完数据后、置位flag之前需要插入smp_wmb()消费者在读到flag后、读取数据之前需要插入smp_rmb()。这个知识点在存储引擎的RingBuffer设计里非常常见。笔试时如果涉及到无锁编程有经验的候选人都会额外强调一点内存屏障不是万能的它只保证同一CPU核心内的可见性顺序跨CPU核时还需要配合原子操作比如atomic_set、atomic_read系列。在x86平台上由于TSOTotal Store Order模型普通store的可见性要好于ARM平台所以很多在x86上跑得好好的无锁代码一移植到ARM上就翻车。5. 算法与海量数据处理笔试里的“最后一道防线”很多人以为存储岗的算法题会简单一些实际上2019年这套题里的算法题不仅考察常规的数据结构功底还特别偏爱“在内存受限条件下处理海量数据”这种工程化场景。5.1 Top K问题的变体从单机内存到分布式内存常规的“求Top K”问题大家条件反射式地会答“堆”或者“快速选择”。但如果问题变成“给定100亿个不重复的64位整数分布在1000台机器上求全局Top 100”你的思路必须立刻切换到“分而治之 归并”的框架上来。第一步先估算数据量。100亿个64位整数总大小是80GB单机内存绝对放不下。所以每台机器先在自己的数据片上求局部Top 100。这里注意如果机器内存足够比如64GB直接用大小为100的小顶堆扫一遍本地数据即可一次扫描的时间复杂度是O(n log 100)非常快。第二步把1000台机器返回的局部Top 100总共10万个整数汇总到一台协调机上再求一次全局Top 100。此时数据量只有10万内存毫无压力理论上可以直接排序也可以再用一个小顶堆。但笔试里的分水岭在这里如果你对存储系统有深入理解你会进一步想到如果这些数据不是静态的而是持续增加的流式数据该怎么办这就需要用到“布隆过滤器”或“HyperLogLog”这类概率数据结构了。布隆过滤器用于去重HyperLogLog用于估算基数。做存储系统每天要处理海量上报日志不可能每条数据都精确入库所以概率数据结构是必修课。当年我在笔试时还遇到过一道类似的题“给定一个非常大的日志文件每行是一个用户ID要求统计不重复用户数内存限制1MB。”很多人第一反应是维护HashMap但一算内存就崩溃了。正确思路是用位图法假设用户ID是32位整数位图大小为2^32 bit 512MB内存还是不够。那就只能退而求其次用分片哈希映射的思路把大文件切分为多个小文件每个小文件里只包含特定哈希区间内的用户ID然后逐个加载小文件到内存统计最后汇总。虽然多了一轮IO但这是工程上最常见的“空间换时间”解法。5.2 大数据量的“排序”场景外部排序与败者树另一个高频算法考点是外部排序。笔试里常给一个“200GB文件每行一个整数内存只有2GB请设计排序方案”的题。标准思路是先分块读入2GB数据在内存里排好序写回磁盘形成约100个有序的临时文件run。然后进行“多路归并”。这里有一个容易被忽视的点——多路归并时如果用普通的“堆”来做每取出一个最小值都要调整堆结构时间复杂度是O(log k)。但如果用败者树Loser Tree比较次数更少归并速度更高。对于考存储方向的同学掌握败者树的原理是加分项因为在Kafka的日志分段合并、LSM-Tree的Compaction中都有类似思想。另外一个实战细节是外部排序的中间临时文件最好放在尽可能快的磁盘上。我当年做笔试时一开始把临时文件落在机械盘上导致归并速度被拖垮得厉害。后来醒悟过来把临时目录挂到tmpfs内存文件系统上速度瞬间上来了。虽然tmpfs有掉电丢失风险但临时文件本来就是可重建的无所谓。6. 系统设计题从“会做题”到“会做系统”的临门一脚2019年这套笔试题里最拉分的是最后的大题——系统设计。通常会给一个几十字的场景说明让你设计一套存储方案。别小看这几十个字很多人在这一步暴露出“知识碎片化”的问题。6.1 场景题实战设计一个“海量小文件”存储系统一个典型场景是“有一个相册业务每天生成约1亿张小图片平均大小50KB需要支持按用户ID查询读多写少平均读时延要求低于100ms。请设计存储方案。”拿到这种题第一件事不是画架构图而是先做“数量级估算”。每天1亿张 * 50KB 5TB的原始数据。一年就是1.8PB。如果全部存在单机磁盘上纯属做梦。所以必须分布式。按用户ID查询意味着我们需要一个可靠的分片键。最简单的方案就是对用户ID做哈希然后按哈希区间映射到不同的存储节点。但问题来了50KB的小文件在传统文件系统上会有不小的元数据开销。每个文件至少有一个inode如果全部采用单机文件系统存储inode的数目会非常惊人。因此更合理的方案是“小文件合并成大文件”。把一定数量的小图片打包成一个逻辑上的大对象单独维护一张“对象索引表”记录每个小图片在哪个大对象中的偏移量和长度。这种设计思想在Facebook的Haystack、以及百度的图片存储系统里都有体现。在索引表的设计上可以采用“两级索引”的方式一级索引按用户ID分片记录该用户的索引文件地址二级索引是具体的对象映射记录每张图片的URL/ID到大对象ID offset, size的映射关系。这样按用户查询时只需要锁定一级索引再扫二级索引过程非常快。6.2 如何在笔试中“展示架构思维”答题模板与表达技巧系统设计题最怕的不是方案不完美而是不知道如何有条理地组织答案。我的经验是把答案分成四个步骤第一步明确需求边界。先问自己读多写少还是写多读少数据量多大一致性要求多高时延要求多少这些边界不明确直接给方案等于白答。第二步做容量与性能估算。用数字说话比如“每天5TB一年180天就是900TB”“平均读时延100ms那么查询链路上任何一环都不能超过10ms”。估算过程能展示你对工程规模的敏感度。第三步拆分核心模块。画一个简单的架构草图文字描述即可通常包含接入层、逻辑层、存储引擎层。然后逐个说明每个模块选择的理由。第四步专门设计“故障处理”和“数据生命周期管理”。比如底层存储节点宕机了怎么办冷数据要不要自动降级到廉价存储数据删除后空间如何回收这四步走完即使你的方案不是最优解面试官也会在“思路清晰”这一项给你加分。我当年参加百度面试时这个问题我用了12分钟答完面试官随后追问的几个细节也都包含在这四步框架里。7. 备考路线与经验教训从“刷题机器”到“系统构建者”聊完具体题目最后我想分享一些备考和复习的私货。这部分没有标准答案纯粹是个人的血泪教训。7.1 复习资料怎么选不只是《数据密集型应用系统设计》很多准备面试的同学喜欢啃《数据密集型应用系统设计》DDIA这本书确实经典但它的优点和缺点同样明显优点是覆盖面广缺点是每个点都点到为止缺乏代码层面的落地细节。我的建议是把它当作“地图”然后再补充三样东西Linux内核源码阅读重点是mm/page_cache.c、fs/ext4、block/blk-mq.c这几个目录。不用全部读完但要理解大致的调用链路。开源存储项目源码比如RocksDB的Compaction流程、WAL的实现Redis的RDB/AOF设计。读这些源码时重点关注“为什么这么设计才高效”。经典SIGMOD/FAST论文《The Log-Structured Merge-Tree (LSM-Tree)》、《BigTable: A Distributed Storage System for Structured Data》、《The Google File System》。这些论文是存储领域的思想源头面试时引用它们瞬间提升档次。7.2 时间分配与自我检测三个月足够但必须有节奏我比较推荐“三阶段复习法”。第一个月打基础。主攻操作系统和分布式理论把“什么是Page Cache”、“什么是Raft”、“什么是两阶段提交”这些概念全部吃透做到不看笔记也能讲清楚。第二个月看代码。从RocksDB或者LevelDB的源码入手自己动手编译一个简单的Store。有条件的话给RocksDB加一个统计项的patch你会对它的工作方式有更立体的感知。第三个月模拟实战。拿出历年真题严格按两小时限时做一遍。结束后拿出一张白纸把自己写的系统设计完整复述一遍。如果复述时感觉有些模块衔接不上说明还有知识盲区抓紧补。自测时有一个小技巧假装自己是面试官对每道题连续追问三次“为什么”。比如回答“我用LSM-Tree”就要追问“为什么用LSM-Tree而不用BTreeLSM-Tree的写放大问题怎么解决Compact策略怎么选”能扛住连续三个“为什么”才算真正掌握了这个考点。8. 写在最后一个存储老兵的实际感受回头再看2019年这套笔试题我最大的感触是它筛选的不是“知道得多”的人而是“能把知识织成网”的人。存储系统研发这个岗位本质上是跟“物理世界”打交道。你得理解一块SSD的随机写为什么比顺序写慢你得理解一次fsync可能触发多少层级的等待你甚至得理解一次内存屏障缺失能让一个线上集群在凌晨三点“神秘”抖动。这些知识点是散的分布在操作系统、计算机组成、分布式系统、数据库原理等各个角落。笔试的意义就是看你有没有能力把这些“散点”串联起来变成一条清晰的“数据路径”。如果你现在正在准备这个方向的校招给你一个最朴素的建议别把时间花在背模板答案上多问自己“如果这里换一种设计会怎样”。存储系统的世界从来没有唯一正确答案只有更适合当前场景的工程取舍。能把这种“权衡感”练出来无论笔试过不过你在存储这条路上都已经走在了很多人前面。
返回列表