
自己当年投七牛云校招的时候拿到这份“2018七牛云校招笔试题卷一”第一反应是“终于见到一份不水的题了”。市面上很多公司的笔试题要么全是八股文要么直接甩两道LeetCode让你自生自灭但七牛的卷一明显是奔着“你能不能干活”去的。整张卷子覆盖了网络协议、Linux、数据结构与算法、分布式存储还有一道需要你现场设计对象存储上传接口的题目考完出来我就觉得这家公司是真的在认真选人而不是随便筛简历。这份卷子过去几年了但里面的考点放到今天依然很有参考价值。现在很多云厂商、存储类公司的校招笔试风格跟它是同一套路只是题目换了个皮。我结合自己做存储相关开发的经验把这套卷子里涉及的核心方向、每类题背后的考察意图以及我当时是怎么答的、哪些地方答砸了都整理出来。不管你是准备云厂商校招还是想看分布式系统方向会考什么这篇都能给你一个比较完整的参考。1. 七牛云这份卷一到底在考什么拿到卷子先别急着刷题先搞清楚出题人的思路。七牛云的核心业务是对象存储、CDN加速、数据处理管道加上后来做的机器数据分析平台整个技术栈都是围绕海量数据、高并发、低延迟这些关键词展开的。所以这份卷子的题目设计摆明了就是要筛出那些对底层原理有真实理解、不是靠背题混过来的候选人。1.1 从题目方向反推公司业务卷一里涉及的网络题目占了不少比例主要围绕TCP、HTTP、CDN缓存这几块。这跟七牛的对象存储和CDN产品直接相关。你看他们官网上挂的存储服务用户上传文件、下载文件走的都是HTTP协议文件在全国各地分发依赖CDN边缘节点。如果候选人连TCP三次握手、HTTP缓存策略都说不清楚那进来之后大概率连日志都排查不明白。Linux和并发相关的题目也是重头戏。做存储服务端开发每天打交道最多的就是多线程、多进程、I/O多路复用这些。你写一个文件上传服务要能同时扛住几万路并发请求不懂epoll、不懂线程池的调度机制写出来的东西线上跑两天就能出事故。所以这套卷子里出现进程线程区别、锁的选型、内存管理这类题目完全是意料之中。数据结构与算法题算是分水岭。这部分不光是考你会不会写快排、会不会遍历二叉树而是考察你在有限时间内能否写出边界正确、复杂度可控的代码。存储系统里最常用到的就是哈希表、跳表、B树、LRU缓存淘汰这些卷子里直接出现LRU这种题很正常因为对象存储的元数据服务就是典型的读多写少场景缓存设计好不好直接影响延迟。最后那道系统设计题我记得是围绕“一个对象存储的上传接口”来展开的。这种题在普通互联网公司笔试里很少见但在云厂商里几乎是必考项。它考察的不只是你会不会用某个框架而是你有没有全局思维怎么分片、怎么做断点续传、怎么做秒传、元数据放哪里、数据放哪里、失败怎么重试。一套答下来面试官基本能判断出你有没有做过真实系统。1.2 难度与筛选逻辑这套卷子整体难度比一般互联网公司的校招笔试题要高半档但它并不是靠偏题怪题难住你而是靠“基础细节”和“设计深度”两层筛人。第一层筛的是基本功。TCP握手、进程线程、二叉树遍历这些只要科班学过计算机基础认真复习过都能答上来。这一层筛掉的是那些基础不扎实、简历写得天花乱坠但一考就露馅的人。第二层筛的是深度。比如同样是问TCP卷子里会追问“为什么TIME_WAIT要等2MSL”这种细节同样是问并发会问“自旋锁和互斥锁分别适合什么场景”。这些题目没有标准答案但有没有真正写过并发代码、有没有在线上环境排查过网络问题几句话就能看出来。我当时有几个细节答得模棱两可后来复盘才发现这些地方正是拉分的关键。1.3 一套实际的备考侧重点建议如果你现在正在准备云厂商或存储方向的笔试我的建议是不要盲目刷LeetCode而是先按“网络→系统→算法→设计”四个板块建立知识框架。网络部分重点吃透TCP状态机、HTTP缓存、DNS解析链路系统部分重点吃透进程线程模型、内存分配、I/O模型算法部分重点刷哈希表、二叉树、链表、动态规划主题设计部分重点理解对象存储的分层架构、一致性哈希、副本策略。下面每一类题目我都放了一些具有代表性的示例和拆解方便你对照着自测。2. TCP、HTTP与CDN网络题拿分的正确姿势网络题是卷一里最稳的送分题同时也是最容易丢分的地方。为什么因为很多人只记住了结论没记住推导过程。面试官问“TCP为什么要三次握手”你回答说“因为要确认双方收发能力正常”这句话没错但如果你画不出状态迁移图说不出SYN Flood的原理那这道题基本只能拿一半分数。2.1 TCP三次握手的底层逻辑TCP三次握手的本质是“在不可靠的信道上可靠地建立连接”。第一次握手客户端发送SYN服务端收到后知道自己接收正常、客户端发送正常第二次握手服务端回复SYNACK客户端收到后知道自己发送正常、服务端收发正常第三次握手客户端再回一个ACK服务端收到后确认客户端接收正常。到这里双方才确认彼此的收发能力都没问题。很多人会忽略一个细节为什么不把第二次和第三次合并成两次握手答案是防止历史重复连接请求造成资源浪费。如果只有两次握手客户端一个迟到的旧SYN会被服务端当成新连接建立起来白白占用资源。第三次握手带了确认序列号服务端可以通过序号判断这个连接请求是新是旧从而拒绝掉过期连接。在笔试里如果遇到这道题我建议你除了写清楚三次握手的过程再补一句“如果只握手两次无法防止客户端已失效的连接请求报文段突然又传到服务端而产生错误”。这一句话就能让阅卷人看出你确实理解了这个设计而不是背了网上的八股模板。2.2 HTTP缓存与CDN回源卷一里关于HTTP的题目通常不会只问状态码而是会把HTTP缓存和CDN结合起来考。比如给你一个场景用户通过CDN节点访问一个图片资源第一次请求回源站拉取第二次请求命中CDN节点缓存但如果源站的文件更新了CDN节点怎么才能让用户拿到最新版本这类题的考点是HTTP缓存头部的理解。源站返回响应时CDN节点会缓存Cache-Control和ETag这些字段。Cache-Control: max-age3600表示这个资源在3600秒内可以直接用缓存ETag是资源的内容指纹CDN节点可以在资源过期后用If-None-Match向源站发一个条件请求如果源站返回304 Not ModifiedCDN节点就继续用本地缓存而不重新回源下载。我当时答这道题时还额外写了一个实际生产经验对象存储里上传新版本文件时建议文件名带上版本号或内容的哈希值而不是固定用同一个URL覆盖。这样CDN节点完全不需要处理缓存刷新问题用户每次拿到的一定是最新的内容。这个经验我在后来做存储时经常用笔试里写出来也能显得你确实思考过线上问题。2.3 TIME_WAIT与2MSL答到点子上的加分项说到TCP卷子里几乎必有一道关于TIME_WAIT的题比如“主动关闭连接的一方为什么需要TIME_WAIT状态为什么等待时间是2MSL”。主要原因有两个一是确保最后一个ACK能到达对端。如果这个ACK丢了对端会重发FIN主动关闭方需要能再次回复ACK所以不能立刻关闭。二是让旧连接的报文在网络中自然消失。2MSL是报文最大生存时间的两倍保证上一次连接的所有报文在网络中彻底消失避免它们干扰后一个使用相同四元组的新连接。这个知识点在笔试里好答但在实际运维中很重要。如果你的服务作为客户端频繁连接外部服务并且处于TIME_WAIT状态的socket很多就会出现端口不够用的情况。解决思路一般是开启tcp_tw_reuse并在应用层使用长连接而不是治标不治本地改短TIME_WAIT时间。我在卷子上把这层实际经验也写了进去后来面试时面试官还特地追问了这个问题。2.4 网络题答卷的实操心得我在整理网络题答题思路时踩过最大的坑是“只背状态名不理解状态迁移”。比如TIME_WAIT是在哪一端、由谁进入的很多人张口就说但一画状态图就错。建议你在复习时把TCP状态图亲手画个五遍尤其注意SYN_SENT、SYN_RCVD、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSE_WAIT、LAST_ACK这八个状态之间的转换条件画熟之后网络题基本不会丢分。3. Linux与并发区分“背过八股”和“写过代码”的考题Linux和并发这块卷子的出题风格很务实。它不问你“什么是死锁”这种概念题而是直接在场景里考你“这段代码为什么死锁了”或者“这两个线程共享变量会出什么问题”。这也是很多只背面试题的候选人最容易翻车的地方因为光知道概念没用你得真的会分析代码。3.1 进程和线程以及“协程”的乱入进程和线程的区别是校招必考题但七牛的卷一会在末尾加一个“协程”的追问。基础答案很简单进程是系统资源分配的最小单位线程是CPU调度的最小单位同一个进程内的线程共享进程的地址空间而不同进程的地址空间相互隔离。但协程这个点很多人答不好。协程是用户态调度的“轻量级线程”它不需要内核参与切换切换开销远小于线程。在I/O密集型的存储服务里用协程可以轻松拉起几十万并发连接比如Go语言的goroutine就是这种模型。七牛云的后端服务大量使用Go语言开发所以这道题出现得很自然它考的其实是“你知不知道他们技术栈背后依赖的并发模型”。我当时的回答思路是先给出进程和线程的标准区别再说明协程和线程的核心差异在“调度者是用户程序而不是内核”最后举一个具体场景比如用epoll配合协程处理海量连接比“每连接一个线程”的模式节省大量内存。这样答完阅卷人基本就知道你不是只会背名词。3.2 同步机制锁选型背后的权衡卷子里关于线程同步的题通常会以“多线程同时写一个计数器怎么保证结果正确”为切入点然后不断追问用普通的i行不行加锁行不行用原子操作行不行性能有什么区别。这道题从表面看是考并发编程实际考的是对“原子性、可见性、有序性”的理解。i在底层是读取、加一、写回三步操作线程A执行到一半线程B插入进来就会丢更新。加互斥锁能解决问题但临界区过大会降低并发度。如果只是计数器这种简单操作用原子操作比如Go里的atomic.AddInt64或C里的std::atomic在大部分架构上会编译成一条CPU指令开销远小于锁。答题时如果能补一句“锁是有代价的加锁解锁本身涉及内核态切换和缓存同步所以能用无锁数据结构或原子操作时尽量不用锁”整体答案会更有深度。这不仅是笔试加分项也是真实开发中性能调优的基本思路。3.3 内存管理堆、栈、内存泄漏与OOM卷一里关于内存的题通常会放在Linux板块常见问法有进程的堆和栈有什么区别什么是内存泄漏它和栈上变量的生命周期有什么不同线上服务内存持续上涨你怎么排查。堆和栈的区别可以从分配方式、大小、生命周期、碎片化几个维度来答。栈由编译器自动分配释放容量一般在几MB到十几MB函数返回即释放连续且高效堆由程序员手动申请和释放容量受物理内存和虚拟内存限制容易产生碎片生命周期可以跨函数。还有一点容易被忽略栈上变量是线程私有的堆上数据可以被多个线程共享这也是线程之间传数据通常要用堆内存的原因。内存泄漏的排查我建议你掌握一个基本流程先用top或free看内存占用趋势再配合jstat或pprof抓内存快照对比多个时间点的对象增长定位到具体函数或数据结构。如果卷子上出的是分析题你可以写“用valgrindC/C或pprofGo抓取堆样本按对象大小排序找出可疑点”这个答案比单纯说“查看日志”要扎实得多。3.4 并发题最容易被忽略的I/O模型Linux并发题里有一个高频隐藏项是I/O模型尤其是epoll。比如“高并发网络服务为什么用epoll而不是多线程阻塞I/O”“epoll和select的区别是什么”。这类题在卷一里不一定单列但会在算法题或系统设计题的背景里出现。我的习惯是无论有没有问都主动把I/O模型的知识补充进并发题的答案里。比如在讲线程对比时提一句“在做高并发网络服务时线程模型不适合一连接一线程epoll配合非阻塞I/O可以在单线程内处理数千个连接”。这样既展示知识广度又让答案显得和实际工程接轨。4. 数据结构与算法有限时间内写出能跑的代码算法题在任何笔试里都是硬骨头七牛的卷一也不例外。不同的是这份卷子的算法题通常和存储系统有关联。比如LRU缓存淘汰、一致性哈希、跳表查询这些数据结构和分布式系统强相关如果只是单纯刷题不看应用场景碰到变形题容易懵。4.1 高频题LRU缓存从原理到边界LRULeast Recently Used是卷子里出现概率极高的题因为它既是经典面试题又和对象存储的缓存设计高度相关。要求通常是实现get和put两个操作并在O(1)时间内完成。最佳解法是“哈希表双向链表”。哈希表负责O(1)查找双向链表负责O(1)插入和删除。具体操作get时如果key存在把对应节点从链表当前位置移到头部并返回valueput时如果key存在更新value并移到头部如果key不存在在头部插入新节点如果容量超过上限删除链表尾部节点同时删掉哈希表里对应的key。我当年写这道题时犯过一个低级错误——在链表节点删除时没有同步删除哈希表里的key导致缓存污染。笔试里的代码不会运行给你看但阅卷人会一行行读逻辑这种低级错误很影响印象分。所以你在练这道题时一定要检查“哈希表和链表的一致性”这个点。4.2 二叉树遍历与递归转迭代卷一里二叉树相关的题属于基础题比如中序遍历、层序遍历、求二叉树深度。基础归基础它考察的是你能不能写出“代码整洁、边界完整”的实现。中序遍历的递归实现几行就写完了但卷子上有时候会加一个要求能不能不用递归写。这背后考的是对栈的理解。递归本质上就是函数调用栈自己模拟一个栈就能把递归转成迭代。层序遍历则需要借助队列每层开始时记录当前队列长度然后一次性处理完这一层的节点而不是边处理边入队导致层边界错乱。我建议你在笔试前把三种遍历、层序遍历、求深度、求宽度、判断对称这些基础二叉树题过一遍熟练到闭着眼睛能写出来。算法题的时间本来就不宽裕如果在这种基础题上还卡壳后面的设计题基本来不及写。4.3 排序、哈希与边界条件卷一里的算法题不大会考超高难度的竞赛题但会在经典题上增加边界条件的花样。比如快排要求处理大量重复元素二分查找要求找左边界和右边界链表题要求原地操作且不能使用额外空间。这里给你一个实用建议写排序题时先分清楚稳定和不稳定排序的适用场景再考虑复杂度。哈希表的题重点不是会用map而是要理解哈希冲突的两种解决方式——开放地址法和链地址法。链地址法在哈希冲突严重时查找退化成O(n)这就是为什么Java 8的HashMap在链表长度超过8时会转成红黑树的背后逻辑。如果你能在卷子里写出这个细节说明你确实理解哈希表的工程实现而不只是会调用API。4.4 算法题答题的节奏控制一套笔试题做完时间一定紧巴巴。我当时的策略是先把所有题目扫一遍按“会做-有点思路-完全没思路”分成三类。会做的题目优先做拿到稳的分数有点思路的题先写核心逻辑哪怕是伪代码也要写上关键步骤完全没思路的题最后再硬着头皮写思路哪怕只能写个暴力解法也要写“最坏时间复杂度O(n^2)”这种复杂度分析让阅卷人看到你有分析能力。记住一个原则在笔试里空着不写是0分写个暴力解法加复杂度分析至少能拿一半分。很多候选人眼高手低觉得暴力解丢人直接放弃反而丢了分数。5. 分布式存储与系统设计题从“写代码”到“做架构”的跨越卷一最后一道大题的风格是给你一个需求场景让你画出整体架构并说明关键设计。比如“设计一个对象存储的上传接口支持大文件上传要求断点续传、秒传、并发上传”。这种题没有标准答案但有一套常见的评估维度阅卷人主要看你的思路是否完整、有没有考虑异常情况。5.1 上传接口的分层设计对象存储的上传流程核心可以拆成四个部分客户端、接入层、元数据服务、存储引擎。客户端负责切片和并发上传接入层负责鉴权、限流、请求路由元数据服务负责记录文件ID、大小、分片信息、存储位置存储引擎负责实际的数据落地和副本复制。常见的设计是客户端先把文件切分成固定大小的分片比如4MB或8MB然后并发上传每个分片到接入层。每上传完一个分片客户端就向元数据服务登记一个分片信息。所有分片都上传完成后客户端发起一个“合并请求”元数据服务把所有分片拼接成一个大对象并返回最终的下载URL。这套流程在对象存储里已经很成熟了它最大的优势是把一个大文件的上传压力拆成多个小分片并发传输单个分片失败后只需要重传分片不需要重新传整个文件。这背后对应的就是分片上传的特性支持失败重试、支持暂停恢复、提高传输速度。5.2 断点续传、秒传其实是一件事断点续传和秒传这两个功能在对象存储里经常被合起来问。断点续传的原理是客户端记录已经上传成功的分片ID列表重新上传时先向服务端查询哪些分片已经存在跳过这些分片只传缺失的。秒传的原理更简单客户端在上传前先计算整个文件的哈希值把这个哈希值发给服务端如果服务端发现已经存在相同哈希的对象直接返回“上传成功”根本不用传数据。实际工程里为了算文件的MD5或SHA-256大文件都需要额外做一次完整的文件读取这个计算过程也会消耗时间和I/O。所以秒传一般会配合“先快速比对文件大小再计算哈希”来减少不必要的计算。而这些细节你在系统设计题里主动写出来就能和那些只会画架构图的人拉开差距。5.3 元数据与数据分离为什么是黄金法则设计对象存储时一个非常重要的原则是元数据和数据分离。元数据量小但访问频繁适合放在高性能数据库或专门的索引服务里数据量大但访问模式简单适合放到分布式文件系统或对象存储引擎里。两者分开之后元数据服务可以独立扩展存储节点也可以独立扩展互不干扰。如果你在卷子里画了“客户端直接上传到一个存储节点”这么简单的架构基本会被判为没有分布式经验。因为这种做法小文件可能还能用大文件上传时存储节点的带宽会成为瓶颈而且元数据没有地方登记文件在哪个节点完全靠猜后续查询和删除都没法做。5.4 系统设计题的踩坑经验与提分思路我在答这题时踩过一个坑只画了正常流程没有画异常路径。比如某个分片上传了一半客户端崩溃了怎么办某个分片上传完成但元数据登记失败了怎么办。这些异常处理才是分布式系统的难点。笔试时哪怕不写完整方案也要把“客户端先查询已上传分片再补传缺失分片”的重试机制写出来让阅卷人看到你有异常意识。还有一个提分技巧主动提数据可靠性。比如“每个分片在存储引擎中写入多个副本保证单个副本丢失后还能从其他副本恢复”或者“在多个可用区各存一份防止单机房故障”。这些点会让整体答案的完整度上一个台阶我见过很多候选人答案里完全没提副本和容灾这其实挺可惜的。6. 摸底自测这几道易错题你踩不踩坑前面是按知识板块拆解的这里我再挑几道我在复盘时觉得容易出错的题目类型单独捋一下。这些题单看都不难但在笔试现场的高压环境里很容易犯各种低级错误。第一类TCP相关TIME_WAIT是在主动关闭方还是被动关闭方答案是主动关闭方。但我见过不少人把CLOSE_WAIT和TIME_WAIT搞反CLOSE_WAIT是被动关闭方进入的状态表示对端FIN已经收到但本端还有数据没发完或者还没调用close。如果不理解这两个状态的区别排查线上大量CLOSE_WAIT连接时会一头雾水。第二类进程线程相关多线程中哪个资源是共享的、哪个是私有的以Linux pthread为例线程共享地址空间、全局变量、打开的文件描述符、信号处理器各自私有的包括栈指针、寄存器状态、线程局部存储TLS。这道题很多人会答错“栈”这一项以为是共享的。实际上每个线程的栈是独立的只是它们都在同一个进程地址空间里从高地址往低地址生长。第三类LRU实现里为什么用双向链表而不是单向链表因为删除链表节点时需要知道它的前驱节点。双向链表可以在O(1)时间内拿到前驱而单向链表只能从头遍历那样删除操作就变成O(n)了。这个细节虽然小但很能体现代码功底。第四类对象存储上传中秒传是“服务端算哈希”还是“客户端算哈希”正确答案是客户端计算哈希并传给服务端服务端做比对和查询。如果让服务端全量读一遍文件再算哈希那秒传就没有意义了因为数据已经全量传上来了。7. 考场上的一些实战经验笔试不只是考你会不会还考你在有限时间内的取舍和表达。我根据自己的经历分享几个可能对你有用的实战经验。拿到卷子建议先花5分钟把整张卷子浏览一遍大概判断各题的分值占比和时间消耗。我那次的策略是基础题控制在30分钟内完成算法题留40分钟系统设计题留40分钟剩下时间用来检查网络题里的细节。如果你在某一题卡了20分钟以上果断先跳过笔试最大的禁忌是在一道题上死磕。代码题的书写规范很重要。很多候选人写代码不讲排版变量命名用a、b、c阅卷人看着费劲就算逻辑对也容易被误判。你不需要写得像生产代码那么严谨但至少变量命名要有意义、缩进要清晰、关键步骤能加注释就加一句注释。记住笔试阅卷本身就是一段不轻松的过程让阅卷人舒服你的分才会高。另外有一个小技巧如果题目要求“写出思路”不要只写一句话。哪怕没有完整的代码也建议把数据结构的选型、算法的步骤、时间复杂度和空间复杂度都写出来。比如“用哈希表存储每个元素的索引遍历数组时查找target - nums[i]是否存在时间复杂度O(n)空间复杂度O(n)”这种答案即使代码没写全也能拿到大部分思路分。我在实际笔试时发现很多候选人完全不重视复杂度分析代码写了一堆但对错都不知道。其实阅卷人很看重你对代码性能的感知力写复杂度分析是一种“你在动脑子”的强信号。7.1 时间分配参考表题型建议时间答题策略网络与协议15分钟直接答核心点TCP状态图要画Linux与并发20分钟先答概念再补充场景细节数据结构与算法40分钟先做会的LRU这类必拿下系统设计题40分钟分层答正常流程和异常流程都写复查5分钟检查状态码、边界条件、代码变量名这个时间表是我自己实践中试出来的。如果你的代码能力偏弱可以适当把算法题的时间压一点留给系统设计题。系统设计题只要有一定思路拿分效率往往比死磕一道hard算法题更高。7.2 面试官想看到的答卷状态最后说一点关于“答题心态”的体会。七牛云的卷一整体风格偏工程化它不太喜欢那种“答案完美但和实际脱节”的内容。你在答卷时与其追求每个字都严谨不如多写一些“我在实际中见过/遇到过”的经验。比如讲到缓存设计你可以说“实际系统中缓存和数据库的一致性是很大的难点比如先更新数据库还是先删缓存每种方案都有坑”这种话一出来比你写十个优雅的架构图都有说服力。我复盘自己的答案时发现得分最高的部分往往不是那些标准八股内容而是我结合实际项目写出来的“为什么”。比如分片上传为什么分片大小选4MB而不是1MB是因为4MB在性能和传输次数之间比较平衡比如副本数为什么选3而不是2是因为三副本可以在容忍单副本故障的同时保证读性能。这些“为什么”才是阅卷人真正想看到的东西。这套卷子对我后续做存储方向帮助挺大的它逼着我把零散的知识点串成了一张网。现在你如果让我再去考一次我可能不会把每个细节背得多精确但我一定会更自信地跟面试官聊聊分布式存储里那些设计取舍背后的真实场景。准备七牛这套题的过程本身就是一次面向实际工程的技术复盘。希望这份拆解对你有用祝笔试顺利。