十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

小红书后端笔试复盘:Java岗必考知识点与系统设计实战

小红书后端笔试复盘:Java岗必考知识点与系统设计实战 2023年那场秋招我投了小红书的后端开发岗。笔试那天晚上两个半小时的题做下来我的感觉是这不像是一场考试更像是一次针对后端工程师知识体系的全面体检。如果你正准备投互联网大厂或热门公司的后端岗位或者还在纠结“后端开发到底需要学什么”这篇复盘应该能给你一些很实在的参考。我尽量把当时的题目类型、我的解题思路、以及考完之后的反思都写清楚有些地方会直接还原题目和代码方便你照着检验自己的水平。1. 为什么说这场笔试是后端岗的“体检报告”现在的校招笔试早就不是单纯考“会不会写代码”了。尤其是像小红书这种内容社区产品后端研发要面对的是高并发读写、复杂的推荐/搜索链路、海量内容存储、以及用户行为数据分析。笔试题目设计得相当有针对性基本是在用最短的时间试探你计算机基础扎不扎实、写代码的熟练度高不高、遇到设计问题有没有工程思维。我自己考下来的整体感受是它不会故意出偏题怪题但覆盖面很广。从数据结构与算法到计算机网络、操作系统、数据库、Redis、消息队列再到一道简单的系统设计题全部都有涉及。如果你只刷题不补基础或者只背八股不看工程分数都不会太好看。对于“后端开发需要学什么”这个几乎每个新手都会问的问题这场笔试本身就是一个标准答案。我的基本情况是双非硕研二主攻Java技术栈准备秋招大概有七个月。LeetCode刷了差不多三百道JavaGuide和《深入理解Java虚拟机》翻过两三遍Redis、MySQL、消息队列的底层原理自认为掌握得还可以。但考完之后我发现自己还是有几个明显的盲区后面会具体讲。提示小红书后端笔试通常在每年8月到9月分批开放今年投递的话时间线也差不多。建议提前在官网留意笔试通知它一般会通过邮件和短信发链接如果没有及时查看垃圾邮箱很可能错过。2. 开考前的信息战岗位JD里藏着考题范围在聊具体题目之前我想先说一个很多同学会忽略的事笔试范围其实在岗位JD里已经暗示了大半。我当时投递的是“后端开发工程师社区技术方向”JD里写得很清楚负责社区内容生产与消费链路的服务端研发要保证系统稳定性、可扩展性参与高性能分布式服务的建设。这意味着什么意味着高并发、缓存、消息队列、微服务治理这些点一定会考哪怕不直接出简答题也会藏在算法题和系统设计题里。我提前做了一件事把小红书后端相关的技术文章、开源项目、技术分享看了一遍。这家公司的技术栈里Java和Go都有使用社区场景下典型的业务是feed流、评论、点赞、关注。所以我在复习时专门加强了两块。一是feed流相关内容比如推拉结合模式、缓存淘汰策略二是社交关系链的存储设计当时想的如果考设计题大概率会朝这个方向出。事实证明这个准备太值了。后面那道系统设计题虽然不完全是feed流但核心思路是相通的。另外笔试环境也要提前适应。用的是牛客网的在线oj系统编程题需要自己处理输入输出。如果你平时在IDE里写习惯了自动导包、智能提示建议考前两三天专门去牛客网练几道需要自己写完整import和main的题不然真正考场上光是调试输入输出的格式都要浪费不少时间。3. 笔试全流程复盘四个板块的实际体验整场笔试时长150分钟题量不算少。我遇到的题型分布大致是这样的模块题型题量分值占比第一部分单选题20题约30%第二部分多选题5题约10%第三部分编程题3题约40%第四部分系统设计1题约20%选择题涉及的知识点相当杂Java集合类的底层结构、HashMap在JDK 8的改进、JVM内存区域划分、线程池参数含义、TCP四次挥手状态变化、Linux常用命令、MySQL索引失效场景、Redis持久化方式对比。这些题目单拎出来都不难但20道连在一起非常考验日常积累的扎实程度。多选题是丢分重灾区。原因是它的选项经常“看起来都对”但有一个是错的。比如有一道关于ConcurrentHashMap的题问你哪些说法正确里面有一个选项说“所有操作都用到synchronized”这个就是典型错误说法JDK 8已经改成了CAS加锁优化。如果你只看过八股标题没深入源码这种题很容易踩坑。编程题总共三道难度梯度拉得比较开。第一道是基础题差不多LeetCode中等偏下难度。第二道和第三道明显有区分度需要一定的算法功底。系统设计题放在最后要求画出架构图并写出关键模块的伪代码或数据结构设计。我本来以为时间会紧张但因为前面的选择题没有纠结太久最后剩了大概40分钟来做设计题还算从容。事后复盘这个时间分配是对的。系统设计题分值高拿满分的可能性低但只要能写出清晰的思路和合理的模块划分哪怕不完整也能拿到不错的分数。4. 两道值得反复咀嚼的算法题代码级拆解编程题是拉开差距的关键我挑两道印象最深的展开讲讲。4.1 第一道区间合并的变体考察边界处理能力题目大意是给定若干个形如[start, end)的区间表示用户在线时段现在需要统计所有区间合并后的总覆盖时长。输入是二维数组输出是一个整数。如果区间没有重叠总覆盖时长直接相加即可但题目明确说区间的两端都是整数且可能互相包含。这道题的核心思路就是先按起点排序再线性扫描合并属于贪心思想。但真正容易出错的地方在于边界判断——题目里用的是左闭右开区间如果上一个区间的end大于等于当前区间的start就说明有重叠需要合并。很多人在写的时候会写成导致相邻但不重叠的区间被错误合并。我当时的解法是这样的import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); int[][] intervals new int[n][2]; for (int i 0; i n; i) { intervals[i][0] sc.nextInt(); intervals[i][1] sc.nextInt(); } Arrays.sort(intervals, (a, b) - a[0] ! b[0] ? a[0] - b[0] : b[1] - a[1]); long covered 0; int curStart intervals[0][0]; int curEnd intervals[0][1]; for (int i 1; i n; i) { int s intervals[i][0]; int e intervals[i][1]; if (s curEnd) { // 重叠注意这里 curEnd Math.max(curEnd, e); } else { covered (curEnd - curStart); curStart s; curEnd e; } } covered (curEnd - curStart); System.out.println(covered); } }这道题本身不算难但它很好地检验了一个后端工程师的基本功边界条件处理能力。在日常开发里这种“差一个等号就出事故”的情况实在太常见了。另外用long接收结果而不是int也是考察点之一因为总覆盖时长完全可能超过int范围。4.2 第二道带限制的最短路径Dijkstra堆优化这道题我记得很清楚因为我在考场上犹豫了很久才找到正确的解法路径。题目大意是一个n x m的网格每个格子有通过成本从左上角出发只能向右或向下移动要求在路径上“最多经过k个高成本格子”的前提下找到最小总成本路径。这里高成本格子有一个阈值超过阈值就算。第一反应可能是动态规划但仔细想想会发现“最多经过k个高成本格子”这个限制给普通DP增加了一个维度。瞬间我就意识到这不是一道普通网格题而是需要将状态扩展为dp[x][y][c]其中c表示已经经过的高成本格子数量。由于只能向右和向下走没有回头路所以这个DP是可行的不需要Dijkstra。但如果这道题改为“可以上下左右走”那就必须转成带约束的最短路问题解法有两种一是拆点建图把每个格子拆成k1个状态节点跑Dijkstra堆优化二是在Dijkstra的状态里同时记录成本和高成本格子数用小顶堆按总成本扩展。我当时写的版本偏向Dijkstra堆优化因为题目如果改一下细节这样写更稳。核心代码思路// 状态[x, y, highCostCount] - 最小成本 // 使用优先队列每次弹出成本最小的状态尝试向四个方向扩展 // 如果下一个格子是高成本格子则highCostCount1并判断是否超过k // 超过k则不能走否则更新dist数组 // 复杂度O(n*m*k*log(n*m*k))这道题的意义在于它考察的并不是堆优化本身有多难而是你能不能快速判断出“普通二维DP不适用”以及“如何把限制条件变成状态的一部分”。这在实际后端开发里对应的是接口的降级与限流策略设计——在有限资源里找到一条满足约束的最优路径思路完全同构。5. 系统设计题详解个人记账服务的核心模块设计系统设计题是这场笔试的重头戏分值20%我觉得也是最有意思的一道。题目不涉及小红书的具体业务反而给了一个很通用的场景设计一个支持多端同步的个人记账服务要求包含用户认证、账单记录、分类管理、统计分析、预算管理这几个功能模块。看到这个题目我愣了一下因为“个人记账”看起来似乎和社交平台没有关系但仔细想想就明白了——这是一个非常典型的数据密集型业务系统涵盖了用户系统、核心交易数据流、报表聚合、定时任务、消息通知等多个后端核心组件。5.1 用户认证模块的设计思路用户认证部分是整道题的基本盘我选择了JWT方案而不是Session方案。原因很简单支持多端同步的移动应用使用无状态认证机制更利于水平扩展用户每台设备持有一个令牌服务端不需要维护会话状态。设计时我画了三个核心表用户主表、认证令牌表、第三方绑定表。用户主表存用户名、加密密码哈希我选择bcrypt而不是MD5因为MD5防暴力破解能力太弱、状态字段。令牌表存userId、jwt、过期时间。第三方绑定表用于支持微信或手机号一键登录。这里有一个我在实际项目中踩过的坑想提醒大家JWT虽然无状态但一旦泄露就无法主动失效。正规做法是引入一个黑名单机制把注销用户的jti存起来每次请求时校验。我在设计时特意加了这个模块可能也是加分项。5.2 账单记录与分类管理的数据表设计账单记录是整个系统的核心数据设计是否合理直接决定了后续统计分析的性能。我设计的核心表是CREATE TABLE bill_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, category_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, bill_type TINYINT NOT NULL COMMENT 0-支出 1-收入, occur_time DATETIME NOT NULL, remark VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_time (user_id, occur_time), INDEX idx_user_category (user_id, category_id) );bill_type单独用一个字段来表示收支类型而不是靠金额正负来判断这样查询统计时效率更高也不容易出错。索引的设计上我选择了(user_id, occur_time)作为联合索引因为统计分析类查询基本都是“查某个用户某个时间段的账单”这个索引能直接覆盖。分类管理这块我预置了一套默认分类餐饮、交通、购物、娱乐、工资等允许用户自定义。表结构是父子层级parent_id为0表示一级分类。这样设计是为了统计报表里能够从大类聚合到小类灵活度更高。5.3 统计分析和预算管理的关键实现统计分析的核心是聚合查询性能。我采用的方案是实时查询 预聚合缓存双层架构。当天或近三天的统计数据直接查数据库历史统计数据从Redis或ClickHouse类存储中读取预聚合结果。对于月度报表这类高频访问的数据设置一个定时任务每天凌晨计算好前一天的数据存入缓存。预算管理的核心是“超额提醒”。我设计了一张预算表存预算金额、周期类型月/周、提醒阈值然后通过两个途径检查是否超额用户查询页面时实时计算以及定时任务扫描即将超额的活跃用户推送提醒。定时推送适合用延迟队列或消息队列实现避免大量用户同一时刻触发推送造成系统压力。关于“小程序云开发是不是不用写后端代码”这个常见疑问这里也顺便说一下。个人记账这类业务确实可以依托云开发快速做出来尤其是学生作品或MVP阶段效率和成本都很有优势。但如果业务的统计分析越来越复杂、访问量增长、需要多端同步和精细化权限管理还是需要独立后端服务的。云开发的后端逻辑虽然不用自己运维服务器但业务本身的核心复杂度并不会凭空消失只是从“写代码”变成了“配置云函数和数据库规则”。6. Java核心考点集合、JVM与并发编程回到选择题部分Java考察点非常集中。我回忆出的高频考点基本没有偏离这几个方向集合类源码、JVM内存结构与垃圾回收、Java并发工具的使用与原理。这些东西在“后端开发面试题”里堪称常青树小红书笔试也完全不避讳考纲反而考得很细。6.1 HashMap在JDK 8之后的变化字节跳动、美团、小红书这类公司对HashMap的钟爱程度可以说到了“逢面必考”的程度。笔试里有一道直接问“HashMap在JDK 8做了哪些优化”选项包括引入红黑树解决哈希冲突后的链表过长问题、扩容时保留原索引或原索引加旧容量的规律、头插法改为尾插法避免死循环、以及引入CAS保证线程安全。前三个都是对的第四个是故意设置的错误选项——CAS是ConcurrentHashMap的优化手段不是HashMap的。而且JDK 8的HashMap仍然不是线程安全的多线程put可能导致数据不一致或死循环。这个坑我在复习时就注意到了所以判断得很果断。如果要在源码层面理解要点是链表长度超过8且数组容量超过64时链表转红黑树扩容时每个节点要么留在原索引要么移动到原索引旧容量的位置这样设计是为了利用hash oldCap是否为0来判断省去了重新计算索引的代价。6.2 JVM内存区域与垃圾回收的实操理解JVM相关的题目我觉得不算难但需要真正理解概念而不是死记。它考了这样一道题以下哪些区域在Java 8之后不再单独划分答案是永久代PermGen它被元空间Metaspace取代。但如果你只背结论可能不知道为什么换——因为永久代的大小难以控制容易触发OutOfMemoryError元空间则使用本地内存默认情况下不会被上限限制。垃圾回收方面考察了CMS和G1的适用场景区别。我印象中选项里有“G1可以通过-XX:MaxGCPauseMillis设置目标停顿时间”这句话是对的。CMS则侧重降低停顿时间但会产生内存碎片。回答这类问题时我从“内存布局不同、回收步骤不同、适用场景不同”三个维度展开能把概念讲清楚。对于正在准备面试的同学有一个实操建议不要只看JVM理论务必在本机用jstat和jmap观察一次JVM实际运行时的堆内存变化和GC日志。没有实际操作经验面试时一旦被追问细节就很容易露馅。6.3 并发编程里的Bug案例并发题是我自觉答得最稳的部分。考了一道关于ThreadPoolExecutor的题问你以下哪种情况会触发拒绝策略答案是“任务队列满了且线程数已经达到maximumPoolSize”。核心线程数、最大线程数、阻塞队列三者之间的关系是理解线程池的关键。我准备时构建过一个自己的线程池配置用来处理异步的账单导入任务ThreadPoolExecutor executor new ThreadPoolExecutor( 4, // corePoolSize 16, // maximumPoolSize 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(200), // 队列容量 new ThreadPoolExecutor.CallerRunsPolicy() );我的理解是核心线程撑不住时任务先进入队列排队队列也满了才创建额外线程直到最大线程数再满就触发拒绝策略。CallerRunsPolicy这条策略的意思是让提交任务的线程自己去执行被拒绝的任务对账单导入这种对实时性要求不高的场景来说既不会丢任务又能自然限流。我还特别注意了ThreadLocal在跨线程传递场景下的坑。笔试里也考到了ThreadLocal内存泄漏问题核心是ThreadLocalMap的Key是弱引用但如果Value不主动remove()在线程池复用的场景下Value会一直残留在线程上。轻则串数据重则内存泄漏。我在实际项目里处理用户登录信息时就用过一次当时线上出现偶发的“用户A看到了用户B的账本数据”最后查明就是ThreadLocal没清理。这类Bug特别隐蔽强烈建议做后端开发的同学都把“用完必remove”这四个字刻在脑子里。7. 计算机网络、数据库与Redis的核心考察7.1 TCP连接管理与HTTP协议细节计算机网络考卷里的题量不是最多的但都很典型。TCP四次挥手的题肯定会有一道考法很直接TIME_WAIT状态出现在主动关闭连接的那一端等待时间为2MSL。为什么需要2MSL因为要确保最后一个ACK能被对方收到防止旧连接的数据包干扰新连接。HTTP这块有一道关于HTTP和HTTPS区别的多选题几乎算送分题但选项里稍不留神就会被带偏。HTTP默认端口80HTTPS默认端口443HTTPS在TCP和HTTP之间加了一层TLS/SSL加密HTTPS首次连接会更慢因为多了TLS握手。这些都对。但有一个选项说“HTTPS的证书是客户端生成的”这就是典型错误选项证书需要由受信任的CA机构签发客户端不自己生成证书。7.2 MySQL索引失效场景、事务隔离级别与MVCC数据库的考察非常落地几乎每道题都能在真实开发中找到对应场景。有一道关于索引失效的题问在WHERE条件里对索引列使用LIKE %abc%这个索引是否生效答案是失效。记住一个简单原则左前缀模糊匹配才可能走索引前后都带百分号一定全表扫描。但千万注意LIKE abc%是可以走索引的。事务隔离级别那道题我印象很深。它考的是MySQL默认的隔离级别是什么以及在这种级别下能不能解决幻读。答案是REPEATABLE READ可重复读是MySQL InnoDB的默认隔离级别。在这个级别下MySQL通过MVCC 间隙锁机制可以在大部分场景下阻止幻读的发生但并不是使用标准的SERIALIZABLE级别的锁实现。还有一道关于慢SQL优化的题选项里给出了几个优化方案用EXPLAIN查看执行计划、避免SELECT *、对大表字段建索引、在索引列上进行函数运算。最后一个是错误选项因为对索引列进行函数运算会导致索引失效。这种题就是为了提醒你优化SQL的前提是理解索引的底层结构而不是死记“不要用函数”这种结论。7.3 Redis的缓存穿透、击穿、雪崩Redis相关题目小红书笔试和绝大多数中大型互联网公司后端岗几乎是同一个套路绕不开缓存穿透、缓存击穿、缓存雪崩这三个问题。笔试在选择题里考了一个场景判断实际面试则很可能会要求你描述解决方案。我的理解是这样的缓存穿透查询一个不存在的数据缓存里没有数据库里也没有请求直接打到数据库。解决思路是缓存空值并设置较短过期时间或者用布隆过滤器预判数据是否存在。缓存击穿某个热点key在过期瞬间大量并发请求全部打到数据库。解决思路是使用互斥锁如Redis的SETNX让只有一个请求去加载数据其余请求等待。缓存雪崩大量key在同一时间段集体过期或者Redis实例宕机导致请求全部打到数据库。解决思路是过期时间加随机抖动或者采用高可用架构如Cluster模式以及服务降级和限流方案。如果让我出一个加分项的小贴士那我会说不要把缓存当数据库用。个人记账服务里如果实现统计报表缓存缓存应该始终保持可丢失、可重建的原则Redis只是一个加速层而不是持久化真理的来源。8. 答题节奏与时间分配的真实教训考完复盘时间分配我发现自己有做得好的地方也有处理得不够聪明的地方。选择题部分我给自己定的上限是45分钟实际用了35分钟。有些题犹豫了几十秒之后果断按第一感觉选了然后标记为“待复查”不过最后没有时间复查这其实是明智的——纠结不但浪费时间而且通常改答案会改错。编程题部分我花的时间最久差不多70分钟。第一道区间合并我用了10分钟左右第二道最短路径我用了25分钟第三道题是一道关于字符串处理和哈希计数的题难度介于两者之间我用了20分钟写完并通过了示例用例。剩下15分钟用来调试第二道题的一个边界情况但最终没能完全通过所有隐藏测试用例。系统设计题我给自己预留了35分钟实际用了40分钟。我选择先用文字描述整体架构再画核心数据表然后补充一个关键接口的时序描述最后写了预算过期提醒的代码逻辑。因为是在线笔试没法真正画图我用文字和缩进代替了架构图考试系统里通常有“文本绘制”的方式用-来表示数据流向。整个时间分配比例大概是选择题23%编程题47%系统设计题27%留2%给检查。这个比例明显是偏重编程题的我个人的建议是如果你的算法基础信心不足可以稍微压缩选择题的时间——说白了选择题猜对的概率有25%但编程题不会写就是零分性价比完全不同。9. 笔试之后我重新调整了后端开发学习路线这场笔试考完一周后我收到了进入面试环节的通知。但比这更重要的是这次笔试成了我秋招准备过程中的一次“认知刷新”。我把自己当时的薄弱点列了个清单也重新梳理了“后端开发学习路线”的优先级。如果你也正在准备后端岗位可以参考一下这个优先序。第一优先算法与数据结构持续刷题保持手感。这是笔试的敲门砖没有什么捷径。我用的是“按标签刷题定期周赛模拟”的方式效果比盲目刷题好很多。第二优先数据库知识不能停留在会写SQL的层面。索引底层数据结构、事务隔离机制、MVCC、锁、慢查询优化每一个都要能讲出原理而且最好能结合自己的项目说。我在个人记账服务项目里就做过一次慢查询优化用EXPLAIN发现一个统计接口全表扫描加联合索引之后耗时从800ms降到了30ms这种真实案例比背一百道题都有说服力。第三优先Java基石的性价比极高。集合源码、JVM、并发编程是Java后端笔试面试的重头戏。不要只看博客要看源码记结论没有用面试官一追问就露馅。第四优先Redis和消息队列要懂应用场景。不要只背数据结构和持久化方式更要想清楚什么样的业务需要缓存、缓存会带来哪些新的问题、消息队列怎么保证不丢消息。小红书这种体量的公司非常看重这个因为它的系统天然是分布式的遇到的很多问题都和这些中间件相关。第五优先一定要有一个拿得出手的实战项目。不用多炫酷主流的例如个人记账、博客系统、短链接服务都可以关键在于把用到的技术点讲透。我那个记账服务虽然称不上大项目但把用户认证、数据库设计、缓存优化、定时任务全部串起来了面试聊到项目时明显更有底气。这份路线图并不是说一定要按顺序学完才去投简历而是想告诉你后端开发的知识体系是网状的而不是线性的。很多新手容易陷入“我还没学完所以不敢投”的误区。实际上校招笔试的目的不是筛选出“学完的人”而是筛选出“有潜力且基础扎实的人”。只要你算法手感在线、基础概念扎实、项目里有真实的思考就可以尽早投递不要等。最后分享一个考完笔试后很有用的动作复盘。趁记忆还没消退立刻打开备忘录把所有能回忆出的题目按知识点分类记录下来。选择题里模糊的概念、编程题中没通过的用例、系统设计题里没想清楚的模块全部记下来。这不仅是秋招的复习资料更是你接下来学习路线调整的依据。我直到今天整理这篇复盘的时候还在从当时的记录里翻找细节可见这一步有多重要。
返回列表