工程与系统设计 面试题
算法岗系统设计面试:把模型做成服务
算法岗的系统设计不要求你设计一个淘宝,但会问:这个模型怎么上线、QPS 多少、超时怎么办、失败了怎么降级、怎么灰度。考的是你有没有把模型当成一个服务来想过。
这个方向在考什么
- 服务化:同步/异步、批处理、超时与重试、幂等
- 稳定性:限流、熔断、降级、缓存穿透与雪崩
- 数据链路:离线训练与在线特征的衔接、回填与一致性
- 发布:灰度、AB 实验、回滚与监控指标
最常见的几个坑
- 只说"用 FastAPI 包一层",不讲超时、并发和降级
- 缓存设计只讲 Redis,不讲穿透/击穿/雪崩的区别
- AB 实验说不清分流单位与统计显著性
七天准备路线
- 第 1 天把一个你做过的模型服务画成架构图:入口、队列、模型、存储
- 第 2 天稳定性专场:限流、熔断、降级、重试的具体策略
- 第 3 天数据链路:特征回填、线上线下一致性
- 第 4 天发布与实验:灰度、AB、监控指标
- 第 5-7 天按方向刷题,练"先问约束再给方案"
工程与系统设计高频面试题
- 你负责一个批量任务模块,函数写成 `def add_task(task, queue=[])`,内部 `queue.append(task); return queue`。测试时多次调用后发现 queue 里累积了之前所有 task,即使没有传 queue。请解释根因,并给出至少两种修复方式,说明各自适用场景。 · 1-3 年
- 一个常驻服务启动时把一份 100 万行的 CSV 一次性载入内存,每行解析成 dict 缓存起来(8 个字段:3 个短字符串、2 个 int、1 个 float、1 个 bool、1 个时间戳字符串)。实测进程 RSS 约 800MB,而机器只给了 1GB,跑一段时间后被 OOM Killer 干掉。你会怎么把常驻内存压下来?请说出你会选的具体方案、预估能降到多少、以及这个方案让你牺牲了什么。 · 3-5 年
- 你写了一个 `async def fetch_all(urls)`,里面用 `requests.get(url)` 循环请求 100 个 URL,然后 `await asyncio.gather(*[fetch_one(u) for u in urls])`。压测发现 QPS 和串行差不多,且一个慢请求会拖住其他所有请求。请解释原因,并给出两种改法;如果某个同步库没有异步替代,怎么放才不阻塞事件循环? · 1-3 年
- 一个基于 asyncio 的 HTTP 服务,单进程单事件循环,正常 P99 是 20ms。接入一个新的下游 SDK 后,该 SDK 只有同步阻塞接口(单次调用约 50ms,内部是阻塞 socket 读)。上线后 P99 涨到 500ms,QPS 也上不去。你会怎么定位和改造?请说清你选哪种方案、线程/进程开多少、以及并发量上来之后会先崩在哪里。 · 3-5 年
- 你在 feature 分支上有 3 个本地提交,期间 main 已被同事合并了多个提交。现在要提 PR,团队要求历史整洁且不能覆盖别人的提交。你会选 rebase 还是 merge?如果 rebase 到第 2 个提交时冲突,具体怎么处理?如果这个 feature 分支已经 push 且有人基于它开了新分支,还能不能 rebase? · 1-3 年
- 一个 10 人团队,main 分支每天合并约 30 个 PR,长期使用 squash merge。现在线上出现一个回归,怀疑是过去 3 周内约 400 个提交中的某一个引入的,需要用 git bisect 定位。你负责这次排查。请说明你现在会怎么做、这次排查会遇到什么麻烦、以及排查完你是否要改团队的分支/合并策略,依据是什么。 · 3-5 年
- 一个 Python 服务的 Dockerfile 目前是 `FROM python:3.11`、`COPY . .`、`RUN pip install -r requirements.txt`。每次只改一行业务代码,`docker build` 也要重新下载全部依赖,约 5 分钟。请调整 Dockerfile 利用层缓存;如果还要求镜像尽量小且构建可复现,基础镜像、依赖安装和启动用户分别怎么设计? · 1-3 年
- 一个 Python 服务的 Dockerfile 是:FROM python:3.11 → COPY . /app → RUN pip install -r requirements.txt。本地改一行业务代码,CI 上重新构建镜像要 8 分钟,其中 7 分半花在装依赖上。请给出你会怎么改这个 Dockerfile、改完之后改一行代码的重建时间大概是多少,以及这套改法还有什么坑你没解决。 · 3-5 年
- 线上一个服务所在机器 CPU 使用率飙到 90%,你登录后需要快速定位是哪个进程、哪个线程、以及是用户态还是内核态开销。请给出一条按顺序执行的命令链;如果定位到是 JVM 或 Python 进程,下一步怎么把高 CPU 线程对应到堆栈? · 1-3 年
- 生产机告警:45GB 的系统盘使用率 100%,应用写日志报 No space left on device。你登录机器后执行 du -sh /*,各项加起来只有 9GB。请给出你的排查路径和每一步用的命令,说明最可能的原因、确认方法,以及怎么在不重启进程的前提下把空间放出来。 · 3-5 年
- 订单表 orders 约 2000 万行,字段有 id(主键)、user_id、status(只有 3 个取值:待支付/已支付/已取消)、created_at、amount,现已有单列索引 idx_user_id(user_id)。线上核心接口执行:SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 20; EXPLAIN 显示 key=idx_user_id、rows≈3 万、Extra 里有 Using filesort。你会怎么调整索引?为什么建 (user_id, status, created_at) 而不是 (user_id, created_at, status)? · 1-3 年
- 一张 2000 万行的订单表 orders(id PK, user_id, status, created_at, amount),status 只有 5 个取值,其中 'pending' 约占 3%。已有索引 PRIMARY(id)、(user_id, status)、(created_at)。现在有这样一条查询:SELECT * FROM orders WHERE status='pending' ORDER BY created_at DESC LIMIT 20。它在生产上要跑 3 秒左右,DBA 说它在全表扫描。请你判断为什么会走全表扫描,并给出你的索引方案,说明为什么是它。 · 3-5 年
- 订单表 3 亿行,MySQL 8.0 + InnoDB。核心查询是 `SELECT ... FROM orders WHERE merchant_id=? AND status IN (1,2) ORDER BY created_at DESC LIMIT 20`,QPS 约 800,P99 从 30ms 涨到 1.2s。现有联合索引是 idx_mch(merchant_id, status, created_at),主键 order_id。你接手这个慢查询,要求说明:你第一步看什么来确认瓶颈、为什么现有索引没能避免排序、你改什么(索引还是查询)、怎么评估改动对线上写入和主从延迟的影响,以及如果业务不愿意改查询语义你怎么办。 · 5 年以上
- 表 orders(id BIGINT PRIMARY KEY, user_id BIGINT, status VARCHAR(16), created_at DATETIME, amount INT),已有索引 idx_user(user_id),表里 2000 万行,绝大多数订单 status='paid'。线上查询:SELECT * FROM orders WHERE user_id=? AND status='paid' ORDER BY created_at DESC LIMIT 20; EXPLAIN 结果是 type=ref、key=idx_user、rows≈18000、Extra='Using where; Using filesort'。你会怎么优化这条 SQL?如果要建复合索引,字段顺序怎么定,为什么这样定? · 校招
- 库存表 stock(sku varchar 主键, qty int),隔离级别为 RR。扣减写法 A:UPDATE stock SET qty = qty - 1 WHERE sku = ? AND qty >= 1,然后判断 affected rows 是否为 1。写法 B:先 SELECT qty FROM stock WHERE sku = ?,在应用层判断 qty >= 1,再执行 UPDATE stock SET qty = qty - 1 WHERE sku = ?,两个操作都在同一个事务里。现在库存只有 1,两个并发请求各扣 1,分别用 A 和 B 会不会超卖?为什么? · 1-3 年
- MySQL InnoDB,隔离级别 REPEATABLE READ。下单扣库存的代码是这样写的:先在事务里执行 SELECT stock FROM product WHERE id=1(普通 SELECT,不带 FOR UPDATE),应用层判断返回值 > 0,然后执行 UPDATE product SET stock = 5 WHERE id=1 把算好的值写回去。压测时这个逻辑出现了超卖。请解释为什么在 RR 下还会超卖,并给出至少两种修复方案,分别说明它们的代价。 · 3-5 年
- 一个电商下单链路:订单服务写订单库后通过 RPC 调库存服务扣减,两个库在不同实例上。线上每天出现几十笔“订单创建成功但库存未扣减”,也偶发反向的“库存扣了但订单回滚”。你被要求主导修复。请说明:你怎么先止血和定位根因、你选哪种一致性方案及理由、怎么保证幂等和补偿、怎么设计对账兜底,以及怎么说服业务方接受“准实时一致”而不是强一致。 · 5 年以上
- MySQL InnoDB,隔离级别是默认的 REPEATABLE READ。转账逻辑是两条 SQL 包在同一个事务里:UPDATE account SET balance=balance-100 WHERE id=1; 然后 UPDATE account SET balance=balance+100 WHERE id=2;。现在并发跑两笔方向相反的转账(事务 A 从 1 转到 2,事务 B 从 2 转到 1),出现了死锁报错。请说明这个死锁是怎么产生的,以及你会怎么改代码避免它。 · 校招
- 商品详情接口用 Redis 做缓存,key 是 item:{id},value 是完整商品 JSON,TTL 设为 24 小时;读路径是先查 Redis,miss 则查 DB 并回填。运营改价时写路径目前是:UPDATE DB 成功后执行 DEL Redis。问:这个做法在什么情况下会让缓存里长时间停留在旧值?如果反过来改成先 DEL 再 UPDATE DB,风险是什么? · 1-3 年
- 商品详情服务用 Redis 缓存商品 JSON,读 QPS 峰值约 8 万,缓存 TTL 统一设为 30 分钟。大促时发现某个爆款商品的缓存 key 每到过期瞬间,就有 2 万左右 QPS 直接穿透到 MySQL,把数据库连接池打满。请说明你会怎么改缓存层的读逻辑,以及你选这个方案放弃了什么。 · 3-5 年
共 46 道,这里列出前 20 道。 去题库检索
针对你的简历,生成一套这个方向的题
题目会挂回你简历里的项目,并按真实面试顺序追问。