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。

2. 线上排查实战 (PPFROF)

场景:线上服务突然 CPU 100% 或 内存暴涨,怎么查?

  1. 开启 PProf:代码里引入 import _ "net/http/pprof",启动 HTTP 服务。

  2. 抓取 CPU Profile

    go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
    • 等 30 秒,进入交互模式。
    • 输入 top:看哪个函数耗时最长(如 runtime.scanobject 可能是 GC 问题,业务函数可能是死循环)。
    • 输入 list <函数名>:看具体哪一行代码卡住了。
  3. 抓取 Heap (内存) Profile

    go tool pprof http://localhost:6060/debug/pprof/heap
    • top:看谁占内存最多。
    • 关注 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 可以在进程内传递。

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
}
// 易错点:原地交换,不要开新数组。

🚀 终极建议

  1. 心态:面试前一晚不要熬夜刷题,保持清醒。
  2. 引导:遇到不会的,往会的引。比如问”红黑树”,如果不会,说”红黑树通过旋转保持平衡,在 Java HashMap 用到,但我对 B+ 树更熟,它适合磁盘 IO…”。
  3. 自信:“虽然我没用过 XXX,但我通过学习了解到它的原理是…”,展示你的学习能力