Go 后端面试复盘:语言、并发与服务实践 · 29.9 LRU Cache 核心设计至模板边界自检
前置:Go 系统学习,并完成一个HTTP 工程练习。本页用于面试前查漏补缺,不按章节替代语言课程。
29.9 LRU Cache 核心设计
使用:
- map:O(1) 找节点。
- 双向链表:O(1) 移动到头部、删除尾节点。
- mutex:并发保护。
操作:
- Get:map 找节点,移动到头部。
- Put:存在则更新并移动;不存在则插入头部。
- 超容量:删除尾节点和 map key。
面试时需要解释为什么不是只用 map:map 无法 O(1) 维护最近使用顺序。
29.10 singleflight 思路
目标:同一 key 缓存未命中时,只允许一个 goroutine 加载,其他等待相同结果,防止热点击穿。
核心状态:
map[key]*call
call:
done channel
result
error流程:
- 加锁检查 key 是否已有 call。
- 有则等待 done。
- 无则创建 call,解锁后执行加载。
- 写入结果,关闭 done。
- 从 map 删除 call。
生产中应直接使用成熟实现,并考虑 context 取消是否只取消等待者还是底层共享请求。
30. 面试回答方法与复习路线
30.1 一个技术题应该怎么回答?
推荐五层结构:
- 一句话结论:先回答是什么。
- 核心原理:解释内部机制。
- 使用场景:什么时候用。
- 风险与取舍:什么时候不能用。
- 项目实践:结合真实问题、指标和结果。
例如“channel 和 mutex 怎么选”:
channel 更适合任务传递、所有权转移和流水线;mutex 更适合保护共享状态和短临界区。 channel 通过发送接收建立同步,mutex 通过临界区保证互斥。 我会先根据数据所有权选模型,再通过 race detector 和 benchmark 验证。 如果只是高频访问一个 map,用 channel 把每次读写都串行化可能增加复杂度;如果是 worker pool 和停止通知,channel 更自然。
30.2 项目题如何使用 STAR 表达?
- Situation:当时业务背景和问题。
- Task:你的职责和目标。
- Action:你做了什么,为什么这么做。
- Result:量化结果、风险变化和后续复盘。
示例框架:
Domino 游戏入口高峰时,登录链路 P99 上升并出现下游超时。我的任务是定位瓶颈并在不改变协议的情况下稳定延迟。 我通过 trace、数据库连接池指标和 goroutine profile,发现请求并发无上限、下游超时大于入口预算,并伴随重试放大。 我增加有界并发、统一 2 次指数退避、缩短下游 deadline,并补充连接池等待指标和熔断。 上线灰度后 P99 从 X 降至 Y,超时率从 A% 降至 B%,同时建立了对应压测基线和告警。
不要虚构数字;面试前从真实监控或复盘中准备。
30.3 七天快速复习计划
第 1 天:基础与容器
- 类型、零值、值传递。
- string、rune、UTF-8。
- slice 共享、扩容、内存滞留。
- map 原理与并发限制。
- interface nil。
第 2 天:函数与工程语义
- defer、panic、recover。
- error wrap、Is、As。
- 方法集、指针接收者。
- 泛型。
- 包初始化和 modules。
第 3 天:并发
- GMP。
- channel、select。
- context。
- Mutex、RWMutex、WaitGroup、Once、Pool。
- 数据竞争、死锁、goroutine 泄漏。
第 4 天:内存和 GC
- 逃逸。
- 分配器。
- 三色标记。
- 写屏障。
- GOGC、内存限制。
- pprof 和 trace。
第 5 天:网络和存储
- TCP 字节流、粘包拆包。
- HTTP Client/Server 超时和连接池。
- database/sql。
- 事务、索引、慢查询。
- Redis 缓存问题和锁。
第 6 天:微服务稳定性
- gRPC、Proto 演进。
- Kratos 分层。
- 超时、重试、限流、熔断。
- 幂等、消息、Outbox。
- 优雅启停和可观测性。
第 7 天:模拟面试
- 60 道快问快答。
- 手写 worker pool、LRU、并发安全缓存。
- 准备 3 个 STAR 项目案例。
- 复盘不会的问题并回到原理。
30.4 一个月深入路线
第 1 周:语言与标准库
- 每天阅读一个标准库小接口:io、context、errors、sync。
- 写 slice/map/interface 小实验。
- 使用
-gcflags="-m=2"观察逃逸。
第 2 周:并发与运行时
- 实现 worker pool、pipeline、超时与取消。
- 故意制造 race、deadlock、goroutine leak,再用工具定位。
- 阅读 runtime 调度和内存相关文章,并结合 trace 实验。
第 3 周:服务与数据
- 写一个带 MySQL、Redis、HTTP/gRPC 的小服务。
- 配置连接池、超时、限流和 graceful shutdown。
- 加 metrics、logs、traces。
第 4 周:性能与项目表达
- 建立压测基线。
- 采集 CPU/heap/mutex/goroutine profile。
- 完成一次基于证据的优化。
- 总结 3 个项目案例和 10 个故障排查案例。
30.5 面试前最终检查表
语言:
- [ ] 能解释 slice 三字段、append、共享和内存滞留。
- [ ] 能解释 map、扩容、并发问题。
- [ ] 能解释 interface nil 和方法集。
- [ ] 能解释 defer 求值和返回顺序。
- [ ] 能解释 error wrap、Is、As。
并发:
- [ ] 能画出 GMP。
- [ ] 能说明 channel 各种状态。
- [ ] 能解释 context 取消不是强杀。
- [ ] 能识别 race、deadlock、leak。
- [ ] 能写有界并发。
- [ ] 能说清 mutex 与 channel 的选择。
内存:
- [ ] 能解释逃逸。
- [ ] 能说清 mcache、mcentral、mheap 的层次。
- [ ] 能解释三色标记和写屏障。
- [ ] 能区分 heap、RSS、累计分配和存活内存。
服务:
- [ ] HTTP Client/Server 设置了合理超时。
- [ ] 知道 response body、rows、ticker 等资源如何释放。
- [ ] 能解释连接池和事务。
- [ ] 能说明缓存穿透、击穿、雪崩。
- [ ] 能说明限流、熔断、重试和幂等。
工程:
- [ ] 能介绍自己的项目分层和依赖方向。
- [ ] 能给出一次性能排查案例。
- [ ] 能给出一次线上故障复盘。
- [ ] 能给出一次复杂并发设计。
- [ ] 所有项目数据真实且能解释测量方式。
跨专题复习边界
Go 服务面试还会讨论语言之外的机制,按实际岗位另行复盘:
- 网络专题:TCP 的 TIME_WAIT 与 2MSL、连接建立关闭、PING 与跨网段通信;gRPC 调用边界见第 24 章。
- MySQL 与 Redis:索引、隔离、日志、缓存一致性及锁;第 19、20 章侧重 Go 调用方的使用与故障。
- Docker 与 Kubernetes:镜像、命名空间、组件、工作负载、Pod 网络与持久化;它们不属于 Go 语法前置。
- 算法与设计模式需要单独练习。用 Go 算法与容器实践验证不变量;单例示例见第 29 章,项目中优先明确构造与依赖。
附录 A:常用排查命令
# 格式化
gofmt -w .
# 单元测试
go test ./...
# 数据竞争
go test -race ./...
# 基准测试与分配
go test -bench=. -benchmem ./...
# 覆盖率
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# 静态检查
go vet ./...
# 查看逃逸和内联
go build -gcflags="-m=2" ./...
# 查看依赖
go list -m all
go mod graph
go mod why example.com/module
# CPU profile
go tool pprof cpu.pprof
# Heap profile
go tool pprof heap.pprof
# Trace
go tool trace trace.out附录 B:按设计路径回看
完成一次口述或手写后,沿同一条数据和生命周期路径核对,避免把零散结论当成完整设计。
| 核对路径 | 具体内容 |
|---|---|
| 语言语义 | 参数复制与共享数据 → append 返回值 → 接口 nil |
| 并发协议 | 任务所有者与退出 → 通道关闭职责 → 等待阶段 |
| 内存证据 | 逃逸 → GC 调节 → 存活与累计分配 |
| 服务调用 | 连接复用 → 响应体 → 事务提交 |
| 稳定性与优化 | 锁的故障边界 → 重试预算 → 测量与复测 |
最重要的不是背完所有结论,而是把高频问题连接成完整知识链: 代码写法 → 编译器 → runtime → 操作系统 → 网络/存储 → 分布式故障 → 监控与验证。
模板边界自检
提示:逐段区分算法机制与业务占位符,不把片段叫作完整生产实现。firstKB两个片段要求len(big)>=1024,短输入需先检查;StartWorkers要求workers>0且调用方持续接收results或取消context,results仅由协调者关闭;FetchJSON额外使用math.MaxInt64避免maxBody+1溢出,需导入math;TTL缓存值仍是浅复制,过期仅判断不自动清理。
参考判断
事务模板应在任何未完成退出时尝试Rollback,包括panic展开;成功Commit后defer的Rollback可忽略已完成结果。重试maxAttempts=0应拒绝,不能返回nil冒称成功;请求体上限不可加一溢出。read错误必须先返回,不把短内容当成功。并发取消仅为协作协议,不代替等待或强行结束阻塞任务。所有模板的网络、数据库和性能行为均未在本轮执行。
继续阅读
- 排错
- 排错
- 排错