B站技术岗笔试复盘:前端、运维、后端与移动端核心考点解析 2019年秋招季我前后投了不少视频社区方向的岗位B站的笔试是让我印象最深的一批。和其他大厂不同B站技术岗笔试题没有把前端、运维、后端、移动端放在同一张卷子里硬考而是按方向分了多套题第三套流传最广。这套题整体难度中等偏上但区分度很高——基础题不白送实战题也不浮空不少题目直到我后来做项目才真正想明白出题人想问什么。这篇文章是我结合当年考场记录和后来复盘整理的版本重点不是贴答案而是把每道题背后的考点链、易错点和面试官视角拆开讲一遍。适合正在准备技术岗校招或者想查漏补缺的工程师参考。1. 前端笔试题复盘基础扎实比炫技更重要1.1 从一道“事件循环输出顺序”题说起前端这套题里最容易被轻视的是第一道代码题给出一段setTimeout、Promise、async/await混用的代码要求写出控制台输出顺序。题目本身不绕但错误率极高。它考察的是JavaScript事件循环的完整链路——宏任务、微任务、同步代码、异步回调的排队规则。我记忆里那道题大致长这样console.log(A); setTimeout(() { console.log(B); }, 0); Promise.resolve().then(() { console.log(C); }); async function foo() { console.log(D); await Promise.resolve(); console.log(E); } foo(); console.log(F);我当时是这样推理的同步代码先执行所以A、D、F依次输出。Promise.resolve().then()的回调C和await之后的E都属于微任务按照入队顺序C会排在E前面setTimeout的回调B属于宏任务要等当前宏任务执行完、微任务队列清空后才轮到。所以最终顺序是A、D、F、C、E、B。多数人错在把await和Promise.then当作同一种排队方式实际上await后面的代码相当于被Promise.resolve().then()包装但async函数本身是同步执行的。遇到多级await时需要一级一级拆开看。你可以把await看作一个暂停标记但真正的调度规则还是微任务队列。这里给一个可复用的检查清单先把同步代码画完再标出所有微任务的入队顺序最后才看宏任务。每次遇见Promise.resolve、async/await、process.nextTick混搭的题按这个顺序推就不会乱。这道题在2019年并不算偏但B站把它放在第一题其实是在筛掉那些只会用框架、不懂底层调度的简历。1.2 经典“防抖节流”手写题两种实现的边界差异第三套前端题里有一道手写题实现一个防抖函数和一个节流函数。很多人背过模板但题目额外要求说明两者分别在什么场景使用并处理this和event对象。这就在考两个边界第一防抖和节流的核心区别不是延迟执行而是多次触发时如何合并。防抖是把一段时间内的连续触发合并成一次适合搜索框输入、窗口resize节流是保证一段时间内至少触发一次适合滚动加载、点击提交。写代码时要注意防抖函数的定时器是否该在等待期间被clear节流函数的首次触发是立即执行还是延迟执行都会改变行为。第二this和event的处理。如果直接用setTimeout包裹回调回调里的this会丢失event对象也可能变成undefined。所以必须保存当前调用上下文用function关键字而不是箭头函数在返回的函数里缓存self this和args arguments再传给定时器。有的候选人能写出防抖但忽略了这条面试官会追问如果在Vue模板事件里绑定这个防抖函数会怎样答不上来就很可惜。一个不加额外讨论的防抖实现至少要把上下文和参数传进去function debounce(fn, wait) { let timer null; return function (...args) { const context this; if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); }, wait); }; }节流实现可以用时间戳也可以用定时器两者对第一次触发是否执行的处理不同。时间戳版第一次立即执行定时器版第一次延迟执行。场景题里如果面试官问滚动到底部加载更多希望停止滚动后不再触发用哪种这时候防抖反而不合适应该用节流加一个是否在滚动中的判断。1.3 网络与浏览器缓存考点背后的原理前端题有一道关于缓存策略的选择题一个静态资源URL如何保证用户更新后能及时拿到新版本同时减少不必要的请求四个选项涉及Cache-Control、ETag、Last-Modified、Expires。这题表面是HTTP头配置实际考的是强缓存和协商缓存的配合关系。我当时的回答思路是文件名加hash配合Cache-Control: max-age31536000实现强缓存当文件名不变时服务器可以返回ETag做协商缓存如果业务需要即时更新最好在版本发布时主动修改引用URL。关键是要明白强缓存命中时根本不会发请求协商缓存命中时会发一个带条件的请求但响应体很小。面试官还可能进一步问为什么ETag优先级高于Last-Modified因为Last-Modified只能精确到秒且某些场景下文件内容变了但修改时间没变。这类题在2019年几乎是前端必考但B站喜欢在选项里埋坑比如把ETag优先级低于Last-Modified这种说法混进去。复习时要连原理一起记不能只记结果。实际项目里特别要注意Cache-Control: no-cache不是不缓存而是每次都要再验证一次。如果被字面意思误导很容易在配置CDN时出错。2. 运维笔试题复盘排查思路比命令本身更值钱2.1 一道系统负载排查题的完整推理链运维方向第三套题里有一道场景题线上服务器load average达到20CPU使用率却只有30%问可能是什么原因、后续怎么排查。这道题没有标准答案但考察的是排查链路是否完整。我拆解的顺序是先看load average的含义——它是运行队列中可运行线程和不可中断睡眠线程的平均数。CPU使用率低但负载高说明大量进程在等待I/O。此时不能只盯CPU要用iostat看磁盘的await和util用vmstat看b列和wa列如果是磁盘I/O问题再用iotop定位是哪个进程在写如果等待的是网络或者锁就要结合上下文。如果这些都没有异常还有一种可能系统里出现了D状态不可中断睡眠的进程例如NFS挂载超时或内核线程卡住。这道题想考察的是你遇到负载异常时是直接重启还是能找到负载高和CPU高的差异点。运维岗的核心价值不是会敲命令而是有体系化的排查思路。我当时在答题纸上画了一条从现象到根因的决策链面试官后来反馈说这是加分项。具体来说可以按这个顺序写先执行uptime确认三个load值的方向。执行top看排队进程数按CPU或MEM排序。执行vmstat 1 5重点看r可运行线程、b阻塞线程、waI/O等待三列。如果b和wa高用iostat -x 1看磁盘util和await。用iotop或pidstat定位具体进程。如果是D状态进程卡死检查内核日志和挂载点状态。这个链路看起来简单但很多人只写到查看load就停了。笔试要把判断依据写完整比如wa高说明CPU在等磁盘而不是被计算任务占满。2.2 Linux常见命令的隐藏考点top/ps/netstat笔试里还专门出了一道选择题问top输出中load average的三个数值分别代表什么。很多人知道是1分钟、5分钟、15分钟的平均负载但选项里混入了三个CPU核数这种干扰项。类似的还有ps -ef和ps aux的差异netstat -tunlp和ss -tunlp的使用场景。这些命令在运维工具箱里是主力但笔试不会直接问你如何查看端口占用而是给一段输出让你判断哪一列是进程名、哪一列是监听地址。比如netstat输出中的Recv-Q和Send-Q如果数值持续变大说明接收队列或发送队列积压常见原因是应用处理不过来或对端不读数据。这种细节需要平时靠tcpdump抓包观察光背命令记不牢。另外2019年那会儿systemd已经普及但很多笔试考点还停留在SysVinit时代比如chkconfig、service。B站当时没有考这些偏门的反而考了systemctl的常见操作和unit文件依赖关系。复习时最好把新旧两套命令对照着看别被过时的教程带偏。针对命令类题目我的建议是动手搭一套虚拟机环境把top、ps、netstat、ss、lsof、iostat、vmstat、sar这些命令的典型输出截图打印出来练习一眼定位关键列。笔试不是让你默写参数而是给你一个真实输出让你解释异常点在哪里。2.3 网络问题定位从Ping不通开始运维方向还有一道经典题用户反馈某个内部系统无法访问Ping不通如何一步步排查这道题几乎是大厂运维笔试的标配第三套题里它的变体是区分是网络不通还是服务不通。我的排查顺序是先从本机出发ping网关确认本机网络是否正常再ping目标IP看ICMP是否通如果IP通但域名不通查DNS解析如果IP不通用traceroute看路径上哪一跳丢包最后如果网络层全通再用telnet或nc测目标端口判断服务是否在监听。笔试答题时不能只写命令还要写出每一步的判断依据和分支条件比如ping网关不通时应该检查网卡和路由表。这里有一个容易忽略的点有些网络策略会禁ICMPPing不通不代表TCP不通。所以最后一步一定要测端口用curl访问HTTP服务也很有用。答完整条链路面试官才认为你有真实排障经验而不是背了一堆命令。网络层排查经常会和DNS混淆。如果内网域名解析超时Ping域名和Ping IP会呈现完全不同的现象。笔试题目里会故意给出一段nslookup输出让你判断是DNS服务器问题还是缓存问题。这种题需要理解DNS递归和迭代查询的基本流程其实不难但很多人没在真实环境里抓过包只靠背题很容易掉坑。3. 后端笔试题复盘设计能力和细节并重3.1 一道SQL场景题的索引优化分析后端这套题里有一道SQL题给出一张用户订单表和一段慢查询要求分析如何优化。表结构大概是user_id、order_id、amount、status、create_time慢查询的WHERE条件是WHERE user_id ? AND status ? ORDER BY create_time DESC数据量百万级。我的第一反应是在user_id和status上建立联合索引但要注意顺序等值查询的字段放前面排序字段放最后。所以最合适的索引是(user_id, status, create_time)。为什么不是(status, user_id, create_time)因为等值条件区分度更高的是user_id把它放第一位能更快定位到目标用户的数据。如果只建(user_id, create_time)也能满足user_id过滤和排序但status过滤会在回表后进行效率不如三列联合索引。面试题里还问如果status区分度很低是否值得放在索引里实际上联合索引可以覆盖查询避免回表即使字段区分度低只要查询条件固定放在索引里就有意义。关键是要理解B树索引的最左前缀原则以及索引如何同时满足等值过滤和排序需求。我在答题时画了简单的索引结构示意比直接写加索引更有说服力。这里还可以继续展开如果查询要按create_time排序数据库是选择索引排序还是文件排序当索引前缀列包含create_time时MySQL可以直接顺序扫描索引取得排序结果避免filesort。如果条件里status是范围查询那么索引中create_time就无法参与排序优化了这时候可能需要考虑把范围查询改成等值或者在应用层提前过滤。笔试答案里能写出这一层范围查询会中断排序优化的细节就已经超过大多数候选人了。3.2 JVM与并发为什么总在问“内存溢出”后端卷子还考了一道JVM内存溢出场景题一个Java服务运行几天后响应变慢堆内存不断增长Full GC频繁但老年代回收效果差可能是什么原因选项里有内存泄漏、代码中持有大对象、JVM参数设置不当、线程池配置过大等。这题的考察点不是背JVM结构而是区分内存泄漏和内存过大。如果是泄漏GC后内存仍然持续增长如果是分配速率过高通常调整堆大小或对象缓存策略就能缓解。答题时应该给出排查方法用jmap导出堆dump用MAT或VisualVM分析对象引用链同时用jstat看GC日志观察各代内存变化。笔试作答时写清楚先查GC日志再抓堆dump最后看线程堆栈这套流程比单纯写可能是内存泄漏要高分数得多。更具体地说内存泄漏的常见模式是静态集合类不断添加对象但从不移除或者使用ThreadLocal时没有调用remove导致线程对象上的Entry无法释放。如果是线程池每次任务都往ThreadLocal里塞上下文线程复用后Value仍然被引用就可能撑爆堆。笔试问到这类题如果能把ThreadLocal的内存泄漏作为例子写出来面试官会眼前一亮。并发部分也有一道题多个线程同时递增一个count为什么最终结果小于预期要答出i不是原子操作、内存可见性、以及使用AtomicInteger或synchronized/Lock解决。这道题虽然基础但B站会追问volatile为什么不能解决i的原子性问题因为volatile只能保证可见性和有序性不能保证复合操作的原子性。这个追问能筛掉一大半背面试题的人。回答这道题时最好画出两个线程同时读取count为10然后各自加1最终写回11的时序。笔试答题纸上画图比较慢可以用文字描述read-modify-write三步的竞态窗口再说明如何用CAS或加锁消除。3.3 一道接口幂等性设计题的边界讨论后端方向还有一道设计题用户重复提交订单如何保证接口幂等这种题在2019年秋招里很常见但B站给的场景更具体——用户点了两次支付后端收到两个相同orderId的请求。答题时要分场景如果请求是同步的可以在业务入口用Redis SETNX设置一个处理中的key并带上超时时间如果请求走MQ可以基于消息ID做去重表如果是对账系统可以用数据库唯一索引兜底。重点是说明分布式环境下检查再插入存在竞态必须用带原子性的操作比如Redis的setnx或者DB唯一约束。同时要考虑幂等键的失效时间、重试机制、接口返回的语义是否一致。我当时额外补充了最终一致性的思路即使某个请求超时前端显示失败后端通过回调或定时对账最终完成订单这也能保证不重复扣款。踩坑时要注意光靠前端按钮置灰不叫幂等因为恶意请求或重试工具可以绕过前端。笔试里把这类边界写清楚面试官会觉得你有真实项目的复杂度意识。还有一个容易被忽略的点幂等不只是一次请求只生效一次还包括重复请求返回的结果应该和第一次一致。比如支付成功接口第一次返回成功第二次重复请求时如果返回订单已完成而不是成功对客户端来说语义不同可能导致前端重复发起轮询。所以设计幂等key时通常会把请求参数来源标识一起hash保证同一来源的重复请求能被识别。4. 移动端笔试题复盘性能优化和适配是主旋律4.1 一道“卡顿优化”场景题的回答框架移动端这套题里有一道典型的性能优化题一个视频列表页滑动卡顿如何定位和优化答案不外乎布局层、渲染层、数据层、内存层但B站把场景限定在Android端RecyclerView或iOS端UICollectionView列表每项有封面图、标题栏和滚动播放的预览视频。我的分析框架是先用SystraceAndroid或InstrumentiOS查看主线程耗时确认是掉帧还是丢帧再看布局复杂度减少嵌套、合并层级检查图片加载是否用了合适的采样率避免大图直接加载到内存列表复用时是否在onBindViewHolder里做了耗时操作比如频繁创建对象