字节的后端实习二面,八股盛宴!
新的一周,祝你开心!
好久没分享面经了,今天来个大的---字节的后端实习二面,简直就是八股盛宴,问的太多太全面了。


面经详解
1. 数据库的隔离级别有哪些?
数据库事务隔离级别主要分为四种,从低到高依次为:
读未提交(Read Uncommitted)
允许事务读取其他事务未提交的数据,可能导致脏读、不可重复读和幻读。
读已提交(Read Committed)
只能读取其他事务已提交的数据,避免脏读,但可能出现不可重复读和幻读。
是 Oracle 和 SQL Server 的默认级别。
可重复读(Repeatable Read)
确保同一事务内多次读取同一数据结果一致,避免脏读和不可重复读,但可能发生幻读。
是 MySQL InnoDB 的默认级别。
串行化(Serializable)
最高隔离级别,强制事务串行执行,避免所有并发问题(脏读、不可重复读、幻读),但性能开销最大。
对比总结:
2. 可重复读隔离级别是通过什么机制来实现的?
可重复读隔离级别通过 MVCC(多版本并发控制) 和 锁机制 实现:
MVCC:
Read View:事务启动时创建数据快照,后续所有读操作基于此快照,确保数据一致性。
Undo Log:存储数据的历史版本。事务读取数据时,通过版本链找到符合快照可见性的版本。
锁机制:
行级锁:更新数据时加锁,防止其他事务修改当前行。
间隙锁(Gap Lock):锁定索引范围内的间隙,防止其他事务插入新数据(解决幻读)。
示例:事务 A 启动后读取账户余额为 1000 元,即使事务 B 修改余额并提交,事务 A 再次读取仍为 1000 元。
3. 说明 MVCC(多版本并发控制)的具体实现流程
MVCC 的核心是通过数据多版本实现读写并发控制,流程如下:
隐藏字段:
每行数据包含
DB_TRX_ID
(最近修改的事务 ID)和DB_ROLL_PTR
(指向 Undo Log 的回滚指针)。Read View 生成:
事务首次读操作时创建 Read View,记录当前活跃事务 ID 列表,用于判断数据版本的可见性。
数据读取流程:
根据数据行的 DB_TRX_ID 与 Read View 对比:
若
DB_TRX_ID
小于 Read View 中最小活跃 ID,说明数据已提交,可见。若
DB_TRX_ID
在活跃事务 ID 列表内,说明数据未提交,不可见,需沿DB_ROLL_PTR
在 Undo Log 中查找旧版本。写操作流程:
更新数据时,将旧数据拷贝到 Undo Log,新数据覆盖原行并更新
DB_TRX_ID
。版本清理:
后台线程定期清理无活跃事务引用的旧版本数据(Purge 机制)。
案例:事务 A(ID=101)更新数据时,事务 B(ID=102)通过 Read View 读取 Undo Log 中的旧版本,避免脏读。
4. 不可重复读与幻读的区别是什么?
5. mysql 的日志都有哪些,用途?
6. redo log 的实现?
Redo Log 通过 Write-Ahead Logging(WAL) 机制实现事务持久性:
物理结构:
固定大小的循环文件(如
ib_logfile0
,ib_logfile1
),写满后覆盖旧数据。Write Pos:当前写入位置;Checkpoint:已刷盘的数据位置。
写入流程:
事务修改数据前,先写 Redo Log Buffer(内存)→ 提交时刷盘到 Redo Log 文件。
刷盘策略由
崩溃恢复:
重启后根据 Redo Log 中的 LSN(日志序列号)恢复未刷盘的数据。
优化点:顺序 I/O 写日志(性能高于随机写数据页)。
7. binlog 的格式有哪几种?
8. Redis 中的漏桶算法
漏桶算法通过固定速率处理请求,控制流量:
原理:
请求如水流入桶(容量固定),桶底以恒定速率漏水(处理请求)。桶满则拒绝新请求。
Redis 实现:
数据结构:用
INCR
统计请求数,EXPIRE
重置桶计数。关键参数:
capacity
:桶容量(最大请求数)leak_rate
:漏水速率(每秒处理数)。桶满策略:
直接拒绝:返回错误(简单但体验差)。
排队等待:请求入队列,桶空闲时处理(需额外维护队列)。
9. redis 的持久化机制
Redis 提供两种持久化方式:
RDB(快照):
原理:定时 fork 子进程生成内存数据快照(二进制文件
dump.rdb
)。优点:恢复速度快,文件紧凑适合备份。
缺点:可能丢失最后一次快照后的数据(默认间隔 5 分钟)。
AOF(追加文件):
原理:记录每个写操作命令(文本文件),重启时重放命令恢复数据。
刷盘策略:
always
:每次写操作刷盘(安全但性能差)everysec
:每秒刷盘(默认,最多丢 1 秒数据)。混合持久化(Redis 4.0+):
AOF 文件包含 RDB 头 + 增量操作,兼顾恢复速度和数据安全。
10. redis 的数据类型
11. 讲一下 Go 语言的垃圾回收机制
Go 语言的垃圾回收(GC)采用并发标记-清除算法,结合三色标记法和写屏障技术,旨在减少停顿时间(STW)并支持高并发场景。其核心机制如下:
三色标记法:
白色对象:未被访问的对象(待回收)。
灰色对象:已访问但引用的对象未完全扫描。
黑色对象:已访问且所有引用已扫描(存活对象)。
标记阶段从根对象(全局变量、栈变量等)开始,递归遍历可达对象并标记为灰色,逐步转为黑色。
并发与并行执行:
并发标记:GC 线程与用户程序并发运行,通过写屏障(Write Barrier)记录对象引用变化,防止漏标。
并行清除:标记完成后,清理白色对象的内存(与程序并行)。
触发条件:
内存阈值:当堆内存达到上次 GC 后存活对象的 2 倍(默认
GOGC=100%
),自动触发。手动触发:调用
runtime.GC()
。系统内存压力:操作系统要求释放内存时。
分代收集优化:
对象分为新生代(频繁回收)和老生代(较少回收),优先扫描新生代,减少全局遍历开销。
性能优化建议:
减少堆分配:复用对象(如
sync.Pool
)。避免小对象频繁分配:使用数组替代切片或预分配内存。
GC 对程序的影响:最大停顿时间通常控制在 10ms 以内,但高频分配仍可能导致延迟升高。
12. slice 的扩容机制
Go 中slice
的扩容通过append
触发,底层调用runtime.growslice
函数,策略兼顾效率与内存利用率:
扩容时机:
当
len(slice) + 新增元素数 > cap(slice)
时触发扩容。扩容策略(Go 1.18+):
容量 < 256:双倍扩容(
newcap = oldcap * 2
)。容量 ≥ 256:按公式
newcap = oldcap + (oldcap + 3*256) / 4
逐步增加(约 1.25 倍),避免过度浪费。特殊处理:若扩容后仍不足,直接采用
所需容量
。内存对齐:
计算
newcap
后,根据元素大小向上取整到内存页大小(如int
类型按 8 字节对齐)。数据迁移:
分配新内存空间,拷贝旧数据到新数组,原数组由 GC 回收。
示例:
优化建议:预分配容量(make([]int, 0, 100)
)以减少扩容开销。
13. Go 协程调度模型(GMP)是什么?
GMP 是 Go 语言实现高并发的核心调度模型,包含三个组件:
G(Goroutine):轻量级协程,初始栈 2KB,由 Go 运行时管理。
M(Machine):操作系统线程(内核线程),负责执行 G 的代码。
P(Processor):逻辑处理器,维护本地 G 队列(
runq
),数量默认为 CPU 核心数(可通过GOMAXPROCS
调整)。
工作流程:
G 创建:
go func()
将 G 加入当前 P 的本地队列;若队列满,则转移一半 G 到全局队列。M 绑定 P:M 需绑定 P 才能执行 G,从 P 的本地队列获取 G;若本地队列空,则:
从全局队列获取一批 G。
从其他 P 的队列窃取(Work Stealing) 一半 G。
阻塞处理:
系统调用:M 解绑 P,P 被其他 M 接管继续执行 G。
恢复后:M 尝试绑定空闲 P 执行原 G,否则 G 加入全局队列,M 休眠。
优势:
高并发:百万级 Goroutine 可被少量 M 调度。
低延迟:协作式调度 + 抢占机制(基于信号)减少长任务阻塞。
14. Channel 的底层实现和阻塞机制是怎样的?
Channel 的底层结构为hchan
(源码定义),核心机制如下:
阻塞条件:
发送阻塞:无缓冲 Channel 无接收者,或缓冲 Channel 缓冲区满(
qcount == dataqsiz
)。接收阻塞:无缓冲 Channel 无发送者,或缓冲 Channel 缓冲区空(
qcount == 0
)。阻塞处理:
阻塞的 G 被封装为
sudog
加入sendq
或recvq
队列,M 切出执行其他 G。当条件满足时(如新数据到达),唤醒队列中第一个等待的 G(FIFO 顺序)。
直传优化:
若发送时
recvq
非空,数据直接拷贝给等待的接收者,避免经过缓冲区。
示例:
15. defer
关键字的执行顺序
defer
延迟执行函数,规则如下:
执行顺序:
多个
defer
按后进先出(LIFO) 顺序执行(类似栈)。
参数求值时机:
defer
的参数在声明时立即求值,而非执行时。
执行时机:
在函数返回前执行(包括
return
赋值后、函数结束前),即使发生panic
也会执行。常见陷阱:
循环中的 defer:若在循环内使用
defer
,可能因延迟执行导致资源未及时释放(如文件句柄)。建议改用匿名函数包裹。返回值修改:若
defer
修改命名的返回值,会影响最终结果。
应用场景:资源释放(文件关闭)、错误恢复(recover
)等。
16. 说下 TCP 和 UDP 的区别
TCP 与 UDP 是传输层协议的核心区别在于可靠性与连接机制:
关键差异解释:
有序性:TCP 通过序列号保证数据顺序;UDP 不保证。
流量控制:TCP 使用滑动窗口;UDP 无控制机制。
适用性:TCP 适合需高可靠性的场景;UDP 适合低延迟容忍丢包的场景。
17. TCP 具体采用了哪些机制来保证其可靠性
TCP 通过以下 6 大机制确保数据传输可靠:
序列号与确认应答(ACK):
每个数据包分配唯一序列号,接收方返回 ACK 确认收到数据(累积确认)。
超时重传:
发送方启动定时器(RTO 动态计算),未收到 ACK 则重传数据。
流量控制(滑动窗口):
接收方通过
Window
字段告知剩余缓冲区大小,发送方据此调整发送速率(避免淹没接收方)。拥塞控制:
慢启动:初始窗口小,每 RTT 翻倍。
拥塞避免:窗口达到阈值后线性增长。
快重传/快恢复:收到 3 个重复 ACK 立即重传,避免等待超时。
校验和:
16 位校验和验证数据完整性,错误则丢弃包并触发重传。
连接管理:
三次握手:建立可靠连接(同步序列号)。
四次挥手:确保双方数据发送完成后再关闭连接。
总结:TCP 通过上述机制实现无丢失、无重复、无错误、有序的数据传输。
18. HTTP 协议的不同版本对比
关键演进:
HTTP/1.1 → HTTP/2:从文本到二进制协议,多路复用提升并发效率。
HTTP/2 → HTTP/3:从 TCP 到 QUIC(UDP),避免传输层队头阻塞,更适合高丢包网络。
记忆口诀:
HTTP/1.1:持久连接省握手,Host 区分虚拟节点。
HTTP/2:二部曲(二进制、头部压缩、乱序传输)。
19. 操作系统中的进程调度算法
操作系统的进程调度算法旨在高效分配 CPU 资源,核心算法包括:
先来先服务(FCFS):
按就绪队列顺序执行,非抢占式。
缺点:长任务阻塞短任务("护航效应")。
短作业优先(SJF):
优先执行预估运行时间短的进程。
缺点:长任务可能饥饿。
轮转法(Round Robin, RR):
每个进程分配固定时间片(如 10ms),超时后放回队列尾部。
优点:公平性强,适合分时系统。
优先级调度:
静态/动态优先级(如高响应比优先:
优先级 = (等待时间 + 执行时间) / 执行时间
)。缺点:低优先级进程可能饥饿。
多级反馈队列(MLFQ):
多队列(优先级递减 + 时间片递增),新进程加入最高优先级队列。
优势:平衡响应时间与吞吐量,结合 RR 与优先级优点。
评价指标:吞吐量、周转时间、响应时间、CPU 利用率。
20. 用户态和内核态之间的区别
用户态和内核态是 CPU 特权级的两种模式,核心区别如下:
切换方式:
用户态 → 内核态:通过系统调用(如
read()
)、中断或异常触发。内核态 → 用户态:系统调用返回前恢复用户态上下文。
示例:
意义:隔离用户程序与内核,防止恶意操作破坏系统稳定性。
21. 怎么从用户态切换到内核态
用户态切换到内核态主要通过以下三种方式触发,核心机制是 CPU 特权级的转换(从 Ring 3 切换到 Ring 0)和上下文保存:
系统调用(主动触发)
原理:用户进程通过调用操作系统提供的接口(如
read()
、write()
)主动请求内核服务。步骤:
① 用户程序将系统调用号存入寄存器(如
rax
),参数存入rdi
、rsi
等寄存器。② 执行
syscall
或int 80h
指令,触发软中断,CPU 切换到内核态(Ring 0)。③ 内核保存用户态上下文(RIP、RSP、RFLAGS 等寄存器值)到内核栈,跳转至中断处理程序。
异常(被动触发)
原理:用户程序执行时发生不可预知的错误(如除零、缺页异常),CPU 自动切换至内核态处理。
示例:
缺页异常:进程访问未分配的内存页,触发内核分配物理页并更新页表。
处理流程:保存用户态上下文→执行异常处理程序→修复后返回用户态(若可恢复)。
中断(被动触发)
原理:外部设备(如磁盘、网卡)完成任务后发送中断信号,强制 CPU 暂停当前指令并处理中断。
流程:
设备中断触发,CPU 保存用户态上下文,切换到内核态执行中断处理程序(如磁盘 I/O 完成后的回调)。
中断处理结束,通过
iret
指令恢复用户态上下文。
切换的底层步骤:
从进程描述符中获取内核栈指针(
ss0
和esp0
)。将用户态寄存器状态(CS、EIP、EFLAGS 等)压入内核栈。
加载中断处理程序的入口地址到寄存器,执行内核代码。
关键点:
系统调用是主动切换,异常和中断是被动切换。
切换开销:上下文保存与恢复耗时约 1μs~10μs,频繁切换影响性能。
22. 进程之间的通信方式
进程间通信(IPC)主要用于数据传输、资源共享或事件通知,分为以下五类:
对比与选择:
性能:共享内存 > 消息队列 > 管道 > 套接字(本地通信)。
复杂度:套接字 > 共享内存 > 消息队列 > 管道。
建议:
父子进程协作:匿名管道。
无关进程通信:命名管道或消息队列。
高频数据交换:共享内存+信号量。
23. 进程之间同步的方式有哪些
进程同步解决资源竞争与执行顺序问题,核心机制如下:
信号量(Semaphore)
原理:整数计数器,通过
P()
(等待)和V()
(通知)操作控制资源访问。类型:
二进制信号量:值 0/1,实现互斥锁。
计数信号量:限制资源数量(如线程池)。
互斥锁(Mutex)
原理:二值锁,同一时间仅一个进程持有锁,其他进程阻塞等待。
场景:保护临界区(如共享文件写入)。
条件变量(Condition Variable)
原理:与互斥锁配合使用,当条件不满足时阻塞进程,条件达成时唤醒(如
pthread_cond_wait
)。示例:生产者-消费者模型,缓冲区空时消费者等待。
读写锁(Read-Write Lock)
原理:允许多个读操作并发,写操作独占资源。
场景:读多写少(如数据库查询)。
屏障(Barrier)
原理:多个进程到达屏障点后同时继续执行,用于并行计算同步。
同步问题的本质:
互斥:确保资源不被同时访问(如打印机使用)。
同步:协调进程执行顺序(如 A 进程输出后 B 进程才能处理)。
24. 介绍一下单例模式
单例模式确保一个类仅有一个实例,并提供全局访问点,常用于资源管理(如配置、线程池)。
实现方式:
饿汉式:类加载时创建实例,线程安全但可能浪费内存。
懒汉式(双重检查锁):首次调用时创建实例,避免资源浪费。
特点:
优点:避免重复创建,节省内存;统一访问入口。
缺点:需处理多线程安全问题;可能隐藏代码依赖关系。
应用场景:
数据库连接池(避免多次初始化连接)。
日志管理器(全局唯一写入点)。
25. 策略模式
策略模式定义一组算法,封装每个算法使其可互换,让算法独立于客户端变化。
核心组件:
策略接口(Strategy):声明算法方法(如
execute()
)。具体策略类(ConcreteStrategy):实现不同算法(如支付宝支付、微信支付)。
上下文类(Context):持有策略引用,调用策略方法。
示例:支付系统
优势:
灵活扩展:新增支付方式无需修改上下文。
解耦:算法与客户端分离,避免条件分支(如
if-else
)。
适用场景:
支付方式、排序算法等需动态切换的场景。
算法需独立测试或复用。
26. 图的遍历算法有哪些,并简要说明它们的特点
图的遍历分为两类,特点对比如下:
关键差异:
路径探索:DFS 适合探索所有可能路径(如迷宫求解),BFS 适合最短路径(如社交关系链)。
复杂度:时间复杂度均为 O(V+E)(V 为顶点数,E 为边数),但 BFS 空间开销可能更大(队列存储)。
27. 介绍图的广度优先算法
广度优先搜索(BFS)按层次遍历图,核心是队列管理和层级扩散,确保先访问的节点其邻接点优先访问。
算法流程:
初始化:
将起始节点标记为已访问,加入队列。
迭代访问:
队首节点出队,访问其所有未访问邻接节点,标记并入队。
重复直至队列为空。
特点:
最短路径:在无权图中,BFS 首次访问到目标节点的路径一定是最短路径。
层级性:队列中节点按距离起始点的层级排序。
应用场景:
网络爬虫(按链接深度抓取)。
社交网络好友推荐(N 度关系)。
性能优化:稀疏图使用邻接表存储,避免邻接矩阵的 O(V²)遍历开销。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。
版权声明: 本文为 InfoQ 作者【王中阳Go】的原创文章。
原文链接:【http://xie.infoq.cn/article/714659391fbab38aadc659f2c】。文章转载请联系作者。
评论