逐日AI

面试题库

共 328 题,当前筛选 2 题。

30 天从前端工程师到 Agent 工程师

D8 为什么 Gateway/Worker 分离;Postgres 表设计(sessions/runs/messages)+ Drizzle

  • 为什么生产级 Agent 服务通常要把 Gateway 和 Worker 拆开?什么情况下不该拆?Why do production agent services usually split a gateway from workers, and when should you not split?
    国内高频海外高频基础#architecture#scalability

    分析过程 · 先想清楚再作答

    1. 题眼在后半句。只答「解耦、可扩展」是从架构书上抄来的,面试官想知道你有没有被某个具体现象逼着拆过——所以答案里必须出现「什么现象」和「不拆的代价」。
    2. 先给一条可复用的推导链:Agent 的一次执行是长耗时且时长不可预测的(模型响应加上多轮工具调用,几秒到几十秒),而接入层要承载全部流量、必须是毫秒级的短请求;把两种时长量级差三个数量级的工作放进同一个进程,慢的那一类必然会挤占快的那一类的资源。
    3. 把现象说具体:单进程时一台机器同时跑十几次长执行,连接与内存被占满,新来的健康检查开始超时,编排系统判定实例已死并重启它——正在跑的执行全部陪葬。这个「健康检查被自己的业务拖挂」的故事比任何抽象论证都有说服力。
    4. 然后给判据:能在 Worker 做的不放 Gateway,接入层只留鉴权、限流、落库、投递这四件耗时确定的事。拆开之后 Gateway 无状态可以任意扩缩,Worker 的并发度可以按模型配额单独调,两者的扩容曲线本来就不一样。
    5. 主动说代价,这是区分度所在:接口语义从 200 变成 202,客户端要多一次订阅往返;系统里多了一条总线和一张 runs 表,可观测性和排障链路都变长;本地开发要起更多进程。所以单次执行只有几百毫秒、没有工具调用、日活很小的场景不该拆——那时候拆分带来的复杂度远大于收益。
    6. 可以预期的追问:不拆但用线程池或者子进程行不行?答案是能缓解「挤占」但解决不了「重启即丢失」和「多实例状态不共享」,因为那两件事的根因是状态在进程里,不是并发模型不对。

    How to reason about it · think before answering

    1. The hinge is the second half. Answering only 'decoupling and scalability' sounds copied from a textbook; the interviewer wants to know which concrete symptom forced you to split, and what splitting costs.
    2. Offer a reusable chain: one agent run is long and unpredictable (model latency plus several tool calls, seconds to tens of seconds), while the ingress path carries all traffic and must stay in the millisecond range. Put workloads three orders of magnitude apart in the same process and the slow one starves the fast one.
    3. Make the symptom concrete: a single process running a dozen long executions saturates connections and memory, health checks start timing out, the orchestrator declares the instance dead and restarts it, and every in-flight run dies with it. That story lands harder than any abstract argument.
    4. Then state the rule: anything a worker can do should not live in the gateway, which keeps only auth, rate limiting, persistence and dispatch — four steps with bounded latency. After the split the stateless gateway scales with traffic while worker concurrency is tuned against model quota; the two curves were never the same.
    5. Volunteer the cost, which is where candidates separate: the contract becomes 202 instead of 200 so clients need a second subscribe round trip, you now operate a bus and a runs table, tracing spans more hops, and local development needs more processes. So do not split when a run takes a few hundred milliseconds, uses no tools, and serves modest traffic.
    6. Expect the follow-up: could a thread pool or child processes do instead? They ease starvation but fix neither 'restart loses in-flight work' nor 'two instances cannot see each other's state', because the root cause is state living inside the process, not the concurrency model.

    答题要点

    • 一次 Agent 执行是几秒到几十秒的长任务,接入层是毫秒级短请求,两者同进程时长任务必然挤占短请求的资源
    • 单进程的三个具体死法:重启丢掉在途执行、多实例状态各存各的、长执行把健康检查拖超时导致实例被误杀
    • 判据是「能在 Worker 做的不放 Gateway」,接入层只留鉴权、限流、落库、投递
    • 拆开后 Gateway 无状态按流量扩容、Worker 按模型配额扩容,两条曲线可以独立调
    • 代价是接口从 200 变 202、多一次订阅往返、排障链路变长;单次执行仅几百毫秒且无工具调用的场景不该拆

    Key points

    • A run takes seconds to tens of seconds while ingress requests are millisecond-scale; in one process the long work starves the short work
    • Three concrete failure modes: restarts lose in-flight runs, multiple instances hold separate state, and long runs stall health checks so the orchestrator kills a healthy instance
    • The rule is that anything a worker can do stays out of the gateway, which keeps only auth, rate limiting, persistence and dispatch
    • After splitting, gateways scale on traffic and workers scale on model quota — two independent curves
    • Costs: a 202 contract plus a subscribe round trip, an extra bus and table to operate, longer traces; skip the split for sub-second runs with no tool calls

D10 分片与租约:userId 哈希→shard、SET NX + TTL + Lua 续约、同用户顺序、handoff

  • 为什么要对 userId 做哈希分片,而不是让消费组随机派发?分片数应该怎么选?Why hash user ids into shards instead of letting the consumer group dispatch freely, and how do you pick the shard count?
    国内高频海外高频基础#sharding#consistent-hashing#scalability

    分析过程 · 先想清楚再作答

    1. 题眼在「为什么不随机派发」。只答「为了负载均衡」就掉进坑里了——消费组本来就是负载均衡,随机派发在均衡上比哈希分片更好。分片解决的是另一件事:亲和性。
    2. 推导链是这样的:消费组的分配单位是「一条消息」,而业务要求的最小串行单位是「一个用户」;单位对不上,同一个用户连发的两句话就会被两个进程同时处理。所以要把分配单位从消息抬到用户。
    3. 第二步是「为什么中间要垫一层 shard,而不是 userId 直接取模 worker 数」。因为 worker 数会变——扩容、重启、崩溃、滚动发布;除数一变,几乎所有用户的归属都会变,正在处理的会话被整体搬家。固定的 shard 数把「用户到 shard」钉死,只让「shard 到 worker」随伸缩浮动。
    4. 分片数怎么选,要给出可执行的判据而不是一个数字:它是并行度的上限(256 个 shard 最多让 256 个 worker 有活干),而且改它等于一次数据迁移(所有用户归属重算,必须停机或双写过渡)。所以宁可一开始定得偏大——256 摊在 3 个 worker 上是 85、85、86,多出来的成本只是几百个 key 的内存;定成 8 个的话扩到第 9 个 worker 就撞墙了。要用 2 的幂,取模能退化成位运算,也方便将来对半拆分。
    5. 主动说出哈希均匀的边界:均匀说的是「用户数均匀」,不是「消息量均匀」。一个日发千条的大客户可能和一千个散户落在同一个 shard 上。缓解是给大客户在哈希前加一张小的例外表、单独占一个 shard,而不是把总分片数调大(那就是上面说的数据迁移)。
    6. 可预期的追问:为什么不用一致性哈希?答案是它优化的是「节点变化时的迁移量」,前提是分片承载状态、搬迁很贵。我们的 worker 是无状态执行体,状态在数据库和 Redis 里,没有数据要搬;而且 shard 到 worker 的归属本来就由租约动态决定。固定分片优化的是可预测性,在这个场景里更简单,也更可靠。

    How to reason about it · think before answering

    1. The hinge is 'why not dispatch freely'. Answering 'for load balancing' misses it — a consumer group already balances load, and free dispatch balances better than hashing. Sharding buys something else: affinity.
    2. The chain: a consumer group's unit of assignment is one message, while the business requires one user as the smallest serial unit. When those units disagree, two messages from the same user get processed concurrently by two workers.
    3. Second step: why insert a shard layer instead of taking userId modulo the worker count? Because the worker count changes on scale-up, restart, crash and rolling deploy. Change the divisor and almost every user is remapped, so in-flight sessions migrate wholesale. A fixed shard count pins user-to-shard and lets only shard-to-worker float.
    4. For the count, give criteria rather than a number: it caps parallelism (256 shards means at most 256 useful workers), and changing it is a data migration (every user is remapped, requiring downtime or a dual-write transition). So oversize it up front — 256 across 3 workers is 85/85/86 and costs a few hundred keys of memory, while picking 8 walls you in at the ninth worker. Use a power of two so the modulo degrades to a bit mask and future splits stay clean.
    5. Volunteer the limit of uniformity: it means uniform user counts, not uniform message volume. One enterprise account sending a thousand messages a day can share a shard with a thousand one-message users. The fix is an exception table before the hash that gives that account its own shard, not a larger shard count — that would be the migration above.
    6. Expect the follow-up: why not consistent hashing? It optimizes remap volume, which pays off when shards carry state that is expensive to move. Our workers are stateless executors with state in Postgres and Redis, so nothing needs moving, and shard ownership is already decided dynamically by leases. Fixed sharding optimizes predictability, which is simpler and more reliable here.

    答题要点

    • 分片解决的是亲和性不是负载均衡:把分配单位从「一条消息」抬到「一个用户」,同一个用户永远落到同一个 worker
    • 中间垫一层固定 shard,是为了让 worker 伸缩时用户到 shard 的映射保持不变,只有 shard 到 worker 的归属浮动
    • 分片数是并行度上限,改它等于一次数据迁移,所以一开始就定偏大、用 2 的幂(本课 256)
    • 哈希均匀保的是用户数均匀,不是消息量均匀;大客户热点要靠哈希前的例外表单独拆 shard
    • 一致性哈希优化迁移量,只在分片带状态时划算;无状态 worker 用固定分片更简单

    Key points

    • Sharding is about affinity, not balancing: it lifts the unit of assignment from one message to one user so a user always lands on the same worker
    • The fixed shard layer keeps user-to-shard stable across scaling; only shard-to-worker ownership moves
    • The shard count caps parallelism and changing it is a migration, so oversize it and use a power of two (256 in this course)
    • Uniform hashing means uniform user counts, not uniform traffic; hot accounts need an exception table before the hash
    • Consistent hashing optimizes remap volume and only pays off for stateful shards; stateless workers do better with fixed shards