Skip to content
掌握度

按简历五条核心职责组织的专项题库,每题带完整答案与演练状态。(演练状态见文末总览表)。配套:专项 · CodeAgent CLI 题库、专项 · 实习题库、自我介绍。

第一部分 · Bullet 1:分片上传与断点续传 ​

1. 「删除一个文件的完整流程是什么?删除顺序有什么讲究?」 ​

完整答案:

先过两道权限门:组织标签过滤器做资源级校验,控制器里再做属主判断——不是文件主人也没有管理员角色返回 403,文件不存在返回 404。然后是五层顺序删除,顺序是有讲究的:第一层先删 ES 里的向量文档——按文件指纹一个删除查询清掉全部,让检索立刻下线,这是用户感知最强的一层,必须最先处理;第二层删对象存储里的合并文件,删失败降级试旧路径、再失败也继续;第三层清 PDF 单页预览的缓存,本地缓存加 Redis 两层都要失效;第四层删 MySQL 的两张表——文本块和分片元数据,分片元数据必须删,否则同一个文件重新上传时分片去重逻辑会误判「这分片传过了」而实际内容已经没了;最后才删文件登记记录——它是「这个文档存在」的权威依据,先删它、后面任何一步失败,这个文档就彻底失联了。

事务语义分两层:MySQL 这几张表包在一个事务里原子提交;ES 和对象存储不在事务保护内,是尽力而为——失败场景下可能出现「记录还在、内容没了」的中间态,但这个状态稳定、可重试:删除操作本身幂等,用户再点一次删除就能对齐,不需要分布式事务。

2. 「文件内容更新了怎么办?你们是『指纹即身份』,替换一个文档要做什么?」 ​

完整答案:

当前模型是「指纹即身份」:文件内容变了 MD5 就变,那就是一个新文件、新记录,旧文件需要用户显式删除——删除会同时清 ES、对象存储、MySQL 三层,所以不存在同名文件内容悄悄过期的问题。

经过幂等改造之后,流水线其实已经「更新友好」了:对同一文件重新解析向量化是安全的覆盖语义——ES 文档 ID 是文件指纹加块序号的确定性 ID,重跑是覆盖不是追加,管理端的重建索引就是证明。

真正缺的是产品级的就地更新:用户想替换文档内容但保留文档身份(引用、收藏不断)。这需要引入业务文档 ID 作为一层新身份,让文件指纹降级为版本概念——涉及一张新表和若干服务的身份解析改造,是明确的演进方向但当前没有做。现阶段「删旧传新」两个操作就能覆盖更新诉求,成本收益上我选择先不做——知道缺什么、为什么暂时不补,比假装它没有更重要。

第二部分 · Bullet 2:Kafka 异步流水线 ​

3. 「流水线里四种不同性质的故障,最后是怎么统一收敛的?」 ​

完整答案:

消费端的失败是统一的失败链:任何一环异常,状态置失败、原因入库,然后重抛给 Kafka 错误处理器做退避重试四次,耗尽进死信。这条链我实际验证过四种不同性质的故障,最终都收敛到同一个状态字段:解析引擎缺失报的进程启动错误、密钥为空的 401 认证错误、ES 僵死的写超时、余额不足的预检拦截——能从库里的错误信息一路剥到根因。

这条失败链的价值:用户能看到失败状态和原因,不用对着转圈猜;管理员能按状态字段筛选处理,不用翻日志;重试和死信的语义对所有故障类型一致,不用为每种故障写一套处理。收敛的关键设计是「状态字段 + 错误原因」都落在文件记录上,四种故障共享同一个收敛点。

4. 「同一条消息重复消费、处理到一半失败重试,ES 会不会出现重复数据或脏残留?」 复检(第二场 8 分) ​

完整答案:

不会,幂等是这条流水线的设计前提,两层机制配合。

写入层:ES 文档 ID 不是随机生成的,是文件指纹加块序号的确定性 ID——同一文件的同一分块,无论写多少次,落到的都是同一个文档 ID,重复消费的写入变成覆盖而不是追加,天然幂等。

流程层:消费端每次重新解析前,先清掉上一次运行的部分产物——旧的文本块和 ES 文档——所以处理到一半失败触发 Kafka 重试时,整条链是在干净的地面上重跑,不会和上次残留叠加。

这两层还互为兜底:就算清理那一刻 ES 或对象存储抖动导致清理失败,写入层的覆盖语义也能保证最终不重复。管理端重建索引用的是同一套语义——清干净再写、写了就覆盖。一条原则管三个场景:重复消费、失败重试、索引重建。

第三部分 · Bullet 3:混合检索 ​

5. 「chunk 是怎么切的?为什么这么切?」 复检(第一场 7 分,行动项:补父子块) ​

完整答案:

切块是结构感知的分级切分,不是无脑定长。文本先按段落切,段落超过上限——512 个 token——再降级按句子切,句子还超再到词,目标始终是语义完整的内容待在同一个块里。512 是权衡:块太大,一个块混多个主题,向量表示被稀释、检索精度降;太小,语义碎片化,召回了也缺上下文。相邻块保留一部分重叠,因为切句会切断指代——「它」「上述规定」指的东西可能被切到两块里。

两个增强。第一,每个块带页码和锚点:PDF 走页级解析,块知道自己在第几页哪一段——这是回答能溯源到「第 X 页」的地基,和引用机制是同一条线;切块前还先滤掉页眉页脚、图片说明这类噪音,扫描件走 OCR 兜底。第二,父子块:检索时用子块匹配保精度,命中后把所在父块喂给模型保上下文——检索粒度和生成粒度分离。当前实现是页级切块带锚点,父子块是明确的演进方向,等评测数据显示「命中了但不够用」时上。

背后是同一个判断:切块的所有决策都要往下游看——元数据服务溯源和权限过滤,块大小服务向量质量和上下文完整。怎么切不是解析器的事,是检索和生成两个下游倒逼出来的。

6. 「检索的权限过滤,为什么放在查询层而不是查出来再在应用层过滤?」 ​

完整答案:

权限过滤放在 ES 查询的 filter 子句里:不参与打分、在查询层生效——越权的文档根本不会进入候选集,这和「检索完再在应用层过滤」有本质区别。应用层过滤有三个问题:一是浪费打分开销——越权文档照样参与召回和排序;二是结果不可控——topN 里越权的占多数时,用户拿到的有效结果变少甚至为空;三是语义污染——越权内容参与向量相似度计算,可能把相关度排序带偏。filter 子句既安全又省。

用户的有效组织标签是登录时按层级展开、缓存在 Redis 里的,列表接口和检索共用同一份缓存——保证「列表看得见的,检索一定搜得到」,两边不会出现权限口径错位。kNN 向量检索同样是 filter 感知的:图遍历时带上 userId 和组织标签条件,所以纯语义召回也一样过权限。

第四部分 · Bullet 4:流式问答与上下文 ​

7. 「用户连发两条消息、中途点停止、页面直接关了——分别怎么处理?」 必复检(第二场忘记) ​

完整答案:

三个难题同一个解法:每条消息创建一个 generationId,所有异步路径都围绕这个可查询、可取消的状态对象转。

连发两条:任务级隔离——每条消息独立的 generationId、独立生成任务和 Redis 快照,互不干扰,停止只停那一个。连接级:推送按 userId 路由到当前 WebSocket,写同一连接用 synchronized 串行化——WS 会话非线程安全。容量级:生成线程池有界(4 核心 16 上限),满则显式拒绝回「系统繁忙」,不挂起连接线程。

点停止:带内部令牌(防伪造)和 generationId 的 WS 指令,服务端标记取消并 cancel 上游模型流——真取消,token 立刻停止计费;已生成的半截回答正常收尾落库。等待模型响应用 200ms 短轮询而非无限阻塞,取消和超时亚秒级感知。

页面直接关闭:靠生成快照兜底——Redis 30 分钟过期的状态对象,模型吐出的每个片段三路同步:内存累积全文、Redis 快照追加、WebSocket 推前端。重连后凭 generationId 查快照,完整记录在 MySQL 历史页找得回。已知边界:注册表是单连接模型,第二个标签页顶掉第一个的推送,但生成任务不受影响——改进方向是用户到连接集合的广播。

记忆口诀:generationId 贯穿三层——任务级隔离靠它,停止要带它,断线凭它找快照。

8. 「流式回答的推送具体怎么实现的?模型吐一个字前端就动一下,后端做了什么?」 ​

完整答案:

模型吐出的每个片段做三路同步:内存累积全文、Redis 生成快照实时追加、WebSocket 推给前端——一次生成,三份状态同步推进。价值各管一头:内存全文用于收尾落库;Redis 快照支撑断线重连续看和停止后保留半截回答;WebSocket 让用户边看边等。

整个链路是异步的:ReAct 循环丢进独立线程池执行,WebSocket 处理线程立刻释放,不会被几十秒的模型流阻塞。前端收到 chunk 追加渲染,打字机效果;生成完成后发完成通知,把引用映射一起推过去,半截回答正常收尾落库——先写 MySQL 全量(含引用映射),成功才更新 Redis 的窗口。

工程细节:写同一个 WebSocket 连接用 synchronized 串行化——WS 会话非线程安全;生成线程池有界,满了显式拒绝。

第五部分 · Bullet 5:权限与可用性 ​

9. 「大模型这个外部依赖的可用性,你做了哪几层?和标准熔断器差在哪?」 必复检(三次未闭环,本次需脱稿) ​

完整答案:

四层,从用户侧往依赖侧排。第一层兜底回复:LLM 调用异常统一捕获,用户看到「AI 服务暂时不可用,请稍后重试」的友好提示,不是 5xx 堆栈。第二层功能降级:embedding 不可用时检索自动退化成纯 BM25 加同样的权限过滤——功能保留、语义能力降级。第三层多维限流:IP 级、账号级、每日次数、token 余额四道闸,把故障流量挡在依赖调用之前。第四层供应商热切换:密钥失效或服务劣化时管理端秒级切备用模型,密钥加密落库、不用重启。

和标准熔断器的差距我清楚:没有失败率的自动统计,没有熔断打开和半开探测——切换是运维手动触发、重试是固定退避。演进方向是按依赖维度引入 Resilience4j:失败率过阈值自动打开,冷却后放少量请求探测,恢复自动闭合——把「人盯着切」变成「系统自己收敛」。

记忆口诀:兜底管体面、降级管功能、限流管流量、热切管供应商。差距一句话:熔断器是自动的,我的是手动的。

10. 「组织级数据隔离具体怎么实现的?层级组织标签怎么算用户的权限?」 ​

完整答案:

多租户隔离基于层级组织标签。管理员创建带父子结构的组织标签,给用户分配标签;用户的有效标签是分配标签按层级展开的所有子级——比如拥有「华东区」就自动拥有「华东区-销售」。层级继承在登录时计算、Redis 缓存,列表接口和检索共用同一份缓存,保证「列表看得见的,检索一定搜得到」。

文件上传时打上权限三元组(组织标签、可见性、属主),随文本块一起写入 ES——每个块自包含权限信息。权限判断统一在一个过滤器里:公开资源直接放行,默认组织或无标签放行,私有标签仅限所有者和管理员,其他情况比对用户的有效标签集合和资源的标签。

多级管控分三层:角色鉴权管接口层(普通用户 / 管理员),资源级校验管单个操作(属主判断),查询层过滤管检索边界(ES filter 子句)——三层各管一段,任何一层被绕过都还有下一层。文件上传、删除、权限变更都会记录日志,出异常能追溯。

演练状态总览 ​

题号主题状态
1五层顺序删除未考
2指纹即身份与文档更新未考
3四种故障收敛未考
4幂等两层机制复检(第二场 8 分)
5chunk 切分复检(第一场 7 分,补父子块)
6查询层权限过滤未考
7generationId 三难题必复检(第二场忘记)
8三路同步流式未考
9稳定性四层必复检(第三次靠查)
10组织标签权限体系未考

本站仅供个人学习使用,请勿外传