Files
mengyastore/面试相关/字节后端面试模拟-mengyastore.md
2026-05-13 12:11:46 +08:00

110 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 字节后端面试模拟(萌芽小店情景 · 套八股式追问)
> **怎么用这份材料**:面试官通常**不会对着你的仓库逐行看**,而是听你说「我做了一个小店后端」之后,用**通用八股 + 业务情景**连续追问。下面 10 题按这种话术写:**先像真面试里的问法**,回答里把**原理(八股)**和**你会怎么落地到项目**揉在一起。
> **比重****Go、MySQL 为主****Redis、RabbitMQ 各挑典型一问为辅**(与当前仓库里可选组件对齐,不必假定对方读过源码)。
---
## 项目一句话(自我介绍用)
用 Go 写了小店后端Gin 提供 APIMySQL + 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要和幂等一起看。
---
## 题量分布(自检)
| 方向 | 题号 |
|------|------|
| Goruntime、context、goroutine、HTTP、错误 | 5、6 |
| MySQL事务、锁、隔离、索引、迁移、连接池 | 1、2、3、4、7、8 |
| Redis为辅 | 9 |
| RabbitMQ为辅 | 10 |
---
## 附:超短技术栈备忘(背 30 秒版)
Go 1.21 + Gin + GORMMySQL可选 RabbitMQ异步邮件、可选 Redis健康/扩展);外部 HTTP 验 Token。下单事务 + 行锁待支付超时回收支付回调匹配订单MQ 失败可降级发信。