字节跳动后端实习一面
面试题目
系统架构
- controller / service / repository 三层架构各自的功能是什么?
- 各层之间如何交互?(依赖注入)
- 鉴权如何实现?(Spring Security,基于身份授权)
缓存与数据库
- 缓存和数据库的数据一致性如何解决?
- 为什么不先删缓存,而是先改数据库再删缓存?
- 缓存删除失败了怎么办?
- 缓存有 TTL 吗?大量 key 同时过期怎么办?(缓存雪崩)
MySQL
- 讲一下 MySQL 索引。(主键索引、唯一索引、B+ 树实现)
- 为什么用 B+ 树而不是其他数据结构?
操作系统
- 进程和线程的区别?线程之间哪些资源可以共享,哪些不能?
- 死锁的四个必要条件是什么?有哪些解决方式?
- CAS 和普通加锁各有什么优劣?
- 互斥锁和自旋锁的区别?
计算机网络
- TCP 和 UDP 的区别?
- TCP 为什么是三次握手、四次挥手?
算法手撕
- 求数组的最大连续子数组和(Leetcode 53,Kadane 算法)
脑筋急转弯
- 100 只老虎和 1 只羊,老虎吃了羊就变成羊,最后羊会不会被吃?
反问
- 面试官反馈:操作系统、数据库、网络、编程语言基础不够扎实;算法撕得快但讲不清楚思路。
参考解析
缓存一致性:先改数据库再删缓存
- 先删缓存再更新数据库:删除缓存后、数据库更新前,其他请求回写旧值到缓存,造成脏数据,且窗口期较长。
- 先更新数据库再删缓存:不一致窗口极短(仅数据库写完到缓存删除之间),是业界更推荐的方案(Cache-Aside 模式)。
- 缓存删除失败:引入重试机制,可借助消息队列(如 RocketMQ/Kafka)做异步重试,保证最终一致性;也可结合 binlog 订阅(Canal)触发补偿。
缓存雪崩
- 大量 key 同时过期导致请求全部打到数据库。
- 解决:① TTL 加随机偏移量,错开过期时间;② 双 key 策略(逻辑过期 + 后台异步刷新);③ 熔断降级;④ 互斥锁防止缓存击穿。
B+ 树索引优势
- 非叶子节点只存 key,单页可存更多索引项,树高更低,磁盘 IO 次数少。
- 叶子节点通过链表相连,支持高效范围查询。
- 相比 B 树,所有数据都在叶子节点,查询路径稳定,性能更可预测。
CAS vs 普通加锁
- CAS 优点:无锁,避免线程挂起/恢复的上下文切换开销,适合竞争不激烈的场景。
- CAS 缺点:① ABA 问题(可用版本号/戳解决,如
AtomicStampedReference);② 自旋空转浪费 CPU;③ 只能保证单个变量原子性。 - 互斥锁适合临界区较长或竞争激烈的场景;自旋锁适合临界区极短、不希望线程切换的场景(如内核中断处理)。
死锁
- 四个必要条件:互斥、持有并等待、不可剥夺、循环等待。
- 预防:破坏任一条件,如资源有序分配(破坏循环等待)。
- 避免:银行家算法,动态判断分配后是否安全。
- 检测+解除:允许死锁发生,定期检测资源图,发现环后抢占/终止进程。
TCP 三次握手 / 四次挥手
- 三次握手:确认双方收发能力均正常,同步双方 ISN,防止旧连接请求被误接受。两次不够(服务端无法确认客户端能收)。
- 四次挥手:TCP 全双工,两个方向需独立关闭。服务端收到 FIN 后可能还有数据要发,ACK 和 FIN 不能合并,故比握手多一次。
最大连续子数组和(Kadane 算法)
- 维护
cur(当前子数组和)和ans(全局最大值)。 - 遍历每个元素:
cur = max(num, cur + num),若cur加上当前元素还不如从当前元素重新开始,则抛弃前缀。 ans = max(ans, cur),时间复杂度 O(n),空间 O(1)。
脑筋急转弯:老虎与羊
- 核心逻辑:老虎吃羊变羊后,自己成为猎物。
- 奇偶分析:若只剩 1 只老虎,它会吃羊(无后顾之忧);2 只时都不吃(吃了变羊会被另一只吃);依此类推,老虎数量为奇数时羊会被吃,偶数时羊安全。
- 100 只(偶数)→ 羊不会被吃。