18-考前查缺补漏_终极备忘
💡 背景:既然基础看差不多了,这篇文档专注**“容易被忽视的细节”和“线上实战场景”**。这些通常是区分“背题家”和“实战派”的分水岭。
1. Go 语言陷阱 (必看细节)
Q1: Interface 的 nil 判断陷阱
func main() {
var s *string = nil
var i interface{} = s
// 问:i == nil 吗?
// 答:false!
// 原因:interface = {type, value}。这里 type=*string, value=nil。
// 只有 {nil, nil} 才是 nil。
if i == nil {
println("is nil")
} else {
println("not nil") // 输出这个
}
}避坑:永远不要把具体的 nil 指针赋值给 interface 返回,除非你明确知道自己在做什么。
Q2: Defer 与 Named Return Value
func c() (i int) {
defer func() { i++ }() // 闭包引用,修改具名返回值
return 1
}
// 结果:return 先把 i 赋值 1,defer 执行 i++ 变成 2,最后返回 2。对比:
func d() int {
i := 1
defer func() { i++ }()
return i
}
// 结果:return 把 i 的值(1)拷贝到返回值区,defer 修改的是 i 局部变量,返回值区不受影响。返回 1。Q3: Slice 扩容与引用
- 小切片引用大数组:
small := huge[:10],底层大数组不会释放。解决:copy到新 slice。 - Append 坑:函数传 slice,append 可能换底层数组,导致外层 slice 没变。
- 口诀:如果要修改 slice 长度或容量,必须传
*[]int或者return新 slice。
- 口诀:如果要修改 slice 长度或容量,必须传
2. 线上排查实战 (PPFROF)
场景:线上服务突然 CPU 100% 或 内存暴涨,怎么查?
-
开启 PProf:代码里引入
import _ "net/http/pprof",启动 HTTP 服务。 -
抓取 CPU Profile:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30- 等 30 秒,进入交互模式。
- 输入
top:看哪个函数耗时最长(如runtime.scanobject可能是 GC 问题,业务函数可能是死循环)。 - 输入
list <函数名>:看具体哪一行代码卡住了。
-
抓取 Heap (内存) Profile:
go tool pprof http://localhost:6060/debug/pprof/heaptop:看谁占内存最多。- 关注
inuse_space(当前使用) 和alloc_space(历史累计分配,查频繁 GC)。
Trace 工具:
- 如果 CPU 不高但请求慢(延迟高),用
curl .../trace?seconds=5抓 trace,用go tool trace看调度图,找 G 是否频繁阻塞在锁或网络上。
3. 数据库进阶 (MVCC & 锁)
Q1: 幻读 (Phantom Read) 到底解决没?
- 快照读 (Select):通过 MVCC (Read View) 解决,看不到新插入的行。
- 当前读 (Select for update / Update):通过 Next-Key Lock (间隙锁) 解决,锁住间隙不让别人插入。
- 例外:如果你先 Select (快照),然后 Update (当前读),由于能更新到新行,再次 Select 就能看到了,这算一种特殊的幻读。
Q2: Redo Log vs Binlog
- Redo Log (InnoDB):物理日志(“在第 3 页第 5 行改了值”),循环写,保崩坏恢复 (Crash Safe)。
- Binlog (Server):逻辑日志(“执行了 Update x set …”),追加写,保主从同步。
- 两阶段提交 (2PC):先写 Redo Log (Prepare) → 写 Binlog → 提交 Redo Log (Commit)。保证两者一致。
4. 微服务/分布式查漏
Q1: 分布式 ID (Snowflake)
- 结构:1位符号 + 41位时间戳 + 10位机器ID + 12位序列号。
- 时钟回拨问题:
- 如果发现当前时间 < 上次生成时间,说明回拨了。
- 解法:等待时间追上来,或者抛异常,或者借用备用机器 ID。
Q2: 链路追踪 (Tracing)
- 核心:
TraceID(全链路唯一) +SpanID(当前节点)。 - 传递:
- HTTP Header:
X-B3-TraceId - gRPC Metadata
- Go
context.Context可以在进程内传递。
- HTTP Header:
Q3: 一致性 Hash (Consistency Hash)
- 解决什么:负载均衡中,节点增删导致大量 Key 重新映射(缓存雪崩)。
- 原理:0~2^32 的环。节点 Hash 到环上,Key 顺时针找最近的节点。
- 虚拟节点:解决节点少时数据倾斜问题(每个物理节点映射成 100 个虚拟点)。
5. 必背算法模板 (最后一眼)
LRU 缓存 (Hash + 双向链表)
// 核心:Get 时把节点移到链表头,Put 时如果在就更新并移头,不在就插头,满则删尾。
// Go 可用 container/list 实现,面试建议手写简单的 prev/next。快速排序 (QuickSort)
func partition(arr []int, low, high int) int {
pivot := arr[high]
i := low - 1
for j := low; j < high; j++ {
if arr[j] < pivot {
i++
arr[i], arr[j] = arr[j], arr[i]
}
}
arr[i+1], arr[high] = arr[high], arr[i+1]
return i + 1
}
// 易错点:原地交换,不要开新数组。🚀 终极建议
- 心态:面试前一晚不要熬夜刷题,保持清醒。
- 引导:遇到不会的,往会的引。比如问”红黑树”,如果不会,说”红黑树通过旋转保持平衡,在 Java HashMap 用到,但我对 B+ 树更熟,它适合磁盘 IO…”。
- 自信:“虽然我没用过 XXX,但我通过学习了解到它的原理是…”,展示你的学习能力。