字节跳动后端开发实习生一面(TikTok 国际直播基础业务)
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 请先做一个自我介绍,重点讲一下后端相关经历。
- 三个项目中,你参与最深的是哪一个?原因是什么?
- 请讲一下项目整体架构,并按用户请求链路进行说明。
- 项目中的 12 个微服务是如何拆分的?拆分思路是什么?
- 项目为什么同时使用 OpenFeign 和 Dubbo?
- 请介绍一下项目中的秒杀系统。
- Redis 扣库存成功后,如果后续订单创建失败,你会如何处理?
- RabbitMQ 在项目中解决了什么问题?如何保证消息可靠性?
- RabbitMQ 出现大量消息积压时,处理方案是什么?
- 如果系统流量突增 10 倍,你会如何保障系统稳定?
- 请讲一下线程池的核心参数和执行流程。
- volatile 保证什么?为什么无法保证 i++ 线程安全?
- ConcurrentHashMap 线程安全的原理是什么?JDK 1.7 与 1.8 有哪些差异?
- Redis 为什么快?单线程模型与 IO 多路复用原理是什么?
- 缓存穿透、击穿、雪崩分别是什么?解决方案有哪些?
- MySQL 为什么选用 B+ 树?什么是聚簇索引、回表、覆盖索引?
- 线上 SQL 耗时从 20ms 突增到 2s,排查思路是什么?
- 请设计一个 TikTok 直播间点赞系统。
- 手撕:LeetCode 152 乘积最大子数组。
- 手撕:LeetCode 200 岛屿数量。
- 你有什么想要了解团队或岗位的问题?
《参考解析》
秒杀扣库存与订单创建失败的处理:核心是把「库存扣减」和「订单落库」拆成可对账的两步,并保证幂等。常见做法是 Redis 预扣(Lua 脚本一次性判断库存并原子扣减,脚本里同时做「一人一单」去重)→ 发消息到 MQ → 消费者创建订单、写库并扣真实库存。Redis 扣减成功但落库失败时,不能简单把库存加回去:如果消费者只是超时或结果未知,回补会造成超卖,正确顺序是先查订单状态(用「用户 ID + 商品 ID」或预生成的请求号做唯一键),确认没落库再回补,并且回补本身也要幂等。可靠性靠本地消息表 / 事务消息 + 定时对账兜底,库存与订单的最终一致性由对账任务收敛。面试时还要讲清:Lua 保证原子性、库存扣到 0 的边界、超卖与少卖的取舍、以及为什么不用数据库行锁直接扣(热点行锁竞争会打满连接)。
RabbitMQ 的可靠性与积压治理:可靠性要分段回答:生产者侧开确认机制(publisher confirm)+ 失败重试与本地消息表,避免消息没到 broker;broker 侧做队列与消息持久化、镜像 / 仲裁队列,防止节点宕机丢数据;消费者侧关闭自动 ack、业务处理成功后再手动 ack,失败按策略进重试队列或死信队列,并给重试次数上限,避免毒消息无限循环。整个链路要配一套「消息日志表 + 定时对账」来发现丢失。积压处理分两层:应急手段是增加消费者并发(注意顺序性与下游承载能力)、批量消费、临时扩容队列与分区、先转存到临时队列再离线消化;根因手段是压测找瓶颈(消费逻辑里的慢 SQL、同步 RPC、单条处理耗时)、优化消费路径、按分区键拆分热点队列。被追问「积压多久会撑爆」时,用队列容量、消息大小与磁盘余量算给他听。
线程池与并发容器:线程池要讲清七个参数与执行流程:核心线程数满之前新建核心线程,满了进队列,队列满了再扩到最大线程数,仍然满则执行拒绝策略(Abort、CallerRuns、Discard、DiscardOldest),线程空闲超过 keepAlive 后非核心线程回收,核心线程是否回收由 allowCoreThreadTimeOut 决定。参数怎么定要按任务类型说:CPU 密集接近核数,IO 密集按「核数 × (1 + 等待时间/计算时间)」估算,并且必须有界队列 + 明确的拒绝策略 + 命名与监控(活跃线程、队列长度、拒绝次数)。volatile 保证可见性与有序性(禁止指令重排),不保证原子性:i++ 是读-改-写三步,多线程交错就会丢更新,要么加锁、要么用 AtomicInteger 的 CAS。ConcurrentHashMap 1.7 用 Segment 分段锁,1.8 改成 CAS + synchronized 锁单个桶头节点,并引入红黑树处理长链表;扩容支持多线程协助迁移(sizeCtl、ForwardingNode),size 用 baseCount + CounterCell 分散热点。
Redis 与缓存问题:Redis 快的原因:纯内存、单线程处理命令避免锁竞争与上下文切换、epoll 多路复用让一个线程管住大量连接、以及紧凑编码与协议优化。IO 多路复用要讲清 select/poll/epoll 的差别(epoll 用红黑树注册 fd、就绪链表返回,O(1) 感知就绪,不受 fd 数量线性影响),Redis 的 6.0 多线程只是把网络读写与协议解析分摊到 IO 线程,命令执行仍是单线程。缓存穿透是查不存在的数据(布隆过滤器、空值缓存并设短 TTL、参数校验),缓存击穿是热点 key 过期瞬间打爆 DB(逻辑过期、互斥重建、永不过期 + 后台刷新),缓存雪崩是大量 key 同时失效或 Redis 整体挂掉(TTL 加随机抖动、多级缓存、熔断降级、集群高可用)。三者常被追问区别与组合使用,答的时候把「哪种场景用哪个」说清楚。
MySQL 索引与慢 SQL 排查:选 B+ 树是因为它是多叉平衡树,树高只有 3 到 4 层就能承载千万级数据,一次查询的磁盘 IO 次数少且可预期;非叶子节点只存键值,能塞进更多索引项;叶子节点用双向链表连起来,范围查询和排序友好。聚簇索引指 InnoDB 的主键索引,叶子节点直接存整行数据,因此按主键查最快;二级索引叶子存主键值,查非索引列要拿主键回聚簇索引再查一次,这叫回表;如果查询列都在索引里(覆盖索引)就免去回表,explain 的 Extra 会显示 Using index。线上 SQL 从 20ms 变成 2s 的排查顺序:先确认影响面与开始时间点(是否刚发版、数据量是否陡增)→ 看慢日志与 explain / explain analyze 的执行计划是否改变(索引失效、统计信息过期、优化器选错索引)→ 锁等待与事务(information_schema.innodb_trx、show processlist)→ 机器层面(CPU、IO、连接数、buffer pool 命中率)→ 外部依赖。常见根因是数据量越过临界点、隐式类型转换或函数导致索引失效、以及热点行锁竞争。
系统设计题与算法题:直播间点赞系统是典型的写多读多、允许最终一致、但要求低延迟的场景:客户端本地先合并 + 节流(比如按秒或按次批量上报),接入层用 Redis 做计数与去重(同一用户重复点赞幂等,可用 Set 或位图),计数分片避免单 key 热点,再异步落库(MQ 批量写、定时聚合),读侧优先返回缓存值并容忍秒级延迟;同时要有防刷(频率限制、风控)与降级(写失败时本地缓存补报)。面试官会追问「点赞数要不要强一致」「Redis 挂了怎么办」「怎么防重复点赞」,提前准备口径。算法题方面,152 乘积最大子数组的关键是同时维护以当前位置结尾的最大值和最小值(负负得正),并且用「要么接上前面、要么从当前重新开始」转移;200 岛屿数量用 DFS/BFS 把访问过的陆地沉掉,或用并查集;写完主动说复杂度、边界(空数组、全零、全负、网格越界)并自己跑一个用例,比写得快更加分。