6619 字
33 分钟
八股文手册:系统设计、架构、Python、Agent

项目题之外,面试还有一类「通用八股」:设计、架构、Python 语言机制、Agent 概念。这一篇是我的整理,每题按 一句话答案 → 展开 → 陷阱/追问 三层组织——一句话用来接住问题,展开用来证明真懂,陷阱用来防翻车。

《简历答辩手册》《Agent 与 RAG 工程 22 问》互补:那两篇是项目和 LLM 工程纵深,这篇是横向通用题。


第一部分:设计与系统架构#

1. API 接口设计要注意什么?#

一句话:资源用名词、动作用 HTTP 方法、错误有码有文案、变更保持向后兼容。

展开

  • URL 是名词/tasks/{id}/results,不是 /getTaskResults;层级表达从属关系;
  • 方法表达动作:GET 幂等可缓存、POST 创建/动作、PUT 全量替换、PATCH 局部更新、DELETE 幂等删除;
  • 错误响应结构化:HTTP 状态码表大类(400 参数错/401 没登录/403 没权限/404 不存在/409 状态冲突/422 语义错),body 里给业务错误码 + 人类可读 message,客户端按码分支、人按文案排查;
  • 向后兼容:只加字段不删字段、不改字段语义;破坏性变更走新版本路径(/v2/)并行一段时间;
  • 分页必须做:列表接口不分页 = 给未来埋雷;
  • 参数校验前置:在入口层(Pydantic/schema)挡住脏数据,业务层假设输入合法。

陷阱:「POST 也能查询为什么还要 GET」——幂等性和缓存语义不同;GET 请求被网关/浏览器/CDN 缓存是特性不是 bug。

2. 什么是幂等?怎么实现?#

一句话:同一请求执行一次和执行 N 次效果相同;实现靠唯一键约束、去重表或状态机条件更新。

展开(三板斧):

  • 天然幂等UPDATE t SET status='done' WHERE id=1(重复执行结果不变);
  • 唯一键:业务单号建唯一索引,重复插入直接冲突——把「查重+插入」的竞态交给数据库原子性解决;
  • 条件更新(乐观锁)UPDATE ... WHERE version=旧值,affected=0 说明被别人改过,重试或报错。

为什么重要:网络超时后客户端必然重试、消息队列必然至少投递一次(at-least-once)、定时任务可能重复触发——下游不幂等,上游就必须精确一次,而精确一次做不到,只能「至少一次投递 + 幂等消费」。

陷阱SELECT 判断存在再 INSERT 不是幂等实现,是竞态窗口;两个并发请求都查到不存在,都插入。

3. 缓存怎么设计?一致性怎么保证?#

一句话:Cache-Aside 是默认模式——读先缓存后 DB,写先更新 DB 再删缓存;一致性靠「删缓存 + 过期时间」双保险,不追求强一致。

展开

  • 为什么删缓存而不是更新缓存:更新缓存有并发写覆盖问题(A、B 同时写,B 先落 DB 但 A 后写缓存 → 脏数据),删除让下次读重建,天然收敛;
  • 为什么先更 DB 再删缓存:反过来(先删缓存再更 DB)在删除后、更新前有读请求进来,会把旧值重新灌进缓存;
  • 极端不一致的兜底:过期时间(TTL)——就算删缓存失败,最多脏 TTL 这么久;
  • 三大经典问题
    • 缓存穿透(查不存在的 key 打到 DB)→ 空值缓存(短 TTL)或布隆过滤器;
    • 缓存击穿(热 key 过期瞬间大量请求打 DB)→ 互斥重建(只放一个请求去查 DB)或逻辑过期;
    • 缓存雪崩(大量 key 同时过期)→ TTL 加随机抖动、多级缓存。

陷阱:「先更 DB 再删缓存」仍有理论上的不一致窗口(读请求在删除前读到旧缓存),但概率极低且被 TTL 兜底——面试时说清「业务能接受多长时间的不一致」比背方案更加分。

4. 消息队列解决什么问题?有什么代价?#

一句话:异步化(提交方不等执行)、削峰(突发流量排队消费)、解耦(生产消费互不感知);代价是复杂度——至少一次投递带来的重复消费、顺序性、积压监控都要自己处理。

展开

  • 什么时候用:耗时长且调用方不需要立即拿结果(转码、报表、通知);流量尖峰明显(秒杀下单);
  • 什么时候不用:同步链路短平快的场景硬上 MQ 是给自己加戏——多一次网络、多一份运维、多一类故障。我们平台选择「同步提交 + 数据库任务表」而不是 MQ:任务量级不大,DB 表本身就能当队列用(状态字段 + 轮询),少一个组件少一类故障;
  • 必答题:消息丢了怎么办 → 生产端确认(ack)、消费端手动提交位点、消费逻辑幂等(回到第 2 题);
  • 必答题:消息重复怎么办 → 消费幂等,不指望 broker 精确一次;
  • 必答题:积压怎么办 → 先看消费慢还是生产暴增;扩消费者(注意分区数上限)、跳过可丢弃的消息、或临时转储后离线补。

陷阱:「MQ 能保证顺序吗」——只有单分区/单队列内有序;需要全局有序就得单分区,等于放弃并行度。

5. 分布式锁怎么做?Redis 锁有什么坑?#

一句话SET key value NX PX ttl,value 放唯一标识,释放时用 Lua 脚本「先比对再删」;坑在于 TTL 与业务时长不匹配、主从切换丢锁。

展开

  • 为什么 value 要唯一:防止误删——A 的锁过期后被 B 拿到,A 干完活直接 DEL 会把 B 的锁删掉;Lua 保证「比对 value + 删除」的原子性;
  • TTL 两难:太短业务没跑完锁先没了,太长持锁方崩溃后别人干等 → 看门狗续期(业务未完成就周期性续 TTL);
  • Redlock 争议:多节点多数派加锁,Martin Kleppmann 的批评点是 GC 停顿/时钟漂移下不安全,antirez 有回应——面试提一句「知道这个争论」就够,工程上单实例 Redis 锁 + 幂等兜底是常见选择;
  • 锁的正确定位:锁是性能优化(避免重复干活),正确性应该由幂等写保证(就算锁失效、两边同时干,结果也对)。

陷阱:「Redis 挂了怎么办」——两个流派:降级无锁继续跑(可用性优先)或直接报错(正确性优先)。选哪个取决于业务:重复执行的代价大就 fail-stop。

6. 微服务拆分的边界怎么定?#

一句话:按业务能力(domain)拆而不是按技术层拆;一个服务对应一块能独立演进、独立部署的业务边界;拆分的信号是团队规模与发布互相阻塞,不是「代码多了」。

展开

  • 拆分依据:领域边界(用户/订单/库存)、变更频率差异大的模块、需要独立扩容的模块(CPU 密集的推理网关 vs IO 密集的业务服务);
  • 拆分的代价:分布式事务(本地事务没了)、跨服务调用链排查、数据一致性从「同库外键」变成「最终一致 + 对账」;
  • 反对过早拆分:单体 + 模块化(清晰的内部分层)能撑很久;拆出去的服务间如果总要一起改、一起发布,说明边界划错了;
  • 数据归属:一个表只属于一个服务,别的服务只能通过 API 访问——共享库是微服务最常见的腐化路径。

陷阱:「分布式事务怎么做」——优先答避免:把需要强一致的操作圈进同一个服务;跨服务用 Saga/本地消息表做最终一致 + 对账补偿,不要上来就 2PC。

7. 系统变慢了,怎么排查?#

一句话:先定位层次(客户端/网关/应用/DB/依赖),再定位类型(CPU/IO/锁等待/GC),工具跟着假设走,不靠猜。

展开(分层二分):

  1. 看监控面板:延迟是整体涨还是个别接口涨?P99 涨还是均值涨?什么时候开始的?——变更(发布/配置)是最常见原因,先查发布记录;
  2. 应用层:CPU 高 → profile(py-spy / arthas)找热点函数;CPU 不高但慢 → 大概率在等 IO 或锁,看线程栈;
  3. DB 层:慢查询日志、EXPLAIN 看执行计划——索引失效、全表扫描、N+1 是三大常客;
  4. 依赖层:下游服务/缓存变慢会级联上来,看调用链(trace)里每一跳的耗时;
  5. 资源层:连接池打满(等连接)、线程池打满(排队)、内存不足触发频繁 GC。

加分句:先问「影响面多大、什么时候开始、有没有变更」三个问题,一半的性能事故在这一步就有答案。

陷阱:上来就说加机器——扩容解决不了 N+1 和锁竞争,只是把问题摊薄。


第二部分:Python 八股#

8. 列表和元组的区别?什么时候用元组?#

一句话:列表可变、元组不可变;元组用于异构的固定结构(一行记录、函数多返回值)、作字典 key、以及表达「不该被改」的语义。

展开:不可变带来的实际收益——可哈希(能进 set/dict key)、可安全共享(不怕被调用方改)、略省内存。同构集合用 list,异构记录用 tuple(更进一步用 NamedTuple/dataclass(frozen=True))。

9. 深拷贝和浅拷贝?#

一句话:浅拷贝只复制最外层容器,内层对象共享引用;深拷贝递归复制整棵对象树。

展开

a = [[1, 2], [3, 4]]
b = a.copy() # 或 list(a)、a[:],浅拷贝
b[0].append(99) # a[0] 也变了!内层列表是同一个对象
import copy
c = copy.deepcopy(a) # 完全独立

陷阱b = a 不是拷贝,是绑定同一个对象;deepcopy 能处理循环引用(内部有 memo 字典),但对大对象很慢——性能敏感场景手动控制共享。

10. 装饰器的原理?写一个带参数的装饰器。#

一句话:装饰器是「接收函数、返回新函数」的高阶函数,@deco 只是 f = deco(f) 的语法糖;带参数就多包一层。

展开

import functools, time, logging
def retry(times=3, delay=1): # 最外层:接收装饰器参数
def decorator(func): # 中层:接收被装饰函数
@functools.wraps(func) # 保留原函数 __name__/__doc__
def wrapper(*args, **kwargs): # 内层:真正的包装逻辑
for i in range(times):
try:
return func(*args, **kwargs)
except Exception:
if i == times - 1:
raise
logging.warning("%s%d次失败,%ds后重试", func.__name__, i+1, delay)
time.sleep(delay)
return wrapper
return decorator
@retry(times=5)
def call_api(): ...

必提 functools.wraps:不加的话被装饰函数的元信息丢失,影响调试、文档工具和依赖签名的框架(如 FastAPI 的参数注入)。

陷阱:装饰器在模块导入时执行(注册),wrapper 在调用时执行——分清这两层能解释很多「为什么打印顺序不对」。

11. 生成器和迭代器的区别?yield 有什么用?#

一句话:迭代器是实现 __iter__/__next__ 协议的对象;生成器是用 yield 写出来的迭代器语法糖——自动实现协议,且天然惰性。

展开

def read_big_file(path):
with open(path) as f:
for line in f: # 文件对象本身就是惰性的
yield line.strip()
# 一次只在内存里放一行,10GB 文件也能处理
  • 惰性求值:不遍历就不计算,处理大文件/大数据流/无限序列的标配;
  • yield from:委托给子生成器;
  • 生成器的进阶身份是协程send() 能向里传值——asyncio 的 async/await 前身就是生成器协程。

陷阱:生成器只能消费一次,消费完再迭代是空的;需要复用就转 list(代价是失去惰性)。

12. GIL 是什么?Python 多线程是不是没用?#

一句话:GIL 是 CPython 解释器的全局锁,同一时刻只有一个线程执行 Python 字节码;所以 CPU 密集用多进程,IO 密集多线程/协程依然有效——IO 等待时会释放 GIL。

展开

  • IO 密集(网络请求、文件读写、DB 查询):线程在等 IO 时 GIL 是放开的,其他线程照跑——爬虫、网关类应用多线程收益真实存在;
  • CPU 密集(计算、编解码):多进程 multiprocessing(各自独立解释器和 GIL),或者把热点交给 C 扩展/numpy(它们计算时释放 GIL);
  • 协程 asyncio:单线程内切换,没有线程开销,高并发 IO(几千连接)首选;
  • 选型口诀:并发数大且纯 IO → asyncio;IO 为主但生态是同步库 → 线程池;CPU 重 → 进程池/C 扩展。

加分句:知道 free-threaded Python(PEP 703,3.13 起的可选无 GIL 构建)正在推进,但生态兼容还需要时间。

13. asyncio 的核心概念?async/await 到底发生了什么?#

一句话:事件循环 + 协程——async def 定义协程函数,调用它只得到协程对象不执行;await 是「挂起点」,把控制权交还事件循环去跑别的任务,IO 就绪后恢复。

展开

import asyncio
async def fetch(url):
async with session.get(url) as resp: # await 点:挂起,让别人跑
return await resp.json()
async def main():
# 并发发 100 个请求,不是串行
results = await asyncio.gather(*(fetch(u) for u in urls))
# 限流:Semaphore(10) 控制同时在飞的数量
asyncio.run(main())
  • 并发 ≠ 并行:单线程内交错执行,靠的是「遇到 IO 就切走」;
  • gather vs 循环 await:循环里逐个 await 是串行;gather/create_task 才是并发;
  • 超时与取消asyncio.timeout()、task.cancel()——不处理超时的网络调用会挂死整个任务;
  • 最大的坑:同步阻塞调用混进协程——一个 time.sleep() 或同步 requests 会卡住整个事件循环(所有人一起等),要用 run_in_executor 包起来或换异步库。

陷阱:CPU 密集代码放进协程同样卡循环——asyncio 只救 IO 等待,不救计算。

14. __init____new__ 的区别?单例怎么写?#

一句话__new__ 创建并返回实例(类方法),__init__ 初始化已创建的实例(返回 None);先 __new____init__

展开:日常 99% 只碰 __init____new__ 用于继承不可变类型(int/str/tuple)或控制实例创建过程。Pythonic 的单例:

class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

但更推荐模块级单例(模块只会被 import 一次)或依赖注入——__new__ 单例在多线程下还要加锁。

15. 内存管理:引用计数、垃圾回收、内存泄漏?#

一句话:CPython 以引用计数为主(归零立即释放)、分代 GC 处理循环引用为辅;「泄漏」在 Python 里多半是意外的强引用(全局容器、缓存无上限、循环引用带 __del__)。

展开

  • 分代回收:对象按存活时间分三代,越年轻扫描越频繁——基于「大部分对象很快死掉」的统计假设;
  • 常见泄漏源:模块级 list/dict 只进不出、functools.lru_cache 用在实例方法上(缓存持有 self)、大对象被异常 traceback 引用;
  • 排查工具tracemalloc(标准库,追踪分配点)、objgraph(找引用链)、gc.get_referrers()
  • 大内存优化:生成器替代列表、__slots__ 减少实例字典、及时 del + 注意 numpy 视图持有原数组。

16. ==is 的区别?可变默认参数为什么是坑?#

一句话== 比值(调用 __eq__),is 比身份(同一个对象);默认参数在函数定义时求值一次,可变对象会被所有调用共享。

展开

def bad(items=[]): # 这个 [] 只创建一次!
items.append(1)
return items
bad() # [1]
bad() # [1, 1] ← 上次的还在
def good(items=None):
items = [] if items is None else items

is 只用于 None/True/False 和哨兵对象比较;小整数缓存(-5~256)会让 is 对数字出现「碰巧为真」——不要依赖。

17. dataclass、Pydantic、TypedDict 怎么选?#

一句话:纯内部数据结构用 dataclass;跨边界(API 入参/配置)要校验用 Pydantic;只是给 dict 标类型(不改运行时行为)用 TypedDict。

展开

  • dataclass:标准库、零开销、frozen=True 可得不可变与可哈希;不做任何校验;
  • Pydantic:运行时校验 + 类型转换 + 序列化三件套,FastAPI 的请求体/响应模型全靠它;v2 核心是 Rust 写的,性能比 v1 高一个量级;
  • TypedDict:静态检查器(mypy)用的注解,运行时就是普通 dict——处理外部 JSON 又不想引入模型类时用。

18. FastAPI 的请求处理流程?Depends 是什么机制?#

一句话:ASGI 服务器(uvicorn)收请求 → FastAPI 按路由匹配 → 解析并校验参数(Pydantic)→ 依赖注入树(Depends 递归求解,带缓存)→ 调 endpoint(async 直接跑,def 扔线程池)→ 响应模型序列化返回。

展开

  • Depends 的本质:声明式的「前置步骤」,可嵌套、可复用、按请求作用域缓存(同一个请求里同一个依赖只求解一次)——数据库会话、当前用户、分页参数都是典型依赖;
  • yield 依赖:yield 前是 setup、后是 teardown——get_db() 的经典写法,请求结束自动关会话;
  • def vs async def endpoint:FastAPI 把同步 endpoint 放进线程池避免阻塞事件循环(对应第 13 题的坑,框架替你兜了一半);
  • 中间件 vs 依赖:中间件管所有请求(CORS、trace id),依赖管单个路由的可复用逻辑(鉴权、取会话)。

19. 多进程之间怎么通信?#

一句话:Queue/Pipe(消息)、共享内存(multiprocessing.shared_memory、大数组用 Array/manager)、或者干脆走 Redis/DB 等外部存储——工程上最常见的是最后一种。

展开:进程间没有共享内存(fork 后写时复制),一切通信都要序列化/传输,所以:小数据用 Queue,大数据用共享内存避免 pickle 开销,跨机器就只能外部存储。选型问题和线程通信本质一样——能用消息传递就不要用共享状态


第三部分:Agent / LLM 八股#

20. 什么是 Agent?和「调一次 LLM API」的区别?#

一句话:Agent = LLM(决策)+ 工具(行动)+ 循环(观察结果再决策),直到任务完成;单次 API 调用是一问一答,Agent 是多轮「想-做-看」闭环。

展开

任务 → LLM 决策(下一步做什么)→ 调用工具 → 结果回填上下文
↑ │
└──────────── 循环,直到判定完成 ────────┘
  • 核心组件:规划(拆解任务)、工具调用(function calling)、记忆(上下文管理 + 外部存储)、反思(根据工具结果修正);
  • 什么时候不需要 Agent:固定流程用 workflow(确定性编排)更好——能用 if-else 解决的不要交给模型自由发挥;Agent 适合路径不确定的开放任务。

陷阱:「Agent 是不是越自主越好」——自主性每加一级,可控性和可测试性掉一级;生产系统通常是「workflow 骨架 + 局部 LLM 决策」。

21. Function Calling 的原理?模型真的「执行」了函数吗?#

一句话:没有。模型只是输出一段结构化 JSON(函数名 + 参数),执行由你的代码完成,结果再拼回上下文给模型看——模型是「点菜的」,你是「炒菜的」。

展开

  1. 请求里带上工具 schema(名称、描述、JSON Schema 参数定义);
  2. 模型判断需要工具时,返回 tool_calls(函数名 + 参数 JSON)而不是普通文本;
  3. 你的代码解析、校验参数、执行、把结果以 tool 角色消息回填;
  4. 模型基于结果继续生成(可能再调工具,循环回到第 20 题)。

工程要点:工具描述就是 prompt——描述写得烂,模型选错工具/编错参数;参数必须校验(模型会幻觉出不存在的枚举值);并行无依赖的工具调用可以一批发出(省轮次);失败结果也要如实回填(模型能据此换路子)。

22. RAG 的完整链路?每一环的常见失败模式?#

一句话:文档 → 切块 → 向量化 → 入库;查询 → 改写 → 检索(向量 + 关键词混合)→ 重排 → 拼 prompt → 生成。每一环都会漏,效果差先定位环节再调参。

展开(失败模式对照):

  • 切块:块太大噪声多、太小语义断裂——按结构(标题/段落)切优于按字数硬切;
  • 向量化:embedding 模型和业务语料不匹配(专业术语召回差);
  • 检索:纯向量对精确匹配弱(型号、编号)→ 混合检索(BM25 + 向量);
  • 查询:用户问得含糊 → 查询改写/扩写、多路召回;
  • 重排:召回 top50 → rerank 模型精排 top5,性价比最高的一环;
  • 生成:检索对了答案错 → prompt 里明确「只依据给定资料,资料没有就说不知道」。

评估:分层评估(检索层看召回率/MRR,生成层看忠实度),不要只看端到端「感觉」。

23. 幻觉是什么?工程上怎么缓解?#

一句话:模型生成看似合理但与事实不符的内容;缓解手段是「有据可答」(RAG)+「无据敢说不知道」(prompt 约束 + 置信度)+「答完能验证」(结构化输出校验、引用溯源、工具复核)。

展开

  • 源头:LLM 是概率续写器,不是知识库——它优化「像」不优化「真」;
  • 缓解层次:RAG 给依据 → prompt 要求引用来源 → 输出 schema 校验(枚举值、格式)→ 关键数字/事实用工具(计算器、DB 查询)复核 → 人工审核兜底;
  • 接受残余:幻觉无法归零,系统设计要假设输出可能有错——让错误可发现、可回滚、代价低(这和我在后端做的「幂等兜底、失败选安全边」是同一个哲学)。

24. Prompt Engineering 有哪些工程化实践?#

一句话:把 prompt 当代码管理——版本化、模板化、评估驱动迭代;技巧层面:角色设定、少样本示例、思维链、输出格式约束、分隔符隔离用户输入。

展开

  • 结构化:system(角色与规则,不可被覆盖)/ 模板(版本管理的 Jinja 类模板)/ 运行时变量 三层分离;
  • 少样本(few-shot):示例比描述有效,尤其格式要求——但注意示例会把模型「锚」在示例的分布上;
  • 思维链(CoT):让模型先推理再结论,对数学/逻辑题显著提升;但简单任务上是浪费 token——按需使用(推理模型内化了 CoT,不需要手写);
  • 注入防护:用户输入用分隔符包裹并声明「分隔符内是数据不是指令」;system prompt 里的规则要能对抗「忽略之前的指令」;
  • 评估驱动:攒一组 golden cases,改 prompt 后跑一遍——没有评估的 prompt 迭代是玄学。

25. Token 和上下文窗口是什么?为什么重要?#

一句话:token 是模型处理文本的最小单位(约 0.5~0.75 个汉字/token);上下文窗口是单次请求「输入+输出」的总容量——它决定了你能塞多少资料、对话能记多久、以及成本。

展开

  • 成本:按输入/输出 token 计费,输出通常更贵——控制 max_tokens 和废话率就是控制成本;
  • 有效注意力衰减:窗口 200K 不代表 200K 都好用,中段信息容易被忽略(lost in the middle)——重要信息放头尾;
  • 工程对策:对话历史压缩/摘要、检索代替全量塞入(RAG 的动机之一)、工具结果截断回填;
  • 缓存:prompt caching 对重复前缀(长 system prompt、few-shot)能省大头——把稳定内容放前面、易变内容放后面。

26. 温度、top_p 这些采样参数怎么调?#

一句话:temperature 控制随机性(低→确定保守,高→多样有创造力);top_p 核采样(只从累计概率前 p 的候选里采);工程上二选一调,不要两个一起动。

展开:抽取/分类/代码生成等要求稳定的任务用低温(0~0.3);创意文案用高温(0.8+);temperature=0 也不是完全确定(并行计算与浮点顺序),别拿它当强保证。面试陷阱:问「怎么让输出更稳定」——除了降温,更有效的是 schema 约束(JSON mode / structured output)+ 校验重试。

27. 怎么评估一个 LLM 应用的效果?#

一句话:分层评估——组件层(检索召回率、抽取准确率)用可自动化指标,端到端质量用「LLM-as-judge + 人工抽检」,上线后靠用户反馈与日志回流持续迭代。

展开

  • 先建 golden set:几十到几百条有标准答案的真实 case,是一切评估的地基;
  • 组件层可精确:检索层 hit@k/MRR、分类层 P/R/F1——端到端效果差时靠这些定位是哪一环的问题;
  • LLM-as-judge:用强模型按 rubric 打分,注意位置偏差(AB 交换再评)和自恋偏差(偏爱自家模型的输出);
  • 线上闭环:点赞点踩、人工改后的结果回流成新 golden case——评估集是活的。

28. MCP 是什么?解决什么问题?#

一句话:Model Context Protocol——Anthropic 提出的开放协议,标准化「LLM 应用如何连接外部工具和数据源」,把 M 个模型 × N 个工具的集成问题变成 M + N。

展开

  • 类比:USB-C 之于外设——工具方实现一次 MCP server,任何支持 MCP 的客户端(Claude、IDE、自建应用)都能即插即用;
  • 三种能力:tools(可调用的动作)、resources(可读取的数据)、prompts(可复用的提示模板);
  • 和 function calling 的关系:function calling 是模型侧的接口(模型怎么表达「我要调工具」),MCP 是传输侧的协议(工具怎么被发现和调用)——互补不冲突;
  • 安全:MCP server 是第三方代码/服务,供应链风险真实存在——生产接入要审查 server 来源、限制其权限面。

附:答题策略#

  1. 一句话先接住:任何八股题先用一句话给结论,证明你知道,再展开——不要一上来铺细节;
  2. 展开带自己的例子:背方案人人都会,「我们平台当时就是这么踩的坑」才是区分度;
  3. 主动交代边界:说清方案的适用条件和代价(缓存一致性窗口、MQ 的复杂度、GIL 的适用范围),比假装方案完美更加分;
  4. 不知道的题:说清知道的部分 + 推理不知道的部分(「我没用过 X,但它解决的问题和 Y 类似,我猜它是这么做的…」)——展示思维方式本身就是答案。
八股文手册:系统设计、架构、Python、Agent
https://gilgameshzzz.github.io/posts/interview-fundamentals-design-python-agent/
作者
Amadeus
发布于
2026-09-21
许可协议
CC BY-NC-SA 4.0