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

资讯详情

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

映客春招研发E卷复盘:从网络基础到高并发系统设计,校招笔试核心考点全解析

映客春招研发E卷复盘:从网络基础到高并发系统设计,校招笔试核心考点全解析 最近后台收到不少读者留言都在问同一类问题“校招研发笔试到底考什么怎么准备才不白费力气”正好手头还留着之前整理过的“映客2020春招研发E卷”的复盘笔记翻出来重新过了一遍发现里面的题目思路放到今天也完全不过时。这套卷子的考察范围很典型网络基础、操作系统、数据库、算法、并发外加一道贴近直播业务的系统设计题基本覆盖了后端研发岗位笔试的高频考点。不管你是准备校招的应届生还是打算跳槽、想检验一下自己基本功的初中级工程师这套题都值得认真做一遍。整份卷子不偏不怪难度梯度也合理基础题让人有话可说算法题需要动手能力系统设计题则直接拉到了真实业务场景。今天这篇就把这套卷子完整拆开结合我当时的答题思路和后来面试别人时的出题视角把每一类题目的解题策略、易错点和加分项都聊透。1. 试卷整体结构与考点分布先看整体。这套E卷的题型分布大致是单选和多选约15至20道、简答题约4至5道、编程题2至3道、系统设计题1道。从分值占比来说基础选择题和简答题加起来大概占40%编程题和系统设计题占剩下的60%。这个结构其实很能说明问题笔试不只考你会不会背八股文更看重你能不能把基础知识落到代码和架构上。单选的考点集中在计算机网络、操作系统、数据结构与算法基础、数据库索引、HTTP协议、Java/TCP/UDP这些方向。比如TCP三次握手和四次挥手的状态变化、进程线程协程的区别、B树索引为什么快、HTTPS握手过程、常见HTTP状态码的含义这些都属于“背了就有分”的题目。简答题则更难一点通常会结合场景让你分析比如“高并发下缓存穿透和缓存雪崩怎么处理”“为什么消息队列能削峰”这类。编程题是拉开差距的核心。E卷考的算法题没有特别偏门的但都有实际业务影子。比如LRU缓存淘汰策略、最长无重复字符子串、多线程交替打印数字这些都算经典中的经典但越是经典越能看出一个候选人的代码基本功和边界处理能力。系统设计题直接聚焦直播场景让候选人设计一个高并发弹幕系统这正好对上了直播平台的业务特点。从岗位和行业角度看直播平台的后端研发核心要解决的就是三件事高并发读写、实时消息推送、海量数据存储。这套卷子能看出来出题人是带着业务视角去设计题目的而不是随便从题库里抽题拼凑。你准备这类笔试的时候不能只埋头刷LeetCode还要多想想这些算法题放到真实业务里会是什么形态这套卷子的价值就在于此。2. 基础题解析网络、操作系统与数据库考点2.1 TCP三次握手与四次挥手这套卷子里必考TCP三次握手和四次挥手而且不是让你简单背诵过程而是会问“为什么握手是三次挥手却是四次”这种原理性问题。三次握手的本质是让通信双方都确认自己和对方的收发能力正常。第一次客户端发送SYN服务端收到后确认客户端的发送能力和服务端的接收能力正常第二次服务端回SYNACK客户端收到后确认双方收发能力都正常第三次客户端再回ACK服务端收到后确认客户端的接收能力和自己的发送能力正常。所以三次是确认双方收发能力对称性所需的最少次数。四次挥手则是因为TCP连接是双向独立的全双工通道。当一端要关闭时它只关闭了自己的发送方向对端可能还有数据要发所以不能一口气把两个方向的通道都断开。第一次挥手发送FIN表示“我的数据发完了”对端回ACK表示“知道了但我可能还要继续发”。等对端数据也发完再回一个FIN最终由发起端回ACK确认。整个流程里TIME_WAIT状态值得特别注意主动关闭方要等2MSLMax Segment Lifetime才能完全关闭主要是为了保证最后一个ACK能到达对方同时让本连接内延迟的报文段在网络里自然消失避免影响后续连接。面试和笔试里提到这个状态最好能顺带说一句“TIME_WAIT过多时会出现大量socket处于TIME_WAIT状态导致端口不足服务端可以用tcp_tw_reuse、tcp_timestamps等参数优化但需要谨慎”这种细节是明显的加分项。2.2 进程、线程与协程的区别操作系统考点里常出的一道简答题是让候选人说说进程、线程、协程的区别并要求结合高并发场景说明选型。进程是操作系统资源分配的最小单位有独立的地址空间。线程是CPU调度的最小单位同一进程内的线程共享堆和方法区各自拥有独立的虚拟机栈和程序计数器。协程是用户态调度的轻量级线程由程序自己控制切换不依赖内核。从开销角度来看进程切换开销最大线程次之协程最小。举个例子一个直播间有几十万在线用户如果每个连接配一个线程资源消耗会非常夸张所以高并发I/O密集型场景通常用协程或者事件驱动模型处理。我当时在笔试卷里是把三者列了个对比表再展开写阅卷人一眼就能看到知识点完整这种答题习惯在笔试里很占便宜。其实这个知识点你在准备时只需要一个切入点去记忆资源拥有者不同、切换方式不同、并发粒度不同三个区别展开写每条补充50字简答题就能拿满。2.3 HTTPS握手过程与HTTP状态码网络部分一定会涉及HTTP和HTTPS。HTTP状态码需要熟练掌握2xx表示成功3xx表示重定向4xx表示客户端错误5xx表示服务端错误。具体到映射的是什么样的业务场景简单题会问“502和504区别”502是网关或代理服务器收到了无效响应504是网关或代理服务器在规定时间内没收到上游响应一个是响应内容问题一个是响应超时问题这个很多人会混淆。HTTPS握手过程属于简答题高频题。核心要理清“对称加密非对称加密”是怎么配合的TLS握手阶段客户端发起ClientHello服务端响应ServerHello并携带数字证书客户端用内置CA公钥验证证书合法性验证通过后生成随机数作为预主密钥Pre-Master Secret用服务端公钥加密发送给服务端双方用这三个随机数各自独立协商出同一份会话密钥后续通信全部走对称加密。之所以要这么设计是因为对称加密性能好但密钥分发难非对称加密能解决密钥分发问题但性能差两者配合既安全又高效。2.4 数据库索引与慢查询优化数据库题目在这套卷子里考察了B树索引和慢查询优化。为什么MySQL InnoDB引擎选择B树而不是B树这个问题需要说明核心差异B树非叶子节点只存索引键不存数据所以每一层能容纳更多键树高更低查询时磁盘I/O次数更少B树叶子节点之间通过指针连接天然支持范围查询而B树做范围查询需要多次回溯。另外一个细节是B树所有数据都集中在叶子节点查询次数稳定不会出现B树那种数据在某些层、有些查找快有些查找慢的情况。慢查询优化是面试里非常贴近实战的题。拿到一条慢SQL第一步先EXPLAIN看执行计划聚焦type、key、rows几个字段。type至少要达到range或ref级别如果是ALL全表扫描就要特别注意。常用的优化手段包括为WHERE条件和ORDER BY字段建立合适索引避免在索引列上进行函数运算导致索引失效针对深分页问题比如LIMIT 100000, 20用延迟关联或子查询先拿主键再回表。这些点我在笔试里也写了阅卷人给评语时特意标了“有实战经验”说明他们确实希望通过笔试筛掉只会背概念的人。3. 算法编程题实战解析3.1 LRU缓存淘汰策略LRULeast Recently Used最近最少使用是E卷里的一道经典编程题要求实现一个支持get和put操作的LRU缓存get和put的时间复杂度都必须是O(1)。这道题的核心数据结构选型是“哈希表双向链表”。哈希表用于O(1)时间定位key对应的节点双向链表用于维护访问顺序每次访问或插入一个key就把对应节点移动到链表头部当缓存容量满了就淘汰链表尾部的节点。class LRUCache { class DLinkedNode { int key; int value; DLinkedNode prev; DLinkedNode next; public DLinkedNode() {} public DLinkedNode(int key, int value) { this.key key; this.value value; } } private MapInteger, DLinkedNode cache new HashMap(); private int size; private int capacity; private DLinkedNode head, tail; public LRUCache(int capacity) { this.size 0; this.capacity capacity; head new DLinkedNode(); tail new DLinkedNode(); head.next tail; tail.prev head; } public int get(int key) { DLinkedNode node cache.get(key); if (node null) { return -1; } moveToHead(node); return node.value; } public void put(int key, int value) { DLinkedNode node cache.get(key); if (node null) { DLinkedNode newNode new DLinkedNode(key, value); cache.put(key, newNode); addToHead(newNode); size; if (size capacity) { DLinkedNode tailNode removeTail(); cache.remove(tailNode.key); size--; } } else { node.value value; moveToHead(node); } } private void addToHead(DLinkedNode node) { node.prev head; node.next head.next; head.next.prev node; head.next node; } private void removeNode(DLinkedNode node) { node.prev.next node.next; node.next.prev node.prev; } private void moveToHead(DLinkedNode node) { removeNode(node); addToHead(node); } private DLinkedNode removeTail() { DLinkedNode res tail.prev; removeNode(res); return res; } }写这道题有几个容易踩坑的地方。第一双向链表的哨兵节点head和tail一定要初始化好很多候选人直接为headnull、tailnull操作链表时空指针异常第二缓存size自增自减的时机要清楚插入新key才算size增加更新已有key不算第三注意HashMap里存的是节点引用而不是值这样才能在get的时候通过key直接定位到节点从而操作链表。除了功能正确笔试里如果能主动写出复杂度分析说明你对算法性能有意识。这道题get和put的时间复杂度都是O(1)空间复杂度O(capacity)。另外如果面试官追问“多线程环境下这个实现不安全怎么办”可以回答加锁或者用ConcurrentHashMap配合同步锁以及在JDK中LinkedHashMap本身就支持accessOrdertrue的LRU近似实现这些延伸往往是加分点。3.2 最长无重复字符子串这道题是滑动窗口的经典应用题题目本身不复杂给定字符串s找出其中不含有重复字符的最长子串长度。滑动窗口的核心思路是维护一个区间 [left, right]区间内保证无重复字符。right指针不断右移扩展窗口每当遇到重复字符时就把left跳到重复字符上次出现位置的下一个位置。用一个HashMap记录每个字符最近一次出现的下标就能在O(1)时间内完成判断。public int lengthOfLongestSubstring(String s) { if (s null || s.length() 0) { return 0; } MapCharacter, Integer lastIndex new HashMap(); int maxLen 0; int left 0; for (int right 0; right s.length(); right) { char c s.charAt(right); if (lastIndex.containsKey(c)) { left Math.max(left, lastIndex.get(c) 1); } lastIndex.put(c, right); maxLen Math.max(maxLen, right - left 1); } return maxLen; }注意一个细节left更新时要取max而不是直接赋值为lastIndex.get(c) 1。原因是可能出现这样的情况当前left已经移动到较靠后的位置而某个字符上一次出现的位置在left之前那么不应该把left往回倒退。比如字符串“abba”遍历到第二个a时a上次出现的位置是0但当前left已经因为b变成了2所以left应保持2而不是变成1。复杂度方面每个字符最多被访问两次一次作为right一次被left跳过所以时间复杂度O(n)空间复杂度O(字符集大小)。这道题在直播业务里其实也有映射比如弹幕关键词过滤、敏感词检测都需要处理字符串滑动匹配的问题。笔试时能说出这个业务关联会让阅卷人觉得你不是单纯的刷题机器。3.3 多线程交替打印数字E卷的编程题里还有一道并发题要求两个线程交替打印1到100的奇数和偶数。题目看起来简单但能有效检验候选人是否真正理解Java并发的基本原语。一种常用解法是用synchronized配合wait/notify。核心思路是让两个线程共享同一个锁对象每个线程打印完后唤醒另一个线程并让出锁同时用条件判断是否轮到自己。public class AlternatePrint { private static final Object lock new Object(); private static int num 1; private static final int MAX 100; public static void main(String[] args) { Thread odd new Thread(() - { while (num MAX) { synchronized (lock) { if (num % 2 0) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (num MAX) { System.out.println(odd: num); num; lock.notifyAll(); } } } }); Thread even new Thread(() - { while (num MAX) { synchronized (lock) { if (num % 2 1) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (num MAX) { System.out.println(even: num); num; lock.notifyAll(); } } } }); odd.start(); even.start(); } }这段代码有几个容易被忽略的关键点。首先是wait方法的调用必须放在synchronized代码块里否则会抛IllegalMonitorStateException。其次wait/notify可能发生过早唤醒和虚假唤醒问题所以判断条件要用while循环而不是if。另外每次唤醒后对应线程要重新检查条件是否满足不能假设被唤醒就一定能执行。这道题也很容易切换到Semaphore、ReentrantLockCondition等方法实现属于一题多解的典型。笔试时如果时间充裕我建议把两种方案都写出来面试官问起来也更容易展开。这种题不要求你写出多优雅的代码但要求你展示对线程安全的敏感性共享变量num需要用synchronized保护条件判断要放在循环里这些细节比主流程本身更能体现真实水平。3.4 Top K问题与堆排序Top K问题在直播平台的高频场景里非常常见比如热门直播间排行、弹幕热度Top N。E卷中问了“长度为n的数组中找最大的K个数”并要求说明时间复杂度和空间复杂度。最直观的思路是排序后取前K个时间复杂度O(n log n)。但最优解是维护一个大小为K的最小堆遍历数组时如果堆未满就直接入堆否则把当前元素和堆顶比较如果比堆顶大就替换并调整堆。最终堆里保留的就是最大的K个数时间复杂度O(n log K)空间复杂度O(K)。当K远小于n时这个方案优势非常明显。public ListInteger topK(int[] nums, int k) { PriorityQueueInteger minHeap new PriorityQueue(k); for (int num : nums) { if (minHeap.size() k) { minHeap.offer(num); } else if (num minHeap.peek()) { minHeap.poll(); minHeap.offer(num); } } return new ArrayList(minHeap); }需要注意的处理细节PriorityQueue默认是小顶堆不需要额外传比较器但如果题目要求找最小的K个数就要改成大顶堆。还有一个容易出错的点是如果K大于数组长度直接返回整个数组排序结果代码里要加一个边界判断。如果面试官追问“K很大接近n怎么办”可以用快速选择算法期望时间复杂度O(n)但最坏O(n²)“数据量远超内存怎么办”可以用分治思想把数据分块后分别在每块内取Top K再合并。这些追问实际上考察的是你对数据规模和算法性能的敏感度准备这类题时建议把衍生问题也一起想过。4. 系统设计题直播间高并发弹幕系统4.1 需求分析与流量估算E卷最后一道系统设计题很务实设计一个支持高并发发送和低延迟展示的直播间弹幕系统。答题的第一步骤是明确需求和规模。一个热门直播间的在线人数可能达到几十万弹幕发送频率在直播高峰期可能达到每秒数万条。弹幕系统的核心指标是两条发送成功率要高、广播延迟要低通常在毫秒级。另外还需要支持弹幕的时效性过滤、敏感词拦截、历史弹幕回放等功能。流量估算要给出计算过程而不是直接抛结论。假设一个头部直播间50万在线用户其中5%的用户会同时发弹幕每秒产生2.5万条消息每条弹幕平均200字节每秒数据量约为5MB直播间平均在线时长1小时单场直播产生的弹幕总数可能过亿。这些数字从0到1推算出来是系统设计题里必不可少的第一步也是很多人容易忽略的。4.2 整体架构与核心模块弹幕系统整体可以分成四个核心模块接入层、消息队列层、弹幕处理服务、存储层。接入层使用WebSocket协议维持客户端与服务端的长连接相比HTTP轮询能大幅降低延迟和无效请求。客户端发送弹幕后请求首先到达接入网关网关负责鉴权、限流然后把消息写入消息队列。消息队列是整个系统的削峰关键。直播弹幕有明显的突发性比如主播中奖、比赛进球瞬间弹幕量可能在几秒内暴涨十倍。如果让后端服务直接面对这种流量峰值很容易被打垮。引入Kafka或RocketMQ之后弹幕服务从队列里拉取消息按自己的处理速度消费即使瞬间流量再大也只是队列积压不会把服务拖垮。这里给出选择消息队列的依据Kafka吞吐量高但RocketMQ在业务消息场景下的可靠性和事务支持更好。直播弹幕属于高吞吐、允许少量延迟的场景Kafka足够但如果需要更多业务特性RocketMQ会更合适。弹幕处理服务是业务逻辑的核心负责敏感词过滤、消息去重、房间维度聚合然后通过WebSocket服务推送给房间内的在线用户。这里有一个关键设计决策弹幕消息不直接推给所有用户而是按房间维度聚合后广播。每个房间对应一个消息通道弹幕服务在内存中维护房间与连接的映射表推送时只需要遍历该房间的连接列表。4.3 存储设计与高可用保障历史弹幕存储可以使用Redis和HBase配合。Redis用于保存最近一段时间的热弹幕比如最近10分钟的弹幕使用ZSet按时间戳排序方便按时间分页拉取。再往前的数据落入HBase或ClickHouse用于直播结束后的回放和数据分析。冷热数据分离的思路在直播场景里非常普遍因为绝大多数弹幕只在直播当下有访问价值过了热窗口访问频率就会断崖式下降。高可用方面需要考虑到单点故障的应对方案。弹幕服务需要多节点部署每个节点通过一致性哈希把房间映射到特定服务节点这样某个节点宕机时只影响部分房间同时可以快速把房间重新分配给其他节点。消息队列本身也要做多副本保证即使单台Broker宕机也不会丢消息。存储层Redis开启哨兵或集群模式HBase靠WAL日志和HDFS冗余机制保证数据安全。整个设计链路从客户端发送弹幕到推送给所有在线用户核心路径是客户端 → WebSocket接入层 → 消息队列 → 弹幕处理服务 → 房间内推送。所有环节都围绕“高性能”和“高可用”两个关键词展开。这道题如果答得好基本就能锁定面试官对你架构能力的认可。写系统设计题最重要的是展示思考过程而不是堆砌一堆高深的组件概念谁都会背能把组件串联成一条完整的数据流并说明每个环节的取舍才是真正拉开差距的地方。5. 笔试中的常见失分点与答题技巧实录5.1 常见失分点从这道E卷的阅卷情况和一些候选人反馈来看最常见的失分点集中在以下几个方面。第一基础概念只背书不结合场景。比如问“进程和线程区别”只说“进程是资源分配单位线程是CPU调度单位”没有结合高并发场景说明选型和切换开销分数就拿不高。第二编程题边界条件处理不到位。比如LRU缓存没有考虑容量为0最长无重复子串没有处理空字符串Top K没有处理K大于数组长度。这些边界值一定要在写完代码后专门检查一遍很多候选人主流程没问题就因为一个边界条件挂了。第三系统设计题忽略数据规模估算。一上来就画架构图却没有给出任何量级数据这种答案显得特别虚阅卷人无法判断你的设计是否合理。还有个很普遍的问题是代码风格差。笔试环境虽然不会像面试那样严格要求变量命名但命名随意、逻辑分支嵌套过深、没有提取方法的代码在阅卷时观感很差。养成写清晰代码的习惯不仅对笔试有帮助对后续的线上代码评审也是基本要求。5.2 实战答题技巧做完这套卷子我总结出几个可以复用的答题节奏。先花2到3分钟快速浏览全部题目按分数和难度排序优先做自己最有把握的题保证这些题分拿满再去啃硬骨头。编程题如果时间不够也要把核心思路和伪代码写出来让阅卷人知道你是有思路的只是没来得及完成和完全空着是两个概念。简答题不要只写结论一定要有“结论解释场景举例”三层结构。问“为什么B树适合做索引”先答结论减少磁盘I/O、支持范围查询再展开原理非叶子节点不存数据所以树矮最后结合一个小例子说明。这种答题模板看起来朴素但阅卷体验非常好信息密度也高。5.3 避开这些“吃力不讨好”的坑有几个我踩过或者看到别人踩过的坑写下来提醒大家。第一是不要在选择题上死磕太久一道题超过2分钟先跳过宁可最后回来蒙一个也不要影响后面大题的答题时间。第二是不要轻视“手写代码”的细节例如Java类要写完整的import或者至少标注清楚很多笔试环境是编译运行的漏import直接编译失败。第三是系统设计题不要画完图就结束图的旁边一定要配文字说明数据流向和关键设计理由纯图没有解释的文字复盘价值很低。还有一点很重要做算法题前先把题目里的约束条件读清楚N的范围和K的大小直接影响时间复杂度选型最好的解法不一定是最快写出来的解法但必须是在当前数据规模下最合理的那一个。6. 后续延伸与个人经验这套卷子做完之后我最大的感受是笔试的结果其实只是表象它真正检验的是你平时写代码、读源码、排查故障时积累起来的工程直觉。比如LRU缓存如果你在业务代码里用过Guava Cache或者Caffeine答题时自然能说出“生产环境一般不自己造轮子”这样的延伸再比如弹幕系统设计如果你维护过WebSocket服务提到心跳保活、断线重连、消息积压时就能讲出真实场景里的细节。所以给准备类似笔试的同学一个实在的建议刷题不要只刷“会做”要刷到“能把思路讲清楚能把衍生问题答上来”的程度。一道题做完了花十分钟想一想它的变体、边界、复杂度、真实应用场景这种学习方式的效率远高于盲目刷新题。时间线拉到现在直播平台的业务形态和技术栈早已迭代了很多轮弹幕协议从WebSocket升级到支持端到端加密缓存方案从Redis Cluster扩展到多级缓存加本地缓存但万变不离其宗的是底层那套“缓存 队列 无状态服务”的思维框架。只要把这套框架吃透无论笔试还是实际工作都能应对大部分挑战。根据我个人的体会真正拉开人和人差距的往往不是某个高深算法会不会写而是面对一个不熟悉的场景时你能不能快速把它拆解成自己熟悉的问题去解决。这套E卷本质上就是在测这件事。
返回列表