阶段3(下):RAG、向量数据库专题;接入 BOSS直聘调研(research/、岗位对照和延伸技术栈);新增 cs 领域和 12 个技术栈骨架;面试题全部补齐追问并加入校验

This commit is contained in:
Shikui Wang committed 2026-10-23 15:56:03 +08:00
1 parent 7f52513ffc
commit 6650f7eb6d
52 files changed
+2777 -38

No files matched your search

+18
View File
@@ -18,38 +18,56 @@ week: [14, 16]
**参考答案**:CI(持续集成)是开发者频繁合并代码,每次合并自动执行检查、测试和构建,尽早发现集成问题;CD 包括持续交付(每次变更都自动产出可发布的产物,发布由人触发)和持续部署(自动发布到生产)。
**追问**:持续交付和持续部署有什么区别?→ 持续交付:每次合并后都能随时发布,但上线需要人工确认;持续部署:通过所有检查后自动上线。
### 【基础】一条典型的流水线包括哪些阶段?
**参考答案**:代码检查(格式、lint、类型检查)→ 单元测试和集成测试 → 构建(编译、打包、构建镜像)→ 安全扫描(依赖、镜像、密钥)→ 部署到测试环境 → (审批)→ 部署生产 → 部署后验证(健康检查、冒烟测试)。
**追问**:单元测试和集成测试放在流水线的哪个位置?→ 快的检查放前面(格式、静态检查、单元测试),失败尽早停止;集成测试和镜像扫描放在后面,可以并行。
### 【基础】流水线里的密钥怎么管理?
**参考答案**:存放在 CI 平台的 Secrets 或专门的密钥管理系统中,以环境变量注入,不写进仓库和镜像;按环境和最小权限划分;不要在日志里打印;来自外部 fork 的合并请求默认拿不到密钥;泄露后立即轮换。
**追问**:第三方 Action / 插件有什么安全风险?→ 可能读取到流水线的密钥。固定到具体的提交哈希或可信版本,最小化令牌权限,优先使用 OIDC 短期凭证。
## 进阶
### 【进阶】蓝绿发布、滚动发布、金丝雀发布有什么区别?
**参考答案**:滚动发布逐台替换实例,资源占用少,但新旧版本会同时在线,回滚也要逐台;蓝绿发布准备一整套新环境,验证后一次性切换流量,回滚即切回,但需要双倍资源;金丝雀发布先把小比例流量导到新版本,观察错误率等指标后逐步扩大,风险最小,需要流量控制和监控能力。
**追问**:金丝雀发布怎么判断能否继续放量?→ 对比新旧版本的错误率、延迟、关键业务指标,设定阈值;异常时自动或人工回滚。
### 【进阶】怎么让数据库变更也能回滚?
**参考答案**:迁移脚本版本化,由工具执行;每次变更保持向后兼容,使新旧代码都能在新表结构上运行;破坏性变更拆成多次发布:先扩展(加字段 / 表),再迁移数据和切换读写,最后确认不会回滚后再收缩(删旧字段)。大表变更用在线 DDL 工具,并提前在预发环境演练。
**追问**:删除字段为什么要分多次发布?→ 旧版本代码还在读这个字段,回滚时也需要它;先让代码不再使用,确认稳定后再删除字段(扩展 - 收缩模式)。
### 【进阶】流水线太慢,怎么优化?
**参考答案**:缓存依赖(按锁文件哈希作为 key);任务并行;按改动路径只运行相关部分;Docker 构建利用层缓存;把慢的端到端测试放到合并后或定时执行;拆分测试并行运行;用更快的执行机器。
**追问**:依赖缓存的 key 怎么设计?→ 用锁文件的哈希(`go.sum`、`uv.lock`、`package-lock.json`),依赖变化时缓存自动失效。
## 场景题
### 【场景题】新版本上线后错误率升高,你怎么处理?
**参考答案**:先止血:确认是否与本次发布相关(时间点吻合),是的话立即回滚到上一个镜像标签,或关闭对应的特性开关;同时保留日志和错误信息。恢复后再定位原因,补充测试,复盘为什么没有在测试阶段发现。前提是平时就有一键回滚的能力,并且数据库变更向后兼容。
**追问**:回滚之后还要做什么?→ 确认指标恢复;保留现场(日志、版本、变更内容);复盘根因,补充测试或告警,防止再次发生。
### 【场景题】你们团队的发布流程是怎样的?(如果之前是前端,可以怎么回答)
**参考答案**:按真实情况讲流程:合并请求触发 lint / 测试 / 构建,评审通过且检查通过后合并;合并后自动构建产物并部署到测试环境;生产发布由负责人触发,发布后观察监控,有问题回滚。前端可以讲:构建产物上传 CDN、`index.html` 不缓存、带哈希的资源长缓存、通过切换入口文件回滚。再补充你在转岗项目里实现的后端流水线(本专题的示例)。不要编造没做过的经历。
**追问**:没有参与过发布流程怎么办?→ 如实说明前端的发布方式(构建、上传静态资源、CDN 刷新、回滚),再讲在学习项目中搭建的后端流水线和自己的设计考虑。
### 【场景题】线上运行的是哪个版本的代码?怎么确认?
**参考答案**:镜像标签使用提交哈希或语义化版本,部署记录里保存每次部署的标签;服务在健康检查或版本接口里返回构建时注入的版本号和提交哈希(Go 用 `-ldflags "-X main.version=..."`);日志和错误上报里带上版本号。这样出问题时能准确定位到代码。
**追问**:镜像标签为什么不用 `latest`?→ `latest` 会变化,无法确定线上具体是哪个版本,回滚也不可靠;用 Git 提交哈希或版本号做标签。
+20
View File
@@ -18,42 +18,62 @@ week: [3, 14, 16]
**参考答案**:虚拟机虚拟化硬件,每个虚拟机有自己完整的操作系统内核,隔离性强但启动慢、占用大;容器共享宿主机内核,靠 Linux 的 namespace 做隔离(进程、网络、文件系统等)、cgroups 限制资源,启动快、占用小,但隔离性比虚拟机弱。
**追问**:容器的隔离靠什么实现?→ Linux 的 namespace(隔离进程、网络、文件系统等视图)和 cgroups(限制 CPU、内存等资源);容器共享宿主机内核,隔离程度弱于虚拟机。
### 【基础】镜像和容器是什么关系?镜像为什么要分层?
**参考答案**:镜像是只读模板,容器是镜像运行起来的实例,在镜像之上加了一个可写层。分层让多个镜像可以共享相同的底层(节省磁盘和下载),构建时没有变化的层可以复用缓存。
**追问**:修改 Dockerfile 中间的一行,为什么后面的层都要重新构建?→ 每一层的缓存依赖它之前的所有层,前面的层变了,后面的缓存全部失效。所以把变化少的步骤(安装依赖)放在前面。
### 【基础】`CMD` 和 `ENTRYPOINT` 的区别?`COPY` 和 `ADD` 呢?
**参考答案**:`ENTRYPOINT` 指定容器启动时固定执行的程序;`CMD` 提供默认参数或默认命令,`docker run` 后面的参数会覆盖 `CMD`。两者都推荐使用 exec 形式(JSON 数组),程序作为 PID 1 才能收到信号。`COPY` 只复制本地文件;`ADD` 还能自动解压 tar 和下载 URL,行为不直观,一般用 `COPY`。
**追问**:`ENTRYPOINT` 用 shell 形式会有什么问题?→ shell 形式由 `/bin/sh -c` 启动,进程不是 PID 1,收不到 SIGTERM,无法优雅退出;应该使用 exec 形式(JSON 数组)。
### 【基础】容器里的数据怎么持久化?
**参考答案**:容器可写层的数据随容器删除而消失。持久化用数据卷(Docker 管理的命名卷,适合数据库)或绑定挂载(把宿主机目录挂进容器,适合开发时挂源码、挂配置文件)。
**追问**:命名卷和绑定挂载怎么选?→ 数据库数据用命名卷(由 Docker 管理,可移植);开发时挂载源代码用绑定挂载。
## 进阶
### 【进阶】怎么减小镜像体积、加快构建?
**参考答案**:多阶段构建,运行阶段只保留产物;选小的基础镜像(slim、alpine、distroless);把变化少的步骤(安装依赖)放前面,充分利用层缓存;合并 RUN 并在同一层清理缓存(如 `pip install --no-cache-dir`);用 `.dockerignore` 排除无关文件。
**追问**:多阶段构建为什么能减小体积?→ 编译工具和中间产物留在构建阶段,最终镜像只复制编译结果,基础镜像也可以用更小的 distroless / alpine。
### 【进阶】`docker stop` 时发生了什么?怎么做优雅退出?
**参考答案**:Docker 向容器的 PID 1 发送 SIGTERM,等待超时(默认 10 秒)后发送 SIGKILL。程序要监听 SIGTERM,停止接受新请求、处理完进行中的请求、关闭连接后退出。前提是程序本身是 PID 1(exec 形式启动),否则信号被 shell 吞掉。
**追问**:程序没有处理 SIGTERM 会怎样?→ 等待超时(默认 10 秒)后被 SIGKILL 强制杀掉,正在处理的请求会中断。
### 【进阶】Compose 中服务之间怎么通信?`depends_on` 能保证依赖服务已就绪吗?
**参考答案**:同一个 Compose 项目的服务在同一个网络里,用服务名作为主机名访问。`depends_on` 默认只保证启动顺序,不保证就绪;要配合 `healthcheck` 和 `condition: service_healthy`,应用本身也要有连接重试。
**追问**:怎么让应用等数据库真正就绪?→ 数据库配置 `healthcheck`,应用的 `depends_on` 使用 `condition: service_healthy`;应用本身也要有连接重试。
## 场景题
### 【场景题】容器启动后马上退出,怎么排查?
**参考答案**:`docker ps -a` 看退出码,`docker logs <容器>` 看输出。常见原因:主进程是后台进程(容器以前台进程为生命周期)、配置或环境变量缺失导致程序报错退出、二进制和基础镜像不兼容(CGO 程序放进 scratch)、权限问题(非 root 用户无权读文件)。可以用 `docker run -it --entrypoint sh 镜像` 进入容器手动执行排查。
**追问**:容器反复重启,日志一闪而过怎么办?→ 已退出的容器日志仍然可以用 `docker logs 容器ID`(`docker ps -a` 找到 ID)或 `docker compose logs 服务名` 查看;`docker inspect` 看退出码;也可以临时把启动命令改成 `sleep infinity` 进入容器手动启动程序排查。
### 【场景题】应用容器连不上数据库容器,怎么排查?
**参考答案**:确认连接地址用的是服务名而不是 localhost;两个容器是否在同一网络(`docker network inspect`);数据库是否真的就绪(健康检查、日志);端口是否是容器内端口而不是宿主机映射端口;用户名密码和数据库名是否和初始化时一致(数据卷已存在时环境变量不会重新生效)。
**追问**:容器里的 `localhost` 指的是什么?→ 指容器自己,不是宿主机也不是其他容器;连接其他容器要用服务名,连接宿主机的方式随平台不同(如 `host.docker.internal`)。
### 【场景题】怎么避免把密钥打包进镜像?
**参考答案**:不在 Dockerfile 里写密钥,不把 `.env` 等文件复制进镜像(`.dockerignore` 排除);运行时通过环境变量或密钥管理系统(Kubernetes Secret、云厂商的密钥服务)注入;构建时确实需要的密钥(如私有依赖仓库的 token)用 BuildKit 的 `--secret` 挂载,不会留在镜像层里;镜像推送前用扫描工具检查。
**追问**:构建时需要私有仓库的令牌怎么办?→ 使用 BuildKit 的 `--secret` 挂载,令牌只在构建步骤中可见,不会写入镜像层。
+20
View File
@@ -18,42 +18,62 @@ week: [15, 16]
**参考答案**:merge 把两条分支的历史合并,可能产生合并提交,保留真实的并行开发历史;rebase 把当前分支的提交在目标分支最新提交之后重新应用,历史变成一条直线,但会生成新的提交哈希。公共分支不要 rebase,个人功能分支可以用 rebase 同步主干、整理提交。
**追问**:什么时候不能用 rebase?→ 已经推送到共享分支、别人基于它开发的提交不要 rebase,否则会改写别人依赖的历史。
### 【基础】`git revert` 和 `git reset` 有什么区别?
**参考答案**:revert 生成一个新提交来抵消指定提交的改动,不改写历史,适合撤销已推送的提交;reset 移动分支指针到指定提交,`--soft` 保留改动在暂存区、`--mixed` 保留在工作区、`--hard` 丢弃改动,会改写历史,只适合未推送的本地提交。
**追问**:已经推送的提交想撤销用哪个?→ 用 `git revert` 生成一个反向提交,不改写历史;`reset` 后强推会影响其他人。
### 【基础】`git fetch` 和 `git pull` 的区别?
**参考答案**:fetch 只下载远程的最新提交,更新远程跟踪分支(如 `origin/main`),不改动本地分支;pull = fetch + merge(或配置为 rebase),会把远程改动合并到当前分支。习惯先 fetch 看清楚差异再决定合并方式。
**追问**:`git pull --rebase` 有什么好处?→ 本地提交会接在远程最新提交之后,避免产生多余的合并提交,历史更清晰。
### 【基础】你们用什么分支模型?
**参考答案**:按真实情况回答,并说明原因。例如 GitHub Flow:main 始终可发布,功能分支通过合并请求、CI 和评审后合并,合并后自动部署测试环境。有固定版本周期的(App、私有化交付)用 Git Flow 的 release / hotfix 分支。持续部署成熟的团队用主干开发 + 特性开关。
**追问**:分支模型怎么选?→ 持续部署的团队适合主干开发(短期功能分支 + 快速合并);有固定版本发布周期的适合 Git Flow 类的发布分支模型。
## 进阶
### 【进阶】误执行了 `git reset --hard`,提交还能找回吗?
**参考答案**:只要曾经提交过就能找回。`git reflog` 记录了 HEAD 的每次移动,找到 reset 之前的提交哈希,`git reset --hard 哈希` 或 `git branch 新分支 哈希` 恢复。删除的分支同理。但从未提交、也没有 stash 的工作区改动无法通过 reflog 找回。
**追问**:reflog 会一直保留吗?→ 不会,默认未引用的记录大约 30 天、可达的 90 天后被清理(可配置),而且 reflog 只在本地。
### 【进阶】`--force` 和 `--force-with-lease` 有什么区别?
**参考答案**:`--force` 无条件用本地覆盖远程分支;`--force-with-lease` 只有当远程分支仍然是你上次获取时的状态才覆盖,如果别人在这期间推送了新提交就会拒绝,避免误覆盖同事的工作。改写过历史的个人分支推送时应该用后者。
**追问**:强推前还应该做什么?→ 先 `git fetch` 并确认远程没有别人的新提交;重要分支应该设置保护,禁止强推。
### 【进阶】什么时候用 cherry-pick?有什么注意事项?
**参考答案**:需要把某个提交单独应用到另一个分支时,例如热修复同时带到 release 和 main、把某个修复回移到旧版本。注意 cherry-pick 会生成新的提交(不同哈希),如果之后两个分支合并,可能出现重复改动或冲突;大量 cherry-pick 说明分支策略可能有问题。
**追问**:cherry-pick 后两个分支有相同修改的不同提交,合并时会冲突吗?→ 内容相同通常能自动合并,但历史上是两个不同的提交;修改有差异时可能冲突。
## 场景题
### 【场景题】有人把密钥提交到了远程仓库,怎么处理?
**参考答案**:第一步立即作废并轮换密钥、检查是否被滥用;然后删除文件并加入 `.gitignore`;视情况用 `git filter-repo` 清理历史并强制推送,通知所有人重新克隆,联系平台清理缓存;最后加上提交前和 CI 中的密钥扫描。清理历史不能代替轮换。详见[热修复和密钥误提交场景](scenarios/hotfix-and-leaked-secret.md)。
**追问**:从历史中删除了密钥,还需要做什么?→ 必须吊销(轮换)这个密钥,因为它可能已经被克隆或抓取;然后检查是否有异常使用记录。
### 【场景题】线上版本有紧急 bug,但 main 上已经有很多未发布的功能,怎么修?
**参考答案**:从线上版本的标签或 release 分支拉热修复分支,修复并补测试,走 CI 后合入 release 分支发布补丁版本;再把修复 cherry-pick 或合并回 main,保证下个版本包含修复。不要从 main 拉分支修复,否则会把未测试的功能带上线。
**追问**:修复之后 main 上怎么办?→ 把修复合并回 main(或 cherry-pick),避免下一次发布时问题重新出现。
### 【场景题】合并时遇到大量冲突,怎么处理?
**参考答案**:先理解双方各自改了什么(`git log`、`git diff` 看两边的提交),逐个文件解决,不要一键全选某一方;解决后运行测试;冲突太多时可以和对方一起解决。根本上要避免长期不合并的分支:功能分支保持短小、经常同步主干。
**追问**:怎么减少冲突?→ 小步提交、频繁合并主干;避免同时大规模格式化;冲突较多的文件按模块拆分。
+15
View File
@@ -0,0 +1,15 @@
---
id: devops.k8s.index
title: Kubernetes 基础
type: index
domain: devops
stack: [k8s]
tags: []
week: [14]
---
# Kubernetes 基础
Pod、Deployment、Service、Ingress、配置与密钥、探针、滚动发布。
> 本专题建设中:课程、企业场景、面试题和示例代码会陆续补充。
+20
View File
@@ -18,42 +18,62 @@ week: [14, 16]
**参考答案**:`ss -lntp | grep 8080`(或旧命令 `netstat -lntp`、`lsof -i :8080`),输出里有进程名和 PID。没有 root 权限时可能看不到其他用户进程的信息。
**追问**:`ss` 和 `netstat` 的区别?→ `ss` 更新、更快,直接从内核获取信息;`netstat` 在很多新系统上默认不再安装。
### 【基础】怎么实时查看日志?怎么查找包含某个关键字的行?
**参考答案**:`tail -f app.log` 实时跟踪;`grep -n "ERROR" app.log` 查找并显示行号,`grep -C 5` 显示上下文,`grep -r` 递归目录;systemd 服务用 `journalctl -u 服务名 -f`。
**追问**:日志文件很大,怎么只看某个时间段?→ 用 `sed -n '/开始时间/,/结束时间/p'` 或 `awk` 按时间字段过滤;systemd 服务用 `journalctl --since --until`。
### 【基础】`kill`、`kill -9`、`kill -15` 有什么区别?
**参考答案**:`kill` 默认发送 SIGTERM(15),进程可以捕获它做清理后退出;`kill -9` 发送 SIGKILL,不能被捕获,进程立即被杀死,来不及释放资源。应该先 SIGTERM,等待超时后再 SIGKILL。
**追问**:为什么不建议直接使用 `kill -9`?→ 程序无法清理资源(关闭连接、刷新缓冲、删除临时文件),可能导致数据不完整;先发 SIGTERM,等待超时后再用 -9。
### 【基础】软链接和硬链接的区别?
**参考答案**:硬链接是同一个 inode 的另一个文件名,删除原文件后硬链接仍能访问,不能跨文件系统、不能链接目录;软链接是一个单独的文件,内容是目标路径,原文件删除后软链接失效,可以跨文件系统、可以链接目录。部署中常用软链接切换版本(`current -> releases/v2`)。
**追问**:删除源文件后,软链接和硬链接会怎样?→ 软链接失效(指向的路径不存在);硬链接仍然可以访问数据,因为数据要等到所有硬链接都删除后才释放。
## 进阶
### 【进阶】服务器负载很高,你怎么排查?
**参考答案**:`uptime` 看 load 和核数对比;`top` 看 CPU 的 `us`(用户态计算)、`sy`(内核态)、`wa`(IO 等待);`wa` 高查磁盘(`iostat -x`、`iotop`),`us` 高找占用最高的进程和线程(`top -H -p`),再用语言工具(Go pprof、py-spy)定位到函数;同时看内存是否在用 swap。
**追问**:负载高但 CPU 使用率低是什么情况?→ 负载包含等待 I/O(D 状态)的进程,可能是磁盘 I/O 瓶颈或 NFS 等存储问题,用 `iostat`、`vmstat` 查看 `wa`。
### 【进阶】TIME_WAIT 和 CLOSE_WAIT 过多分别说明什么?
**参考答案**:TIME_WAIT 出现在主动关闭连接的一方,大量出现通常是频繁创建短连接,应该复用连接(keep-alive、连接池)。CLOSE_WAIT 表示对方已关闭、本方还没关闭,大量出现基本是程序没有关闭连接(例如 Go 没有 `resp.Body.Close()`),属于代码 bug。
**追问**:CLOSE_WAIT 一般是谁的问题?→ 通常是自己程序的问题:对方已经关闭连接,自己没有调用 close(例如 HTTP 响应体没有关闭)。
### 【进阶】磁盘满了怎么处理?
**参考答案**:`df -h` 确认哪个分区满,`df -i` 检查 inode 是否用尽(大量小文件);`du -sh /* | sort -h` 逐层找大目录;常见是日志、临时文件、Docker 镜像和容器日志。删除正在被进程写的文件不会释放空间,用 `lsof +L1` 查找,清空文件内容或重启进程。长期方案:logrotate、Docker 日志大小限制、磁盘使用率告警。
**追问**:删除了大文件但磁盘空间没有释放,为什么?→ 文件还被进程打开着,用 `lsof +L1` 或 `lsof | grep deleted` 找到进程,重启进程或清空文件。
## 场景题
### 【场景题】Go 服务内存持续上涨,最后被系统杀掉,怎么定位?
**参考答案**:先在内核日志确认是 OOM(`journalctl -k | grep -i oom`);服务开启 pprof,在内存上涨过程中多次采集 heap,对比哪些对象在增长(`go tool pprof -inuse_space`、`-base` 对比);同时看 goroutine 数量是否持续增长(goroutine 泄漏也会占内存)。常见原因:无上限的缓存或 map、goroutine 阻塞泄漏、读取大文件 / 大响应到内存。修复后加内存告警和 `MemoryMax` 限制。
**追问**:怎么确认是被 OOM Killer 杀掉的?→ `dmesg -T | grep -i oom` 或 `journalctl -k` 查看内核日志;容器中看退出码 137 和 OOMKilled 状态。
### 【场景题】用户反馈接口偶尔很慢,但你登录服务器时一切正常,怎么办?
**参考答案**:需要事后可查的数据:Nginx 日志记录 `$request_time` 和 `$upstream_response_time`,用 awk 找出慢请求的时间点和接口;应用日志带请求 ID 和每一步耗时;数据库开慢查询日志;看云监控在对应时间点的 CPU、IO、网络曲线。根据时间点关联定时任务、备份、其他服务的批处理。长期方案是接入指标监控和链路追踪。
**追问**:怎么让偶发问题留下证据?→ 增加带请求 ID 的结构化日志和慢请求日志;监控历史指标(CPU、GC、连接数);在问题出现时自动保存现场(如 pprof 数据)。
### 【场景题】怎么把一个 Go 程序部署成开机自启、崩溃自动重启的服务?
**参考答案**:编写 systemd 单元文件:指定运行用户、工作目录、环境变量文件、`ExecStart`、`Restart=on-failure`、`RestartSec`、停止超时;`systemctl daemon-reload`、`enable --now`;日志用 `journalctl -u` 查看。程序本身要处理 SIGTERM 优雅退出。也可以用 Docker 的 `restart: unless-stopped` 实现类似效果。
**追问**:日志写到哪里?→ systemd 服务输出到标准输出,由 journald 收集,用 `journalctl -u 服务名` 查看;需要长期保存时配置日志轮转或采集到日志平台。
+20
View File
@@ -18,42 +18,62 @@ week: [7, 14, 16]
**参考答案**:正向代理代表客户端访问外部服务器,服务器看到的是代理(例如公司上网代理);反向代理代表服务器接收客户端请求,客户端看到的是代理,不知道后端有哪些服务器。Nginx 常用作反向代理,做负载均衡、TLS 终止、静态资源、限流。
**追问**:反向代理除了负载均衡还能做什么?→ HTTPS 终止、静态资源服务、缓存、限流、隐藏后端地址、统一的访问日志。
### 【基础】Nginx 的负载均衡策略有哪些?
**参考答案**:轮询(默认)、加权轮询(`weight`)、最少连接(`least_conn`)、IP 哈希(`ip_hash`,同一客户端固定到同一后端)、通用哈希(`hash $key`,可配合一致性哈希)。通过 `max_fails`、`fail_timeout` 做被动健康检查。
**追问**:怎么让同一个用户总是访问同一台后端?→ `ip_hash` 或一致性哈希(`hash $cookie_xxx consistent`);更好的做法是让服务无状态,会话存到 Redis。
### 【基础】location 的匹配优先级是怎样的?
**参考答案**:先看 `=` 精确匹配;再找最长前缀匹配,如果它带 `^~` 就直接使用;否则按顺序匹配正则(`~`、`~*`),第一个匹配的生效;没有正则匹配就用最长前缀匹配。
**追问**:`location /api` 和 `location /api/` 有什么区别?→ 前者也会匹配 `/apixyz`;配合 `proxy_pass` 时,结尾斜杠还会影响路径是否被替换。
### 【基础】前端单页应用部署到 Nginx 后刷新页面 404,为什么?
**参考答案**:前端路由(history 模式)的路径在服务器上没有对应文件。用 `try_files $uri $uri/ /index.html;` 让找不到的路径都返回 index.html,交给前端路由处理。
**追问**:`try_files` 会影响 API 请求吗?→ 如果 API 也在同一个 server 中,要给 `/api/` 单独写 location,否则不存在的 API 路径会返回 index.html 而不是 404。
## 进阶
### 【进阶】502 和 504 有什么区别?分别怎么排查?
**参考答案**:502 Bad Gateway 是 Nginx 连接后端失败或后端返回了无效响应——后端没启动、端口错、进程崩溃、连接被拒绝,看 Nginx 错误日志里的 `connect() failed` 等信息。504 Gateway Timeout 是后端在超时时间内没有响应——后端处理慢(慢查询、调用外部服务卡住),或者 `proxy_read_timeout` 设置太短(例如大模型长回答)。
**追问**:怎么区分是 Nginx 的问题还是后端的问题?→ 看 Nginx 的 `error.log`(connect refused / upstream timed out)和访问日志中的 `$upstream_response_time`、`$upstream_status`。
### 【进阶】代理 SSE 和 WebSocket 分别要注意什么?
**参考答案**:SSE 要关闭代理缓冲(`proxy_buffering off` 或后端返回 `X-Accel-Buffering: no`),否则流式内容被攒成一大段;读超时要放宽或者后端发心跳。WebSocket 需要 `proxy_http_version 1.1` 并传递 `Upgrade` 和 `Connection` 头完成协议升级,读超时也要调大。
**追问**:WebSocket 连接一段时间后断开是什么原因?→ `proxy_read_timeout` 默认 60 秒,没有数据传输就会断开;调大超时或在应用层发送心跳。
### 【进阶】Nginx 怎么限流?
**参考答案**:`limit_req_zone` 定义按某个 key(如 `$binary_remote_addr`)的请求速率,基于漏桶算法;`limit_req` 应用到 location,`burst` 允许排队的突发量,`nodelay` 让突发请求不排队立即处理;超出返回 `limit_req_status`(默认 503,常改为 429)。`limit_conn` 限制并发连接数。前面还有负载均衡时要用 `real_ip` 模块取真实客户端 IP。
**追问**:`burst` 和 `nodelay` 是什么意思?→ `burst` 允许突发的排队数量;`nodelay` 让突发的请求立即处理而不是按速率排队,超过 burst 的请求直接被拒绝。
## 场景题
### 【场景题】AI 问答的流式输出经过 Nginx 后变成一次性返回,怎么解决?
**参考答案**:Nginx 默认对代理响应做缓冲。给流式接口单独 location,设置 `proxy_buffering off`、`proxy_cache off`,HTTP/1.1 长连接,`proxy_read_timeout` 调大;后端响应头加 `X-Accel-Buffering: no`;确认 gzip 没有压缩 `text/event-stream`;如果前面还有 CDN 或云负载均衡,也要确认它们支持流式。详见[AI 应用网关场景](scenarios/ai-gateway-sse-canary.md)。
**追问**:除了 Nginx,还有哪些地方可能缓冲?→ CDN、云厂商的负载均衡、应用框架的中间件(如压缩中间件)都可能缓冲,要逐层检查;后端可以设置 `X-Accel-Buffering: no` 响应头。
### 【场景题】要让 10% 的用户先用新版本,Nginx 怎么做?
**参考答案**:用 `split_clients` 根据用户标识(如用户 ID Cookie 或 IP)哈希出 10% 映射到新版本 upstream;或者 `upstream` 中用 `weight` 按比例分配;指定用户体验用 `map $cookie_xxx`。注意同一用户要稳定落在同一版本(按用户标识哈希,而不是随机)、新旧版本接口兼容、能一键回滚。
**追问**:按百分比随机分流和按用户分流哪个好?→ 按用户(如用户 ID 的哈希)更好,同一个用户的体验一致,也便于排查问题;用 `split_clients` 对用户标识做分流。
### 【场景题】怎么做到发版时用户无感知(不中断请求)?
**参考答案**:后端多实例滚动发布:先把一台实例从 upstream 摘除(或标记 `down`)并 reload Nginx,等它处理完存量请求后升级,再加回去,逐台进行;后端程序要支持优雅退出。Nginx 自身修改配置用 `nginx -s reload`,旧 worker 处理完已有连接再退出,不会中断请求。
**追问**:`nginx -s reload` 会中断连接吗?→ 不会:新 worker 使用新配置处理新请求,旧 worker 处理完已有请求后退出。但长连接(WebSocket)会在旧 worker 退出时断开(`worker_shutdown_timeout` 控制)。