写在前面

欢迎大家关注Rocky的知乎:Rocky Ding
《三年面试五年模拟》AIGC/LLM/AI Agent算法工程师/开发工程师求职面试秘籍独家资源:【三年面试五年模拟】WeThinkIn/AIGC-Interview-Book,欢迎大家Star~
Rocky最新撰写的10万字AI Agent(AI智能体)深入浅出全维度解析文章: 深入浅出完整解析AI Agent(AI智能体)的核心基础知识
大家好,我是Rocky。
本题单用于一面备考练习,包含多个面试记录中的问题,不对应同一场面试实录。
这组题把 Agent 放回一条完整的产品链路:用户提出目标,服务端执行任务,前端持续展示结果,系统在断线、超时和重复消息之后仍能恢复。Rocky认为,全栈研发的区分度在于能否把模型、协议和业务状态连接成可验证的执行过程。
下面沿用题目的三个分组,围绕机制、工程边界和验证方法展开。涉及项目的示例均用于说明设计,实际面试应替换为本人可核实的经历。
目录
- [项目 / Agent](#项目 / Agent)
- [1. 介绍一个 Agent 项目,从用户输入到结果展示走一遍,最难的点是什么?](#1. 介绍一个 Agent 项目,从用户输入到结果展示走一遍,最难的点是什么?)
- [2. 意图不在预设范围里怎么办?什么时候追问,什么时候兜底?](#2. 意图不在预设范围里怎么办?什么时候追问,什么时候兜底?)
- [3. 长对话怎么压缩?如何避免把关键约束一起压掉?](#3. 长对话怎么压缩?如何避免把关键约束一起压掉?)
- [4. 多 Agent 怎么分工?规划器和执行器之间传哪些信息?](#4. 多 Agent 怎么分工?规划器和执行器之间传哪些信息?)
- [流式交互 / 服务端](#流式交互 / 服务端)
- [5. SSE 断线后怎么恢复?重连时如何避免消息重复?](#5. SSE 断线后怎么恢复?重连时如何避免消息重复?)
- [6. 工具调用参数分段返回,前端怎么拼?半截 JSON 和断流怎么处理?](#6. 工具调用参数分段返回,前端怎么拼?半截 JSON 和断流怎么处理?)
- [7. 消息队列里的结果怎么对应原请求?为什么不用数据库轮询?](#7. 消息队列里的结果怎么对应原请求?为什么不用数据库轮询?)
- [8. Tool Calling、MCP、Rules、Skills 有什么区别?各自适合用在哪?](#8. Tool Calling、MCP、Rules、Skills 有什么区别?各自适合用在哪?)
- [基础 / 手写](#基础 / 手写)
- [9. RAG 和长期记忆有什么区别?短期上下文、长期记忆分别存什么?](#9. RAG 和长期记忆有什么区别?短期上下文、长期记忆分别存什么?)
- [10. 多用户调用同一个工具,Redis 怎么做限流?限流和限制并发有什么区别?](#10. 多用户调用同一个工具,Redis 怎么做限流?限流和限制并发有什么区别?)
- [11. MySQL 常见日志各管什么?联合索引的使用怎么分析?](#11. MySQL 常见日志各管什么?联合索引的使用怎么分析?)
- [12. 手写一个并发上限为 2 的异步调度器,成功和失败都要释放名额](#12. 手写一个并发上限为 2 的异步调度器,成功和失败都要释放名额)
项目 / Agent
1. 介绍一个 Agent 项目,从用户输入到结果展示走一遍,最难的点是什么?
回答:
先用真实项目说明用户目标、个人职责和端到端链路,再选一个有证据的难点深入。可以按"请求进入与认证 → 会话加载 → 意图识别与参数补全 → 任务编排 → 检索或工具执行 → 结果校验 → 流式展示 → 状态持久化"描述,讲清每个阶段的输入输出,不只列出模型和框架名称。
例如企业资料查询与业务办理助手,可以先从授权文档检索政策,再通过实时接口查询业务状态,必要时由用户确认后执行操作。这个例子用于解释设计,不代表个人真实经历。实际介绍要明确自己负责的是路由、上下文、工具适配、前端流式渲染还是评测,以及哪些能力复用了已有组件。
难点应体现具体约束与修复过程,例如连接断开后重复显示内容,或工具提交成功但超时触发重复操作。解释如何借助 request_id、事件序号和业务幂等键定位、解决问题,分别用断网回放、重复请求和状态读回验证,不把"加了重试"当成完整答案。
收益也要说明口径:任务正确率、人工接管比例、端到端耗时、P95 延迟和每个成功任务成本。没有生产数据就明确是离线或原型结果,不编造用户规模、准确率和提效数字。准备一次失败案例及剩余限制,比只展示顺利运行的演示更有说服力。
把项目讲成一条可追踪的状态链。 一个会话可以包含多个请求,一个请求也可能产生多个执行尝试,因此 session_id、run_id、attempt_id 不能混用;业务幂等键则标识"同一项业务动作",跨网络重试保持稳定。前端收到文本只说明展示在推进,工具已执行、任务已验收要有各自的状态证据。

图 1:一次任务由执行状态与展示事件共同描述。SSE 连接可以重建,任务身份与业务动作身份应保持连续。图中为通用设计示意。
2. 意图不在预设范围里怎么办?什么时候追问,什么时候兜底?
回答:
先区分三类情况:已知能力但表述不清、一个请求包含多个已知意图、真正超出系统能力范围。不能强制把所有输入归入已有分类,否则未知请求可能被错误路由到执行工具。路由结果应允许 unknown、needs_clarification 和 unsupported 等状态,并保留必要的候选意图和缺失槽位。
当补充少量信息就能安全完成任务时追问,例如存在两笔可操作订单、日期指代不明、用户只说"帮我改一下"。追问应具体且最少,明确哪个信息影响下一步;多意图请求先拆解依赖,不要把其中一个不确定子任务扩散为整个流程的猜测。
当请求确实不受支持、没有权限、外部服务不可用或进一步澄清也无法解决时,使用明确的兜底:说明可做范围,提供只读结果、转人工或保存任务等待恢复。兜底不能换成看似相关但未经用户授权的操作。高风险动作即使分类置信度高,也仍需服务端权限、参数和必要确认校验。
置信度阈值通过标注集校准,不能把模型自报概率直接当作正确率。评测同时报告自动处理覆盖率、已自动路由样本的准确率、未知意图识别和错误执行率。连续追问或重路由要有次数与时间上限,避免在不能完成的任务上循环消耗用户耐心。
可以把路由策略做成"候选意图 × 必填槽位 × 权限 × 动作风险"的决策表。例如"把明天的会议取消"已经识别出取消意图,但会议时区、对象和权限未确认,仍然不能执行。评测时把"未知意图被误认为可执行动作"单独统计,这类错误的代价通常高于一次多余追问。置信度只是策略输入,允许执行由完整条件决定。
3. 长对话怎么压缩?如何避免把关键约束一起压掉?
回答:
先把对话区分为原始记录、当前工作状态和本次模型上下文。原始记录保留用于审计与回读;工作状态保存用户目标、硬约束、已确认事实、未完成步骤和关键对象;本次上下文只装入完成当前步骤所需的信息。压缩应减少冗余,而不是把全部历史替换成一段自由发挥的摘要。
可以采用近期消息窗口加结构化摘要,再按需检索旧片段。为系统规则、当前请求、关键状态、工具定义、证据和输出预留 token 预算。用户预算、日期、禁止事项和确认过的操作参数独立保存,附带原始消息定位;长日志、文档和工具结果外置,通过引用回读。
摘要更新要区分事实、推测与待确认事项,保留否定、例外和新旧约束覆盖关系。例如用户把预算从 1000 改到 800,工作状态应更新为 800,不能把两者混写为模糊区间;模型自己推测出的偏好不能升级为用户确认。必要时抽取约束后用确定性字段检查或再次询问。
还要保持工具调用与对应结果的关联。只截掉调用消息、留下孤立结果可能破坏 API 消息协议;正在执行的工具状态、调用 ID 和失败原因也不能随摘要丢失。压缩后对重要决策补读原文证据,不把摘要当成永远准确的事实库。
验证使用长会话回放,在早期放入关键约束,再加入无关信息,检查最终行为是否仍遵守约束。比较压缩前后任务成功率、约束保持、指代一致性、token、延迟与误记忆率,尤其测试多次滚动摘要造成的误差累积。
压缩触发可以用预算不等式表达:当前输入 + 预计新增工具结果 + 输出预留 + 安全余量 ≤ 上下文窗口。其中工具结果大小只是估计,执行层仍需截断、分页或外置大结果。结构化摘要也要保存 state_version 和已消费的消息位置;摘要生成期间若用户修改约束,应校验版本后再提交,防止旧摘要覆盖刚收到的新要求。
4. 多 Agent 怎么分工?规划器和执行器之间传哪些信息?
回答:
多 Agent 的分工应对应清晰能力和权限边界,例如规划、资料查询、工具执行与结果校验。先用单 Agent 或固定工作流建立基线;只有独立任务可以并行、不同能力需要隔离,或验证机制带来实际收益时,拆分才有价值。
规划器传给执行器的是有边界的任务契约:task_id、目标、输入引用、依赖、允许工具与权限、成功标准、时间和成本预算、可重试条件。通常不必传整段聊天记录或模型内部推理,只提供执行所需的上下文和决策依据。执行器返回结构化状态、产物引用、证据、实际副作用以及错误类别。
调度器维护依赖图,独立只读步骤可并行,写操作按业务约束排序。状态由明确的协调者或受控存储更新,避免多个 Agent 随意覆盖共同字段。执行器发现条件不满足时返回需要澄清或重规划的原因,不能自行扩大权限或修改用户目标。
规划器重规划要有触发条件和预算,例如工具不可用、关键证据缺失或用户改变需求;不是每个暂时网络错误都重新规划。部分成功时保留已完成产物,副作用先查询状态再重试,补偿使用预定义业务操作。最终汇总需要消解矛盾并报告未完成项,不能直接拼接多个 Agent 的文本。
工程上可把执行结果分成 succeeded、failed_retryable、failed_final、blocked 和 outcome_unknown。最后一种尤其关键:工具超时且无法判断是否成功时,不能把它当作普通失败再次执行。任务契约还应绑定 base_revision 和 plan_version,让协调者能识别"内容看起来正确,但属于旧计划"的迟到结果。
流式交互 / 服务端
5. SSE 断线后怎么恢复?重连时如何避免消息重复?
回答:
SSE 负责服务端到客户端的事件传输。浏览器原生 EventSource 通常会在连接失败后重连,服务端可发送 id 和 retry 字段,客户端按协议在重连时携带 Last-Event-ID;但这些机制只提供重连线索,不会自动保存历史事件,也不保证业务恰好执行一次。
为每个任务建立稳定 run_id,并给事件分配单调序号或可恢复的日志游标。事件包含类型、message_id、必要的 tool_call_id 和增量内容,先进入可回放日志再发送;重连后验证用户是否仍有权访问该任务,从客户端最后处理的序号之后回放。客户端按事件 ID 去重,避免重复增量被再次追加到文本。
需要区分浏览器最近接收的事件 ID 与应用已经可靠应用的状态。刷新页面或新建 EventSource 时,应用通常需要自行保存游标并通过接口允许的方式传入;使用 fetch 读取流时,重连、游标与 SSE 解析也由应用实现。若需要跨崩溃的强恢复,客户端的已应用游标与消息状态应协调持久化。
为避免"回放结束到订阅实时流之间"丢事件,可以持续从同一持久日志游标读取,或建立带高水位和去重的回放与实时衔接。日志已过保留期时返回明确的重置事件及状态快照,不能假装无缝续传。终态事件也必须可恢复,客户端收到终态后主动关闭连接。
生产环境还要配置代理缓冲、空闲超时、心跳与慢消费者背压。断线不应直接重新提交业务任务,任务创建和结果订阅应解耦;用户取消则通过独立的受控接口传达。测试包括重复事件、断在事件中间、刷新、游标过期和终态丢失,而不只测一次网络恢复。
一次最小可恢复事件可以表示为:
text
id: run-42:108
event: message.delta
data: {"run_id":"run-42","message_id":"m-3","seq":108,"delta":"已查询"}
这里的空行是 SSE 事件边界。客户端确认已经应用序号 108 后,从 109 开始恢复;再次收到 108 应跳过。游标必须绑定任务和用户,序号只需在定义好的事件流内有序,不需要跨所有用户建立全局序号。原生 EventSource 还限制了请求方法和自定义请求头;鉴权方式要结合 Cookie、同源策略或受控的 fetch 流接口设计,不能为了方便把长期凭证放进 URL。

图 2:重连恢复的是事件消费进度。工具是否重试,要由持久任务状态与业务幂等机制另行决定。
6. 工具调用参数分段返回,前端怎么拼?半截 JSON 和断流怎么处理?
回答:
先按供应方的事件协议区分"参数增量"与"完整快照"。若协议发送字符串 delta,就按原顺序追加;若发送累计快照,就替换对应版本,不能一律拼接,否则参数会重复。多个并行工具调用的片段可能交错,需要用 run_id、消息 ID、tool_call_id 或稳定的调用索引分别维护缓冲区。
网络字节块并不等于一个完整 SSE 事件,也不一定包含完整 UTF-8 字符。使用支持流式解码的 TextDecoder,并按 SSE 帧规则组装事件,再解析外层事件 JSON;不能对每次 reader.read() 直接执行 JSON.parse。外层合法事件中的 arguments 字符串仍可能只是半截工具参数。
前端可以显示"参数生成中"和暂存文本,但只在协议明确结束且缓冲区可完整解析后,把参数视为候选对象。不要用正则补引号、补括号后当作最终结果,字符串转义、嵌套对象和数字都会导致误修复。若需要增量预览,可使用明确支持不完整输入的解析器,其结果只能用于预览。
执行应由服务端再次做 schema、权限与业务校验,模型完整生成了 JSON 也不代表操作被授权。断流时将调用标记为 incomplete,通过日志恢复缺失事件,或重新生成一个有新 attempt 标识的参数流;不能把两个不同尝试的片段混合。对已可能执行的写操作先查询状态,不能单凭参数流中断就重做。
还应限制缓冲区大小、调用数量和等待时间,检测重复片段、异常索引以及不一致终态。回放必须共享 SSE 去重策略;工具名称和 ID 若只在首个片段出现,应绑定到后续片段,不在缺字段时创建新的调用。
还要防范协议层的"结束"歧义:传输连接关闭、模型响应完成、某个工具参数完成、工具执行完成,是四种不同事件。服务端最好为参数生成维护独立状态机,只有参数流结束、解析成功、schema 与业务校验全部通过才进入待执行状态;工具结果再驱动执行终态。这样前端不会把"参数已经展示完整"误报为"业务操作已完成"。
7. 消息队列里的结果怎么对应原请求?为什么不用数据库轮询?
回答:
请求进入时生成稳定 request_id 或 run_id,队列任务携带该标识、子任务 ID、尝试次数、用户与租户范围及追踪信息。Worker 输出事件继续携带相同关联字段,接入层据此找到该任务的订阅者和状态;不要依赖队列消息顺序、当前网络连接或模型输出文本去猜原请求。
结果可能晚于连接生命周期,用户断线后任务仍应能完成。队列、任务状态和可回放事件日志承担不同职责:队列调度工作,持久状态保存事实,SSE 把结果推给在线用户。多实例接入层可以通过消息总线通知和持久日志查询恢复,不应只在某台机器内存里保存 request_id 到连接的映射。
消息系统可能至少一次投递,消费者按业务幂等键处理重复,状态转换采用条件更新或事务,写副作用还需工具自身的幂等与对账。确认消息应在必要结果可靠保存之后,避免先 ack 后崩溃造成丢失;数据库更新与消息发布需要考虑事务 outbox 等一致性方案,而不是声称两个系统天然原子提交。
数据库轮询不是永远不能用。低流量、低更新频率和可接受秒级延迟的任务列表可以轮询,实现简单;高频 token 流若每个连接都轮询,会产生大量空查询、连接压力和额外延迟,也难以自然表达实时推送与背压。可根据规模采用事件通知加状态查询,数据库仍保留任务事实。
还需区分结果顺序与完成顺序。多个子任务并发时,由 task_id 和事件序号归组,最终汇合检查必要分支是否完成。监控队列积压、重投次数、处理耗时和孤立任务,超时转入可恢复状态或人工处理,而不是让前端无限等待。
Transactional outbox 的典型做法是在同一个数据库事务里写任务状态和待发送事件,提交后由独立发送器投递消息。发送器可能在"已投递、未标记"之间崩溃,所以消费者仍需去重;outbox 解决的是数据库提交与事件最终发布之间的衔接,不会自动消除消息重复。前端收到推送后仍可按版本读取权威状态,用通知加快更新,用状态解决恢复与对账。
8. Tool Calling、MCP、Rules、Skills 有什么区别?各自适合用在哪?
回答:
Tool Calling 是让模型按约定提出工具名和结构化参数的交互能力。模型生成调用请求,应用负责验证、授权、执行工具并将结果返回。适合查询、计算或业务操作,但工具调用格式合法不代表参数语义正确,也不代表模型有权执行。
MCP 是连接 AI 应用与外部能力的协议体系,采用 host、client、server 等角色,提供工具、资源、提示等能力的发现与交互。Tool Calling 与 MCP 可以组合:应用把服务器提供的工具转换给模型选择,再经客户端调用服务器。MCP 本身不是 Agent 规划算法,也不会自动解决所有授权、幂等与信任问题。这里以 2025-06-18 版协议架构为说明范围,具体传输与扩展应按实际版本确认。
Rules 通常是宿主或项目约定的持续指导与约束,例如代码风格、目录规范、必须满足的验收条件。它不是所有工具共同遵守的统一协议,文件格式、加载范围与优先级取决于宿主。自然语言规则仍需测试和程序校验,不能当作强制的安全边界。
Skills 通常把一类任务的适用条件、执行步骤、参考资料和可选脚本组织成可复用能力包。Agent Skills 文件约定以 SKILL.md 为入口,可以先暴露名称与描述,再按需加载正文和资源。Skill 能指导模型如何使用工具,但不会自动赋予操作权限,也不等同于重新训练模型权重。
例如"导出订单分析报告":规则约束数据访问范围与文件命名;skill 描述查询、汇总和验收步骤;工具提供查询与导出操作;MCP 可以承担这些工具的标准化接入;模型通过 Tool Calling 提出具体调用。各层可独立选择,不必为了具备其中一种能力就全部采用。
面试时可以用四个问题快速定位层次:模型怎样提出调用,对应 Tool Calling;应用怎样接入外部能力,对应 MCP;任务应遵守什么约定,对应 Rules;一类工作怎样按步骤完成,对应 Skills。四者都不能替代服务端访问控制。来自文档、网页或工具返回中的"请忽略规则"属于待处理数据,不能因为经过 MCP 或写进某个文件就自动获得更高指令权限。
基础 / 手写
9. RAG 和长期记忆有什么区别?短期上下文、长期记忆分别存什么?
回答:
RAG 是检索外部信息并用于生成的流程;长期记忆是跨会话保存信息的机制与数据。两者不是同一维度:长期记忆可以用检索增强方式读出,RAG 也可以只查询公共知识库而不包含任何用户记忆。
短期上下文服务于当前任务,包含最近必要对话、目标、硬约束、工具调用及结果、临时推理产物和待完成步骤。它受窗口与成本限制,可以通过状态存储、摘要和按需回读管理,不能简单理解为所有短期信息只能保存在内存中。
长期记忆可以保存经确认的用户偏好、稳定事实、历史任务摘要和可复用经验,但必须有身份范围、证据、时间、版本、过期或撤回机制。一次模型推测不能直接升级为真实用户偏好;今天的临时需求也不应默认长期保存。需要敏感数据最小化和可删除性,跨用户共享必须有明确依据。
RAG 数据准备包括解析、切分、索引和权限元数据,在线按任务检索并检查证据;记忆管理还要解决何时写入、如何覆盖冲突、何时遗忘。关系数据库适合稳定事实与结构化状态,全文或向量索引适合语义候选检索,原始记录用于审计和核实,不必把所有信息都塞入向量数据库。
评测知识 RAG 看召回、证据支持和答案正确性;评测记忆还要看时间一致性、错误写入传播、撤回生效和跨用户隔离。恢复一个长任务时,优先加载权威任务状态,再读取辅助记忆,不让相似但过期的历史记录覆盖当前用户约束。
记忆删除还涉及派生数据:仅删除数据库原记录,向量索引、缓存和旧摘要仍可能继续返回相同事实。应为事实分配稳定 ID,把索引条目和摘要引用关联回原事实,并在读取时检查有效性;需要强撤回时,还要刷新相关缓存或重建摘要。评测可以先写入旧偏好,再修改、撤回并跨会话查询,观察是否仍被旧信息影响。
10. 多用户调用同一个工具,Redis 怎么做限流?限流和限制并发有什么区别?
回答:
限流控制单位时间内允许进入的请求数量或速率,限制并发控制同一时刻正在执行的任务数。每秒允许 10 个请求并不等于最多同时执行 10 个任务;若每个任务执行 30 秒,在持续负载下并发可能远大于 10。稳定系统中平均并发可近似用到达率乘平均处理时间理解,但实际还要考虑排队和尾延迟。
多用户共享工具时,可以同时设置用户、租户和工具全局额度,避免一个用户耗尽公共能力。Redis 可用固定窗口计数、滑动日志或令牌桶:固定窗口简单但边界突发明显,滑动日志精确但内存与清理成本较高,令牌桶允许受控突发。令牌桶根据时间补充 token,扣减通过后才放行,并明确容量与补充速率。
检查、补充、扣减和过期设置应在一个原子脚本或受支持的原子机制中完成,避免 GET 后再 SET 的竞态。脚本应短小,过大计算会阻塞 Redis;Cluster 中多键脚本还受键所在 hash slot 约束,多个层级限额如何原子预留或回退需要单独设计。避免只 INCR 成功却未设置过期导致窗口永不释放。
并发上限可以用带唯一持有者 token 的租约集合:申请时原子清理已到期租约、检查数量并加入新租约,结束时只释放自己的 token。长任务续租;进程崩溃后租约可回收,但租约到期不代表旧任务已经停止,旧任务继续运行会突破真实并发上限。对严格场景要结合实际执行池、取消确认或服务端 fencing,不能把 TTL 当成任务终止证明。
Redis 不可用时按业务风险选择拒绝、降级或受限本地兜底,并明确多实例本地额度无法保证全局严格上限。记录拒绝率、等待时间、实际活跃数和租约异常。限流、并发限制、排队和业务幂等各自解决不同问题,应共同设计而不是互相替代。
两个常见细节是时间与重试。分布式客户端时钟可能漂移,补充 token 的时间最好采用同一可信时间来源,或对负时间差和过大的时间跳变做约束;被拒绝时返回可计算的等待建议,并采用带抖动的重试。即使限流脚本在一个 Redis 主节点上原子执行,异步复制与故障切换仍可能造成额度状态回退,强一致的业务配额需要单独设计,不能只凭"用了 Lua"作出保证。
11. MySQL 常见日志各管什么?联合索引的使用怎么分析?
回答:
以 MySQL 8.4 与 InnoDB 为讨论范围。Redo log 记录恢复数据页所需的修改信息,配合预写日志与检查点支持崩溃恢复;Undo log 支持事务回滚,并为 MVCC 一致性读构造旧版本。Undo 也会被更新与持久化机制管理,不能简单理解成"只在内存里保留旧数据"。
Binlog 位于服务器层,记录用于复制与时间点恢复的变更事件,格式与配置影响其内容。它不是所有 SQL 原文的完整流水,也不能独立替代基础备份;时间点恢复通常需要备份加其后的 binlog。事务提交协调、redo 刷盘与 binlog 同步策略会影响崩溃一致性和持久性,不能说写了其中一个日志就必然可靠提交。
Relay log 用于副本接收并回放复制事件;错误日志用于启动和运行故障;慢查询日志用于定位满足配置条件的慢语句;general query log 可记录连接与语句,但有性能和敏感数据成本。日志可能包含用户数据,采集权限和保留周期应明确,不能为了排障长期无差别开启全部日志。
联合索引从真实 SQL、选择性、排序、覆盖和执行计划分析。例如索引 (tenant_id, status, created_at, id) 适用于某租户某状态下按时间筛选或排序的场景,但具体范围、排序方向和返回列会影响效果。左侧前缀通常决定可直接用于定位的条件;遇到范围条件后,后面的列仍可能用于索引条件下推、覆盖或部分过滤,不能一概说全部失效。
使用 EXPLAIN 检查访问方式、选用索引、估算行数与过滤情况,再在安全环境用实际执行统计核实。EXPLAIN ANALYZE 会执行查询,不是零成本元数据检查。检查隐式转换、函数、前导通配符和统计信息偏差,区分"使用了索引"和"高效定位了少量数据"。优化需权衡回表、排序和写维护成本,不能只追求更多索引。
例如列表查询可以从下面的形态分析索引,而不是背一句"最左匹配":
sql
SELECT id, created_at
FROM tasks
WHERE tenant_id = ? AND status = ? AND created_at >= ?
ORDER BY created_at ASC, id ASC
LIMIT 50;
在 (tenant_id, status, created_at, id) 上,前两列的等值条件限定索引区间,时间条件缩小扫描起点,排序与索引顺序一致时有机会避免额外排序,返回列也具备覆盖条件。是否实际采用仍要看执行计划、数据分布和代价。如果把 status = ? 改为多个状态,结果先按 status 分段,便不能直接沿用"全局按时间有序"的结论。大 offset 分页还需考虑游标分页,不能指望索引让任意深度翻页都变成常数成本。
12. 手写一个并发上限为 2 的异步调度器,成功和失败都要释放名额
回答:
先明确接口:输入是返回值或 Promise 的任务函数数组,而不是已经启动的 Promise 数组;最多同时执行两个任务。所有任务都获得执行机会,最终按输入顺序返回成功或失败结果。单个任务拒绝或同步抛错不终止队列;任务必须最终结束,若需要取消或超时,还要另加协议。
采用两个 Worker 循环共享任务索引。JavaScript 单线程事件循环中,读取并增加 next 的过程没有 await,因此两个 Worker 不会领取同一索引;每个 Worker 同时最多等待一个任务。try/catch 吸收该任务错误,然后继续领取下一个,避免名额泄漏。代码中的 finally 显式归还活跃计数,方便检查不变量。
javascript
async function runWithConcurrency2(tasks) {
if (!Array.isArray(tasks) ||
Array.from(tasks).some(task => typeof task !== "function")) {
throw new TypeError("tasks must be an array of functions");
}
const results = new Array(tasks.length);
let next = 0;
let active = 0;
async function worker() {
while (next < tasks.length) {
const index = next++;
active++;
try {
const value = await tasks[index]();
results[index] = { status: "fulfilled", value };
} catch (reason) {
results[index] = { status: "rejected", reason };
} finally {
active--;
}
}
}
await Promise.all(
Array.from({ length: Math.min(2, tasks.length) }, () => worker())
);
if (active !== 0) throw new Error("scheduler invariant violated");
return results;
}
任务开始顺序按输入领取,完成顺序可以不同;结果写入原索引,因此最终数组保持输入顺序。空数组返回空数组,单任务只启一个 Worker,同步返回值也能处理。调用者应保证调度期间不修改任务数组,任务内部自行启动的后台工作不在该调度器的并发统计范围内。
调度本身对 n 个任务的时间开销为 O(n),结果与输入校验需要 O(n) 空间,Worker 数为常数;总耗时取决于各任务耗时和分配。某个任务永不 settle 会占住一个 Worker,两个都不结束则队列停滞,这需要超时和协作取消设计。
特别注意 Promise.race 超时只会让等待先结束,不会自动终止底层请求。如果超时后立即"释放名额",旧任务仍在运行,真实并发可能超过 2。严格限制实际执行数时,应传入 AbortSignal 并等待任务确认终止,或者由有真实停止能力的执行环境管理;不能把超时 Promise 当成操作已取消的证明。
验证调度器要观察真实任务的进入与退出,而不是只断言最终数组长度。可构造不同耗时、同步抛错、异步拒绝与空队列的任务,记录实际活跃任务的峰值,要求始终不超过 2,且每个任务只启动一次、失败后的后续任务仍能运行。若要支持运行中动态追加任务,需要进一步定义关闭队列与等待新任务的语义,当前有限数组版本不承担这个接口。
推荐阅读
1. 深入浅出完整解析AI Agent(AI智能体)的核心基础知识
2025年可以说是AI Agent全面落地应用的元年,因此Rocky在持续撰写对AI Agent的全维度解析文章:
深入浅出完整解析AI Agent(AI智能体)的核心基础知识
2. 深入浅出完整解析扩散模型DDPM、DDIM、Score-Based、SDE、LDM、Classifier/Classifier-Free Guidance、Rectified Flow核心基础知识
Rocky对扩散模型的本质原理与和核心基础知识进行了全面系统的深入浅出分析讲解,同时不断跟进补充扩散模型的最新技术发展,希望能给大家带来帮助:
深入浅出完整解析扩散模型DDPM、DDIM、Score-Based、SDE、LDM、Classifier/Classifier-Free Guidance、Rectified Flow核心基础知识
3. 入浅出完整解析FLUX.2、Seedream(即梦)、Z-image、GLM-Image核心基础知识
Rocky对AIGC时代"中场时刻"之后的主流AIGC创作大模型的核心基础知识进行了全面系统的深入浅出分析讲解,力求让大家通俗易懂理解AIGC时代的技术浪潮的本质价值:
入浅出完整解析FLUX.2、Seedream(即梦)、Z-image、GLM-Image核心基础知识
4. 深入浅出完整解析FLUX.1 Kontext和FLUX.1 Krea核心基础知识
Rocky对FLUX.1 Kontext和FLUX.1 Krea的核心基础知识作了全面系统的梳理与解析:
深入浅出完整解析FLUX.1 Kontext和FLUX.1 Krea核心基础知识
5. 深入浅出完整解析DeepSeek系列核心基础知识
Rocky对DeepSeek系列模型的核心基础知识作了全面系统的梳理与解析:
6. 深入浅出完整解析Stable Diffusion 3(SD 3)和FLUX.1系列核心基础知识
Rocky对Stable Diffusion 3和FLUX.1的核心基础知识作了全面系统的梳理与解析:
深入浅出完整解析Stable Diffusion 3(SD 3)和FLUX.1系列核心基础知识
7. 深入浅出完整解析Stable Diffusion XL(SDXL)核心基础知识
Rocky对Stable Diffusion XL的核心基础知识作了全面系统的梳理与解析:
深入浅出完整解析Stable Diffusion XL(SDXL)核心基础知识
8. 深入浅出完整解析Stable Diffusion(SD)核心基础知识
Rocky对Stable Diffusion 1.x-2.x系列模型的核心基础知识做了全面系统的梳理与解析:
深入浅出完整解析Stable Diffusion(SD)核心基础知识
9. 深入浅出完整解析Stable Diffusion中U-Net的前世今生与核心知识
Rocky对Stable Diffusion中最为关键的U-Net结构进行了深入浅出的全面解析,包括其在传统深度学习中的价值和在AIGC中的价值:
深入浅出完整解析Stable Diffusion中U-Net的前世今生与核心知识
10. 深入浅出完整解析LoRA(Low-Rank Adaptation)模型核心基础知识
对于AIGC时代中的"ResNet"------LoRA模型,Rocky进行了深入浅出的全面讲解:
深入浅出完整解析LoRA(Low-Rank Adaptation)模型核心基础知识
11. 深入浅出完整解析ControlNet核心基础知识
AIGC图像创作开源社区已经形成以Stable Difffusion/FLUX为核心,ConrtolNet和LoRA作为首要AI辅助工具的变化万千的AIGC图像创作工作流。
ControlNet正是让AI图像创作社区无比繁荣的关键一环,它让AIGC图像创作过程更加的可控,更有助于广泛地将AIGC算法解决方案应用到各行各业中:
12. 深入浅出完整解析Sora、Seedance、keling等AI视频大模型核心基础知识
AI绘画和AI视频是两个互相促进、相互交融的领域,2024年无疑是AI视频领域的爆发之年,Rocky对AI视频领域核心的Sora、Seedance、Keling等大模型进行了全面系统的梳理与解析:
深入浅出完整解析Sora、Seedance、keling等AI视频大模型核心基础知识
13. 深入浅出完整解析AIGC时代Transformer核心基础知识
在AIGC时代中,Transformer为AI行业带来了深刻的变革。Transformer架构正在一步一步重构所有的AI技术方向,成为AI技术架构大一统与多模态整合的关键核心基座,大有一统"AI江湖"之势。Rocky也对Transformer模型进行持续的深入浅出梳理与解析:
深入浅出完整解析AIGC时代Transformer核心基础知识
14. 深入浅出完整解析ComfyUI、Diffusers、Stable Diffusion WebUI等主流AIGC创作框架核心基础知识
AIGC创作框架正是AIGC算法工作流的运行载体,目前主流的AIGC创作框架有ComfyUI、Diffusers、Stable Diffusion WebUI等 。在传统深度学习时代,PyTorch、TensorFlow以及Caffe是传统深度学习模型的基础运行框架,到了AIGC时代,Rocky相信ComfyUI就是AIGC时代的"PyTorch"、Stable Diffusion WebUI就是AIGC时代的"TensorFlow"、Diffusers就是AIGC时代的"Caffe":
深入浅出完整解析ComfyUI、Diffusers、Stable Diffusion WebUI等主流AIGC创作框架核心基础知识
15. 深入浅出完整解析ComfyUI、Diffusers、Stable Diffusion WebUI等主流AIGC创作框架核心基础知识
在AIGC时代中,如何快速转身,入局AIGC产业?如何成为AIGC/LLM/AI Agent算法/开发工程师?如何在学校中系统性学习AIGC/LLM/AI Agent知识,斩获心仪的AIGC/LLM/AI Agent算法/开发offer?
Don't worry,Rocky为大家总结整理了全面的AIGC/LLM/AI Agent算法/开发工程师成长秘籍,为大家答疑解惑,希望能给大家带来帮助:
手把手教你成为AIGC/LLM/AI Agent算法/开发工程师,斩获AIGC/LLM/AI Agent算法/开发offer!
16. AIGC产业的深度思考与分析
2023年3月21日,微软创始人比尔·盖茨在其博客文章《The Age of AI has begun》中表示,自从1980年首次看到图形用户界面(graphical user interface)以来,以OpenAI为代表的科技公司发布的AIGC模型是他所见过的最具革命性的技术进步。
Rocky也认为,AIGC及其生态,会成为AI行业重大变革的主导力量。AIGC会带来一个全新的红利期,未来随着AIGC的全面落地和深度商用,会深刻改变我们的工作、生活、学习以及交流方式,各行各业都将被重新定义,过程会非常有趣。
那么,在此基础上,我们该如何更好的审视AIGC的未来?我们该如何更好地拥抱AIGC引领的革新?Rocky准备从技术、产品、商业模式、长期主义等维度持续分享一些个人的核心思考与观点,希望能帮助各位读者对AIGC有一个全面的了解:
深入浅出全面解析AIGC时代核心价值与发展趋势(2025年版)
17. AI算法工程师的独孤九剑秘籍
为了方便大家实习、校招以及社招的面试准备,同时帮助大家提升扩展技术基本面,Rocky将符合大厂和AI独角兽价值的算法高频面试知识点撰写总结成《三年面试五年模拟》之独孤九剑秘籍:
【三年面试五年模拟】AIGC时代的算法工程师的求职面试秘籍(持续更新中)
18. 深入浅出完整解析AIGC时代中GAN(Generative Adversarial Network)系列模型核心基础知识
GAN系列模型作为传统深度学习时代的最热门生成式Al模型,在AIGC时代继续繁荣,作为Stable Diffusion/FLUX系列大模型的"得力助手",广泛活跃于AlGC图像创作的产品与工作流中:
深入浅出完整解析AIGC时代中GAN(Generative Adversarial Network)系列模型核心基础知识