Update mengyastore
This commit is contained in:
109
面试相关/字节后端面试模拟-mengyastore.md
Normal file
109
面试相关/字节后端面试模拟-mengyastore.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 字节后端面试模拟(萌芽小店情景 · 套八股式追问)
|
||||
|
||||
> **怎么用这份材料**:面试官通常**不会对着你的仓库逐行看**,而是听你说「我做了一个小店后端」之后,用**通用八股 + 业务情景**连续追问。下面 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 失败可降级发信。
|
||||
Reference in New Issue
Block a user