8.4 KiB
字节后端面试模拟(萌芽小店情景 · 套八股式追问)
怎么用这份材料:面试官通常不会对着你的仓库逐行看,而是听你说「我做了一个小店后端」之后,用通用八股 + 业务情景连续追问。下面 10 题按这种话术写:先像真面试里的问法,回答里把原理(八股)和你会怎么落地到项目揉在一起。
比重:Go、MySQL 为主;Redis、RabbitMQ 各挑典型一问为辅(与当前仓库里可选组件对齐,不必假定对方读过源码)。
项目一句话(自我介绍用)
用 Go 写了小店后端:Gin 提供 API,MySQL + GORM 存商品订单,结账有支付待确认状态,可选 MQ 异步发订单邮件、可选 Redis;认证打外部账户中心。细节不用背文件路径,按模块说即可。
10 道面试题(主 Go / MySQL)
1. 情景:下单要把「查库存、写订单」绑在一起 —— 为什么上事务?边界怎么收?
问法:你说下单用数据库事务包了一段逻辑。那事务里放哪些、不放哪些?如果要把事务缩到最短,你会拆成几步?和以前不用事务只扣库存相比,要解决什么并发问题?
回答要点:保证 ACID 里的一致性:读到的库存和写入的订单一致,避免并发下 lost update / 超卖。事务外适合做参数校验、调外部 HTTP;事务内只做 行锁下的读写。缩短边界可降低锁持有时间、减死锁概率;但拆太散会失去原子性,要用 TCC、本地消息表、幂等单号 等补偿(视业务接受度)。InnoDB 里常用 SELECT … FOR UPDATE 当前读锁住商品行再扣减。
2. 情景:SELECT … FOR UPDATE 锁商品 —— 和乐观锁、直接 UPDATE qty = qty - ? 有啥区别?
问法:库存扣减你说是行锁读的,那悲观锁和乐观锁各适用什么场景?UPDATE … WHERE qty >= ? 用影响行数判断成败,和你先 FOR UPDATE 再改,语义和性能上差在哪?
回答要点:悲观锁(FOR UPDATE)适合冲突概率高、逻辑分支多的扣减;乐观锁用 version 列 或 CAS 式 UPDATE,适合读多写少、重试用业务可接受。纯 UPDATE 条件扣减一条 SQL 完成,锁时间短,但复杂流程(预留卡密、多表)仍常要事务配合。RR 下 FOR UPDATE 是当前读,和普通快照读不同,别混用。
3. 情景:同一商品只允许一笔「待支付」,不然支付回调不好认 —— 库里怎么表达?和隔离级别有关吗?
问法:如果业务上同一 SKU 同时只能有一单 pending,你除了应用层判断,数据库层还能怎么兜底?MySQL 默认隔离级别下,只靠「先查有没有 pending 再插」会有什么问题?
回答要点:应用层在事务里 FOR UPDATE 商品行 + 查 pending,串行化冲突。DB 层可 唯一索引(如 (product_id, status) 对 pending 状态——MySQL 里部分场景用生成列 / 约束不好表达,更常见是 部分唯一索引 8.0+ 或状态机 + 应用保证)。只靠快照读「先查后插」可能 幻读 / 竞态,要用当前读或约束。追问隔离级别:默认 RR + 当前读/锁 解决多数此类问题。
4. 情景:支付超时要把预留库存还回去 —— 除了定时扫表还有什么?索引注意啥?
问法:待支付订单过期要释放库存,你用定时任务扫「过期 pending」。拖数据库怎么优化?延时队列、Redis TTL 通知这类方案你怎么权衡?
回答要点:扫表要有 status + payment_expires_at 等组合索引,避免全表扫。批量处理、限流、幂等释放(UPDATE … WHERE status='pending' AND expires_at < now() 行数判断)。延时队列 / MQ 延迟消息:精度与运维成本更高;Redis keyspace 通知不靠谱作唯一依据。核心是 DB 仍为权威状态,定时扫是简单稳妥的基线。
5. 情景:Go 里起了后台 goroutine(定时任务、MQ 消费)—— 进程要停时你怎么收尾?
问法:main 外面 go 了长时间运行的循环,收到 SIGTERM 怎么办?context.Cancel 先取消谁?和 server.Shutdown 顺序?
回答要点:根 Context 可取消,select 里监听 <-ctx.Done() 退出循环。优雅关停:先 HTTP Shutdown 停新流量,再 cancel 后台任务,MQ consumer ack 完再关 channel/connection;defer 释放要防 Close 后再用。生产用 errgroup 或包装 runnable 统一等待。
6. 情景:HTTP Handler 里调外部认证、写数据库 —— 超时、错误怎么传?
问法:用户请求进来,你要调外部验 Token、再写库。context 是传 c.Request.Context() 还是自己 WithTimeout?超时到了 SQL 还会不会执行?
回答要点:IO 应带 可取消的 Context;HTTP 入口常用 c.Request.Context() 与用户断开联动,或包一层 更短的业务超时。GORM/MySQL driver 若支持 context,取消后会尝试中断;未挂 context 的请求无法被动取消。错误:包装 fmt.Errorf("%w") 保留链;对外脱敏,日志里打 request_id。
7. 情景:database/sql 连接池 —— 几个参数和 MySQL wait_timeout 关系?
问法:SetMaxOpenConns / SetMaxIdleConns / SetConnMaxLifetime 各自干啥?ConnMaxLifetime 设得比服务端 wait_timeout 长会怎样?
回答要点:MaxOpen 限并发连接数保护 DB;MaxIdle 平衡复用与内存;ConnMaxLifetime 让连接定期重建,避免被 MySQL 或中间层 断开后的坏连接。经验上 Lifetime 略小于 wait_timeout(或云库推荐的 idle 时间),减少 packets.go broken pipe 类问题。MaxIdle=0 会导致高频建连。
8. 情景:ORM + 迁移 —— 你怎么看待 AutoMigrate 上生产?
问法:项目启动时自动 Migrate,大厂里也这么干吗?危险点在哪?你更信哪种流程?
回答要点:AutoMigrate 适合开发/小项目,生产常见 显式迁移(goose / golang-migrate / Atlas),要 review、灰度、可回滚。风险:不能与复杂 DDL(大表改列、索引)等价;线上锁表、双写切换需要 人工方案。面试答「小型服务可 AutoMigrate,有 SLA 就上迁移工具 + CI」。
9. 【Redis · 辅】情景:浏览量或去重防刷 —— 单机内存和 Redis 差在哪?
问法:你说用过进程内 map 做访问去重,多实例部署会怎样?用 Redis 的话 SET NX + EX、HyperLogLog 分别在什么诉求下选?
回答要点:进程内 map 不跨实例,计数会偏高或去重失效。Redis:SET key NX EX TTL 做窗口内去重;INCR 做简单计数;HLL 只要大概 UV、省内存。热 key 要本地缓存、拆分 key 或读写分离;持久化与缓存 DB 一致性按业务容忍度设计(能接受最终一致即可)。
10. 【RabbitMQ · 辅】情景:订单邮件发 MQ —— 还会丢消息吗?你怎么补?
问法:交换机、队列、durable、消息 persistent 都配了,哪三段仍可能丢?at-least-once 业务要做什么?
回答要点:经典三段:Producer→Broker(未 confirm、网络闪断)、Broker(未刷盘、集群未完成同步——视配置)、Consumer(处理完未 ack 或先 ack 后崩)。Publisher Confirm + 消费者手动 ack 提高可靠性;语义多为 至少一次,消费端 幂等(订单号去重、Unique 约束)。重连重发可能 duplicate,要和幂等一起看。
题量分布(自检)
| 方向 | 题号 |
|---|---|
| Go(runtime、context、goroutine、HTTP、错误) | 5、6 |
| MySQL(事务、锁、隔离、索引、迁移、连接池) | 1、2、3、4、7、8 |
| Redis(为辅) | 9 |
| RabbitMQ(为辅) | 10 |
附:超短技术栈备忘(背 30 秒版)
Go 1.21 + Gin + GORM;MySQL;可选 RabbitMQ(异步邮件)、可选 Redis(健康/扩展);外部 HTTP 验 Token。下单事务 + 行锁;待支付超时回收;支付回调匹配订单;MQ 失败可降级发信。