公司动态

字节后端实习面经:Java并发、MySQL与Redis深度复盘

📅 2026/8/30 12:39:18
字节后端实习面经:Java并发、MySQL与Redis深度复盘
先说结论我拿到了这个后端实习的意向书。整个过程从投简历到收到offer大概是三周左右中间穿插了两轮技术面、一轮加面和一轮HR面整体节奏比我想象中紧凑但每一轮考察的内容也都非常扎实。这篇面经我拖了半个月才写完一方面是这段时间一直在忙学校的事情另一方面是想把这些题目再沉淀一下确保自己复盘出来的东西是准确的而不是面试结束后的自我感动。先交代一下背景。我是普通高校的硕士在读主语言是Java之前做过两个前后端分离的项目技术栈集中在Spring Boot、Vue、MySQL和Redis上。投的是字节跳动的后端开发实习岗。复盘之后我最大的感受是字节的面试对“深度”的要求特别高很多东西不是你会用就行而是必须能从原理层面讲清楚。这篇文章会按照面试流程来梳理每一轮问到的核心问题、当时的回答思路、以及面试官真正想考察的点我都会写清楚。如果你正在准备后端实习或者打算投字节可以参考一下。1. 投递阶段简历筛选与项目复盘准备1.1 简历上写什么最有竞争力后端实习简历的重点不是罗列技术名词而是让面试官一眼看出你做过什么、解决了什么问题。我简历上的项目一共写了两个一个是基于Spring Boot Vue的电商后台系统另一个是个人博客平台。前者偏向业务涉及权限管理、订单流程、缓存设计后者偏向工程化涉及前后端分离、接口设计、部署上线。当时我纠结了很久要不要把“若依框架”写进简历。后来想明白了框架本身不重要重要的是你在上面做了什么改造。如果你只是照着文档跑了一个demo那写上去反而是负加分。我当时对若依的权限模块做了二次开发把原本的RBAC模型扩展成了支持数据权限的版本这个点面试官几乎每次都会追问说明“基于框架做深度改造”比“用过某某框架”更有价值。简历上的每一个技术点都要提前准备两个层面的问题一是“你怎么用的”二是“为什么这么用”。比如你在简历上写了Redis就要准备好被问“Redis为什么快”“Redis的持久化机制是什么”“缓存穿透和击穿怎么解决”这些问题不是背答案而是要把原理串起来。我当时花了两天时间把简历里每个关键词都列了一张问题清单逐个过了一遍这个准备习惯帮了大忙。1.2 项目复盘的核心方法很多同学在项目复盘时容易犯一个毛病只准备“做了什么”不准备“遇到了什么问题、怎么排查的”。其实面试官更愿意听的是后者。一个好项目的讲述逻辑应该是项目背景 - 我负责的模块 - 关键技术难点 - 我是怎么解决的 - 最终效果。拿我那个电商后台举例我负责的是订单模块和商品模块。技术难点有三个一是订单号生成方案二是秒杀场景下的库存防超卖三是订单状态流转的一致性。每个难点我都准备了对应的解决方案比如订单号用的是雪花算法库存防超卖用Redis预扣减加数据库乐观锁兜底订单状态用状态机加消息队列驱动。这些方案不一定是最优解但每个都经过了压测和线上验证讲出来就有说服力。另外准备项目的时候一定要把“为什么不用另一种方案”也想清楚。例如库存防超卖为什么不用纯数据库悲观锁为什么不用乐观锁直接更新面试官这样一问就暴露了你到底是真的深思熟虑过还是仅仅看了别人的博客。我当时把Redis方案、数据库方案、消息队列方案都做过对比把各自的优缺点和适用场景写在笔记里最后面试的时候被问到心里不虚。2. 一面Java基础、MySQL与Redis的连环追问2.1 Java并发从synchronized到AQS一面开场是自我介绍然后直接进入Java并发。面试官第一个问题就是“你说说你理解的synchronized。”这题看起来基础其实暗藏很多可以延伸的点。我当时从三个层面回答一是synchronized的用法比如修饰实例方法、静态方法和代码块的区别二是它的实现原理基于Monitor监视器锁在字节码层面是monitorenter和monitorexit指令三是JDK 1.6之后的优化引入了偏向锁、轻量级锁、重量级锁的升级过程以及锁粗化、锁消除等优化手段。面试官听完之后追问了一句“锁升级的触发条件是什么偏向锁什么时候会撤销”这一问就考察你是不是真的理解了锁升级的细节。Java并发这一块面试官还问了ReentrantLock和synchronized的区别紧接着就引入了AQS。AQS是Java并发框架的基石你需要讲明白它内部维护了一个volatile的state变量和一个CLH队列以及独占模式和共享模式的区别。我当时把ReentrantLock的加锁流程说了一遍线程进来先CAS尝试获取state失败之后封装成Node放入队尾然后循环park和unpark。面试官还追问了公平锁和非公平锁的实现差异这个也是高频考点。建议准备Java并发时把知识串成一条线Thread - volatile - CAS - AQS - ReentrantLock/CountDownLatch/Semaphore。不要孤立地背某个类而是理解它们之间的关联。比如CAS是什么、能解决什么问题、有什么缺点ABA问题、自旋开销AQS是怎么利用CAS实现锁的。2.2 JVM内存区域与GC机制一面后半段转向JVM。面试官问“你了解JVM内存区域吗”这题几乎必考但我提醒一点不要只背区域名称要主动把“哪些区域是线程私有的哪些是共享的”也说清楚。这样能省去面试官很多引导时间也显得你有体系感。我当时的回答结构是程序计数器、虚拟机栈、本地方法栈是线程私有的堆和方法区是共享的。其中Java堆是GC的主要区域又分为新生代和老年代新生代里还分了Eden区和两个Survivor区。面试官听完后追问“对象的创建过程是怎样的什么时候会进入老年代”我回答了类加载检查、分配内存、初始化零值、设置对象头、执行init方法这几个步骤然后说了大对象直接进入老年代、长期存活的对象经过15次Minor GC后进入老年代这两个主要规则。GC这块面试官没有直接问“有哪些垃圾回收器”而是换了个问法“你说一下CMS和G1的适用场景和区别。”CMS是并发标记清除主要追求低停顿适合老年代回收G1是区域化分代收集通过维护优先列表来回收价值最大的Region适合大堆内存场景。这个问题的关键不只是背区别而是要能结合场景说出你“怎么选”。我当时补充了一句如果堆内存超过8GB优先考虑G1如果对停顿时间有严格限制可以调-XX:MaxGCPauseMillis参数。2.3 MySQL索引、事务与MVCCMySQL是后端面试的重头戏一面几乎三分之一的时间都在聊MySQL。开篇问题很常规“InnoDB索引为什么用B树而不是B树或红黑树”这个问题考察的其实是对数据结构和磁盘IO特性的理解。B树只有叶子节点存储数据非叶子节点可以存储更多索引项树的高度更低磁盘IO次数更少。而且B树的叶子节点用单向链表连接范围查询非常高效。接着面试官问到了聚簇索引和非聚簇索引的区别。这里有一个容易踩坑的点在InnoDB中聚簇索引就是主键索引叶子节点直接存整行数据非聚簇索引二级索引叶子节点存的是主键值所以二级索引查询需要回表。我当时补充了覆盖索引的概念说“如果查询的列都在索引中就不需要回表”面试官明显比较满意。事务隔离级别肯定逃不掉。面试官问“MySQL默认的隔离级别是什么它是怎么解决幻读的”默认是REPEATABLE READ可重复读。在RR级别下InnoDB通过MVCC实现快照读避免普通读的幻读问题对于当前读则通过Next-Key Lock记录锁间隙锁来防止幻读。这里我建议你把MVCC的三个核心概念理清楚隐藏字段DB_TRX_ID、DB_ROLL_PTR、undo log版本链、ReadView生成机制。面试官追问“RC和RR在生成ReadView时机上有什么区别”时我答了RC是每次快照读都生成新的ReadViewRR是事务第一次快照读时生成ReadView之后一直复用。这一下就体现出了对底层实现的理解。2.4 Redis缓存设计与分布式锁因为简历上写了Redis所以这轮也问了很多。面试官问“Redis为什么这么快”这个问题可以从单线程模型、基于内存、IO多路复用、高效的数据结构这几个方面回答。但要注意Redis 6.0之后引入了多线程IO所以“单线程”这个说法要说准确命令执行仍然是单线程的但是网络IO读写可以多线程。缓存三大问题是必考的穿透、击穿、雪崩。穿透指查询不存在的数据解决方案是布隆过滤器或缓存空对象击穿指热点key过期解决方式是互斥锁或逻辑过期雪崩指大量key同时过期解决方式是过期时间加随机值、多级缓存、集群部署。我当时还把“布隆过滤器为什么会有误判”解释了一遍一个key经过多个哈希函数映射到bit位上如果这些位都被置为1则可能存在但如果查询一个不存在的key它的哈希位可能刚好都被其他key置1就会出现误判。这个点加分明显。分布式锁肯定也会问。面试官问“Redis实现分布式锁要注意什么”核心是SETNX命令加过期时间的原子性不能分两步执行释放锁时要校验value是否是自己加的用Lua脚本保证原子性锁过期时间太短会导致业务还没执行完就自动释放所以要用续期机制比如Redisson的看门狗。面试官又追问“如果Redis主节点挂了锁还没同步到从节点怎么办”这就是RedLock要解决的问题。我在回答时没有过度吹捧RedLock而是说产线上一般不引入那么复杂的方案通常采用高可用部署加合理过期时间RedLock本身也有争议。面试官点头表示认可。3. 算法与手写代码字节面试的硬门槛3.1 高频算法题复盘字节的算法题难度在行业内是出了名的。我准备时把LeetCode热题HOT 100刷了两遍重点练了链表、二叉树、动态规划、滑动窗口这几类。一面实际考的是“三数之和”二面考的是“二叉树的层序遍历”变体加面考了一个“编辑距离”的优化版本。先说三数之和。这题的关键是排序后固定一个数再用双指针找另外两个数时间复杂度O(n²)。但面试官不是简单让你写完就好他会追问“怎么去重”“复杂度是多少”“还有没有更优解法”。我当时在去重逻辑上卡了一下后来是在固定第一个数时跳过重复值、双指针移动时也跳过重复值来解决的。这里建议你多练几遍把边界条件想清楚特别是[-1,0,1,2,-1,-4]这种带重复值的用例。二面的层序遍历变体是“按之字形打印二叉树”。其实就是层序遍历奇偶层反转或者用双端队列。写完之后面试官问“如果不用队列行不行”我愣了一下说可以用两个栈交替实现。这题主要是考察你对BFS的掌握是否灵活而不是死记模板。编辑距离那题考的是经典的动态规划。我一开始用二维数组写的面试官问能不能优化空间复杂度然后改成两个一维数组。状态转移就是if s1[i]s2[j]则dp[i][j]dp[i-1][j-1]否则取增、删、改三种操作的最小值加1。改写的时候需要注意“当前行的值依赖上一行和同行前一列”所以要从左到右遍历并且要用一个变量保存左上角的值。3.2 手写实现题LRU与并发编程除了算法题字节还喜欢让你手写一些工具类或实现某个机制。一面结束前面试官说“写一个LRU缓存吧”。LRU的标准做法是HashMap双向链表这里考察的无非是几个点get要O(1)、put要O(1)、容量满时要淘汰最久未使用的。我写的时候区分了“新插入”和“访问已存在节点”两种情况访问已有节点时要把节点移到链表头部插入新节点时如果容量已满删除尾部节点再插入头部。面试官在看完代码后问“为什么用双向链表而不是单向链表”我回答双向链表可以在O(1)时间内删除任意节点因为删除前驱节点需要知道它的前驱是谁。这个问题的答案其实很简单但是没写过的人容易卡壳。并发编程手写题问的是“多线程交替打印A、B、C”。这道题可以用synchronizedwait/notify、ReentrantLockCondition或者CompletableFuture来实现。我写的是ReentrantLockCondition版本核心思路是三个线程各自等待自己的Condition然后按顺序signal下一个线程。面试官追问“为什么这里Condition比wait/notify好用”我说Condition可以精确唤醒指定线程wait/notify只能随机唤醒一个或者唤醒全部无法实现精准的顺序控制。这个点建议背熟属于非常典型的考察点。4. 二面与加面项目深挖与系统设计4.1 项目细节追问接口设计、跨域与文件流二面开场面试官直接对着简历上的项目开始深挖。第一个问题是“你那个电商后台订单列表接口是怎么设计的分页是怎么做的”当时听了一愣因为平时写完接口很少去思考接口设计的合理性。我回答是用了PageHelper分页插件然后面试官问“底层是怎么实现的”我答了它在执行SQL之前拦截并生成count和limit查询。面试官又问“如果数据量大了分页深了怎么办”我就说可以用游标分页或者基于索引的延迟关联来优化。这个问题提醒我简历上写的每个技术细节都要准备好被追问到最底层。因为项目是前后端分离架构跨域问题也是必问的。面试官问“你项目里是怎么解决跨域的”我说配置了CORS在Spring Boot中通过CorsFilter全局配置允许的源、方法和请求头。面试官追问“浏览器为什么会跨域预检请求是怎么触发的”这是考察你对HTTP和浏览器机制的理解。预检就是当请求方法不是GET/POST、或者带了自定义请求头时浏览器会先发一个OPTIONS请求服务器返回允许的跨域策略后浏览器才发真正的请求。项目里还涉及导出Excel的功能接口返回的是Base64编码的文件流。面试官问“前端拿到Base64之后怎么下载”我回答了转Blob然后通过URL.createObjectURL生成临时链接再触发a标签的download属性。面试官接着问“如果是大文件Base64编码会导致体积膨胀1/3怎么看这个问题”这就要上升到“大文件导出用异步任务文件上传OSS 前端轮询下载”的架构方案了。我虽然项目里没有实现这个方案但能把思路讲出来也体现了知识点是活的。4.2 系统设计题秒杀场景拆解二面核心是一个系统设计题“如果让你设计一个秒杀系统你会怎么做”这题比较开放但很能够考察候选人有没有全局架构意识。我的思考过程是先分模块再讲核心难点。秒杀系统无非是前端防抖、网关限流、缓存预减库存、MQ削峰、数据库最终扣减这几层。我把重点放在“防止超卖”上Redis先做库存预扣减通过Lua脚本保证原子性扣减成功之后发送MQ消息异步下单数据库层面用UPDATE语句条件减库存比如UPDATE stock SET count count - 1 WHERE id ? AND count 0这样数据一致性才有保证。面试官追问“如果Redis里的库存和数据库里的库存不一致怎么办”我说通过定时任务对账将数据库的实际库存回写到Redis。又追问“消息队列挂了怎么办”我说可以引入本地消息表定时扫描重发或者把MQ做成高可用集群。这些问题没有标准答案关键是让面试官看到你在思考权衡而不是背一套方案。4.3 分布式、幂等与消息队列加面环节反而没怎么问项目更多是考察知识面的深度和广度。面试官问“你怎么理解分布式系统的CAP理论”这是个经典的送分题但我建议不要只背“一致性、可用性、分区容忍性三选二”这句话。要能结合具体场景说比如ZooKeeper是CP的Eureka是AP的MySQL主从同步是最终一致性。面试官听了之后继续追问“那BASE理论呢”这个就补充了基本可用、软状态、最终一致性这三个概念。幂等是后端开发非常核心的考点。面试官问“接口幂等你怎么做”我回答了三种场景一是数据库唯一索引约束适合防重二是Redis SETNX做分布式锁适合短时间的防重三是状态机幂等适合订单状态流转比如只有待支付才能改为已支付这样重复回调不会导致状态错乱。面试官追问“如果三个方案都用不了呢”我说可以引入全局ID让接口调用方每次请求带上唯一业务ID服务端用这个ID判重。消息队列相关问的是“怎么保证消息不丢失”和“怎么保证顺序消息”。不丢失要从三个环节分析生产者端使用confirm机制确认发送成功Broker端开启持久化刷盘策略设置为同步刷盘消费者端关闭自动ack改为手动ack处理成功之后再确认。顺序消息则要把相同key的消息发送到同一个队列消费者端用单线程消费或者用队列缓冲区把乱序消息重新排序。这块我答得中规中矩但至少逻辑清晰面试官没有再往深了问。5. HR面与时间线复盘别只顾技术综合素质同样关键5.1 字节HR面会问什么技术面全部通过之后就是HR面。字节的HR面不像一些公司那样只是走过场它会认真考察你的沟通能力、学习能力、团队协作能力和求职动机。常见的问题有为什么选择字节跳动为什么选择后端方向你遇到过最大的技术挑战是什么你平时是怎么学习的你能实习多久我当时的回答思路很简单选择字节是因为业务规模大到足够让你去思考高并发、高性能的问题这对一个刚入行的后端工程师来说是很难得的学习环境。为什么选后端则说自己更喜欢做一些支撑业务逻辑的事情能看到数据从服务端到前端完整链路跑通的样子非常有成就感。HR面的技巧就是真诚不要背模板。比如“你最大的缺点是什么”不要说自己“太追求完美”这种答案太假。我当时说的是“我有时候会陷进一个技术细节里出不来导致整体推进变慢”然后补充“后来我学会了先实现再优化先把主链路跑通再回来打磨细节”。看到没有说真话并不可怕关键是让它变成你成长故事的素材。5.2 整体时间线与我踩过的流程坑我这次的时间线是周三投简历 - 下周二收到约面邮件 - 第二周周四一面 - 第二周周五约二面 - 第三周周一加面 - 第三周周三HR面 - 第三周周五收到意向书邮件。整个流程里有两个小插曲。一是一面结束后我以为表现一般没有报太大希望结果第二天就约了二面说明一面面试官给了Positive的评价。二是加面本来是随机安排的原因是二面面试官认为我的技术深度还可以但希望在系统设计方面再验证一下。所以如果你遇到加面千万不要慌它不一定代表你被挂了也可能代表面试官对你有兴趣但还有疑虑这时候稳住心态好好表现就行。准备面试的时候一定要把自己的时间安排好。我建议技术面之间的等待时间不要浪费在焦虑上而是继续刷题和复盘之前的面试问题。我是每轮面完就把问题整理成文档标注哪些答得好、哪些有欠缺然后针对薄弱的点突击复习。这样即使后面加了轮次也能确保自己一直在进步。6. 避坑指南这些坑我替你踩过了6.1 项目经验“深挖无底洞”每句话都可能是考点我在一面二面里犯了同一个错误介绍项目时太喜欢用“高并发”“大数据量”这种词。结果面试官一追问“你的项目并发量到底多少、你是用什么做压测的、压测的QPS是多少”我只能支支吾吾说“我们在本地模拟的”。这种落差非常影响观感。正确的做法是宁可说小一点也要自圆其说。比如你可以说“项目是个人学习项目没有经过大规模线上验证但我做了JMeter压测单机QPS大约在500左右瓶颈出现在数据库连接池”。你会发现只要你给出的数字是经过实测的面试官反而不会继续刁难而是顺着你的思路往下问优化方案。另外一个教训是简历上写“精通”和“熟悉”一定要谨慎。如果只写过简单的CRUD不要写“精通Spring Boot”如果对GC调优没有实战经验不要写“熟悉JVM调优”。面试官很喜欢挑简历上最“厉害”的那个词去问你若答不上来整场面试的信任感都会降低。6.2 算法题不要只刷不做写在纸上和跑起来是两回事我在面试前刷题时总觉得“这题思路我懂看一眼题解就行”结果到了共享屏幕手写的时候经常在边界条件和语法细节上卡壳。比如三数之和我知道是排序双指针但写的时候忘了处理“当前值和前一个值相同”的情况导致结果里出现重复三元组。建议你在准备阶段一定要营造“真实面试”的感觉打开编辑器开一个计时器像面试一样把题目从读题到AC完整走一遍。遇到想不起来的题至少先写一个有解法的思路再补代码。字节面试的编辑器没有自动补全代码全靠手敲所以平时练习的时候也尽量少依赖IDE的智能提示。还要养成写注释的习惯不一定是给面试官看而是给自己顺思路。面试官经常会要求“你讲讲你这块代码是怎么想的”你看着注释反而更容易把逻辑讲清楚。6.3 不会的题不要硬编该承认就承认但别放弃思考面试中必然会有你不会的题。我二面时被问到“RedLock有没有缺陷”这题我了解过一些但谈不上深入。我当时的回答是“RedLock的设计思路是通过多个独立节点加锁来降低单点故障的影响但我个人没有在产线实践过只是了解它的争议。我理解它的核心问题是依赖系统时钟如果主节点发生GC停顿锁可能会提前失效。”这样回答的好处是承认不熟悉但展示了自己的思考框架和已知的边界。这里特别提醒如果完全不会也不要直接说“我不会”。试试从问题本身出发做推演。比如你不知道“聊聊Raft协议”至少可以从“它解决的是分布式一致性中的共识问题”开始再说“它和Paxos相比更易理解核心是Leader选举、日志复制和安全性”这类已知信息。面试官看重的不是你背了多少而是你遇到未知问题时能不能保持思路清晰。6.4 面试最后的反问环节别总问“没了吗”几乎每一轮面试结束面试官都会问“你有什么想问我的吗”。这个问题不准备的话很容易当场大脑一片空白然后说“没有了”。但面试官是会通过反问来判断你对岗位的兴趣和思考深度的。我每次会准备两个问题。一面技术面我问的是“目前团队后端的技术栈和业务方向是什么如果我入职最可能接手哪部分内容”二面加面我问的是“团队对实习生的培养路径是怎样的有没有固定的 mentor 机制”HR面我就问“实习期的考核标准大概是什么”。这些问题都比较实际既不会冒犯到面试官又能让你真的获取到有用信息。6.5 心态与状态管理面试是一场输出不是输入面字节前我是非常焦虑的总觉得自己准备得不够。但经历过之后我发现面试更像是一场“输出”你在有限时间内把你学过的知识、做过的事情、踩过的坑清晰地表达出来。如果只是抱着“再来补充知识”的心态去面试很容易慌乱如果你是抱着“把我知道的尽量展示出来”的心态状态会平稳很多。还有一个小建议面试前一天的晚上不要刷太多新题可以把之前整理过的面经、项目和算法模板快速过一遍。重点是把一些高频问题的答题框架固定下来比如“说说你对xxx的理解”这种题用“定义-原理-应用场景-优缺点”这个框架去答怎么都不至于跑偏。睡个好觉比熬夜多背十道题管用得多。写在最后回头看这段面试经历我最深的体会是面试结果固然重要但准备面试的过程本身就是在帮自己查漏补缺。我那些被追问到卡壳的问题那些没有考虑到的边界情况那些讲不清楚的原理都成了我这个阶段最宝贵的学习清单。字节的面试风格属于“层层深挖”型它考察的不是你背了多少八股而是你对一个知识点能不能从使用到原理、再到设计方案讲出一条完整的逻辑链。希望这份面经对你的准备有帮助。最后再分享一个小技巧每次面完不管感觉自己答得怎么样都趁热把问题敲进手机备忘录里复盘三天之后再回看你会有非常不一样的收获。