面灵AI→

拼多多服务端开发一面面经:并发幂等、Kafka 积压与向量库索引

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

项目与中间件

  1. 详细介绍实习项目里涉及到的并发和幂等是怎么实现的?
  2. 为什么要用并发和幂等?
  3. 消息的顺序性怎么保证?
  4. 中间件是内部开发的吗?
  5. Kafka 的原理是什么?
  6. 有 topic 发生消息积压怎么办?
  7. 怎么确定 partition 分区数?

知识问答助手与向量数据库

  1. 知识问答助手具体做了哪些内容?
  2. 「不暴露内部业务代码」是什么意思?为什么要加这个?
  3. 你的项目中 Chroma 的作用是什么?
  4. 它与 Redis 的使用场景怎么区分?
  5. 智能体运行调度出错了怎么办?
  6. 向量数据库怎么更新?
  7. 向量数据库的索引结构是什么?
  8. 你还了解其他索引结构吗?

MySQL

  1. MySQL 的主从同步机制是怎样的?
  2. 结合一个插入的例子再讲一遍。
  3. 怎么保证数据的一致性?

开放题

  1. 怎么没投算法岗?
  2. 实习的地方有 offer 吗?
  3. 了解 PDD 的氛围和工作模式吗?你怎么看待?

算法

  1. 数组中能组成三角形的三条边有多少组?
  2. 一道较长的动态规划题。

《参考解析》

并发与幂等:先答动机,再落实现

「为什么用」比「怎么用」更容易露怯,先想清楚动机:并发是为了把互相独立的 IO 等待叠起来(批量拉取、并行调用多个下游、异步写日志),把串行耗时压到最长的那一段;代价是资源竞争、顺序被打乱、错误扩散,所以必须有边界(并发度、超时、熔断)。幂等是为了让「重试」这件事安全——网络超时、上游重发、消息至少一次投递都会造成重复,没有幂等就会重复扣款、重复建单、重复发券。

实现上要能说出具体手段,并按场景分:数据库唯一约束(业务唯一键建唯一索引,重复插入直接失败,这是最可靠的一道闸);幂等表 / 去重表(请求 id 或业务键 + 状态,先插入占位再执行业务,靠主键冲突判重);状态机(订单只能从「待支付」到「已支付」,重复回调因为状态不匹配被忽略);分布式锁或 SETNX 占位(Redis 拿锁 + 过期时间,用完释放,注意锁续期与误删);乐观锁版本号(update ... where version = ?)。回答时最好把「客户端请求 id → 服务端幂等键 → 数据库唯一约束」这条链完整说出来,并交代幂等键的生成规则和保留时长。

消息顺序性怎么保证

Kafka 只保证同一 partition 内有序,跨 partition 不保证,所以顺序性的做法是让需要保序的消息进同一个 partition:发消息时用业务键(订单 id、用户 id)做 key,Kafka 按 key 哈希到固定分区;如果全局保序要求高,就把 topic 设成单分区(吞吐会受限)。

生产端还要注意:开启幂等生产者(enable.idempotence=true,配 acks=all 与重试)避免重试造成的重复与乱序,必要时用 max.in.flight.requests.per.connection=1 彻底串行化。消费端同样要保序——一个 partition 只由一个消费者消费,但消费者内部如果用了线程池并行处理就会破坏顺序,常见做法是按 key 分发到内存队列(同一 key 固定进同一个队列,各队列单线程消费)。最后补一句兜底:即使链路全程保序,业务侧仍要幂等,因为顺序错乱和重复都可能出现在异常路径上。

消息积压怎么处理

先定位再动手,积压无非三种成因:消费能力不足(消费逻辑里有慢调用、每条都同步写库)、突发流量(上游批量导入、大促)、消费者挂了或一直在 rebalance。

常用的处置手段按代价排序:临时扩容消费者实例(注意实例数超过 partition 数就没有收益)、提高单次拉取批量与并行度、优化消费逻辑(批量写、去掉同步远程调用、把非关键逻辑异步化)、把积压消息转存到临时 topic 用另一组消费者并行消费(先把积压清掉、保证线上延迟)、必要时新增 partition 提升并行度(注意新增分区会改变 key 的哈希落点,破坏既有顺序性)。事后要有监控基线:消费 lag、消费速率与生产速率的比值、告警阈值,别等用户投诉。

分区数怎么确定

先算目标吞吐:单分区能扛的写入/消费速率(受 broker 磁盘、网络、复制开销影响,通常按几 MB/s 量级估),用峰值吞吐除以单分区能力,再乘 1.5~2 倍留余量,同时保证分区数不小于期望的消费者并行度上限。还要考虑代价:分区越多,元数据与文件句柄越多,leader 选举和 rebalance 越慢,端到端延迟也可能上升;分区数只能增不能减。工程上通常按「业务峰值吞吐 + 未来一年增长」定一个偏保守的值,并按 key 分布检查有没有热点分区。

Chroma 与 Redis 的使用场景区分

Chroma 是向量数据库:存 embedding 与原文 metadata,提供相似度检索、metadata 过滤和持久化,服务的是「语义召回」——RAG 的知识库这一层。Redis 是内存 KV / 数据结构服务:更快的读写、支持过期、原子操作,适合做缓存(热数据、embedding 结果)、会话状态、分布式锁、限流计数和消息队列。

两者会同时出现在一个 RAG 系统里但职责不同:Redis 缓存的是「同一个问题答过的结果」「query 的向量」「热点文档」,命中就少一次向量检索和一次模型调用;Chroma 负责真正的召回。要补一句边界:Redis 也有向量检索能力(RediSearch),但定位是「已经有 Redis、数据量不大时的顺带方案」,持久化、索引类型和过滤能力都不如专业向量库;反过来 Chroma 不适合当缓存,它的定位是持久化的检索存储。

智能体调度出错了怎么办

分三层处理。单步失败:工具调用超时或报错时,把错误信息作为观测回灌给模型让它换路,而不是直接终止;读类操作可以带退避重试,写类操作必须带幂等键、重试前先查真实状态。流程失败:每步把状态(计划、已完成步骤、中间产物、预算)落检查点,进程挂掉能从最后一步续跑,而不是从头再来。整体失败:设置最大步数、token 预算与总超时,超限就降级(返回部分结果、切到固定流程模板、转人工),并把失败分类(模型侧/工具侧/环境侧)记录下来用于归因。核心原则是:可恢复的重试、不可恢复的降级、所有异常都要有终止条件,不能让调度器无限循环。

向量数据库的更新与索引结构

更新上要区分两种操作:内容没变的元数据修改用 metadata 更新即可;文档内容变更最好是「按文档 id 删除旧 chunk 再写入新 chunk」(upsert + 删除),因为切分变了以后 chunk 与旧向量无法一一对应。工程上再配一层版本管理:文档带版本号与来源,写入时做幂等,删除用软删除标记再后台重建索引,避免读请求读到删了一半的知识库。更新完要能验证——拿一批已知答案的问题回归,确认召回率没有下降。

索引结构常见的三类:HNSW(分层小世界图,逐层跳转逼近近邻,查询快、召回高,内存占用大,构建慢,是多数向量库的默认);IVF 系列(先用聚类把向量分桶,查询时只扫最近的若干个桶;配上 PQ 乘积量化把向量压成短码,就是 IVF-PQ,省内存但损失精度);DiskANN / Vamana(面向磁盘的图索引,单机放不下时用);数据量很小或要求精确结果时就是 Flat 暴力检索(100% 召回,延迟随数据量线性增长)。除了向量索引,还应该知道倒排索引(关键词检索、bm25)、B+ 树(关系型数据库的范围查询)、LSM 树(写多读少的 KV,如 RocksDB)、哈希索引(等值查询)、位图索引(低基数列)。向量检索补一路关键词倒排做混合召回,能显著改善专有名词和数字的召回。

MySQL 主从同步与一致性

流程是:主库把变更写进 binlog;从库的 IO 线程连上主库(由主库的 dump 线程推),把收到的日志写进本地 relay log;从库的 SQL 线程(并行复制时是 worker 线程)读 relay log 并在从库重放,从而追平数据。binlog 有三种格式——STATEMENT(记 SQL,函数与随机值可能不一致)、ROW(记行变更,最安全、日志量大,现在基本是默认)、MIXED(按语句自动选)。

结合一条 insert 讲:客户端提交事务 → 主库写 binlog 并更新存储引擎 → 事务提交返回;dump 线程把这条 event 推给从库 → 从库写 relay log → SQL 线程执行同一条插入 → 从库数据可见。默认的异步复制在这里就返回了,主库挂了可能丢最后几个事务;因此有半同步复制(至少一个从库确认收到日志才返回)、组复制 / MGR(基于 Paxos,多数派确认)和 GTID(全局事务 id,方便自动定位位点与切换)。

一致性要分两个问题答:主从之间——用 sync_binlog=1 + innodb_flush_log_at_trx_commit=1 保证主库不丢、用半同步/组复制降低从库滞后,业务上允许脏读的走从库、要求强一致的读走主库(比如写后立刻读、支付状态查询);缓存与库之间——先写库再删缓存(Cache Aside),配延迟双删或订阅 binlog 投递失效消息,把不一致窗口压到最小。

两道算法题

数组中的三角形个数:三条边 a ≤ b ≤ c 能组成三角形的充要条件是 a + b > c。先把数组排序,固定最长边 c(下标 k,从 2 到 n−1),在 [0, k−1] 上用双指针找满足 a + b > c 的数对:若 nums[i] + nums[j] > nums[k],则 i 从当前到 j−1 的每个取值都能与 j 组成合法数对,一次累加 j − i 后 j 左移;否则 i 右移。整体 O(n²),比三重枚举的 O(n³) 好得多。边界要交代:等于的情况(a + b == c)不算三角形,重复元素按「下标不同的三元组」计数。

动态规划题:题目描述的是一道偏长的 DP(题干往往把状态、转移和约束写在场景里)。这类题的方法是三步——定义状态(dp[i][j] 表示什么含义,必须能一句话说清)、写转移(当前状态从哪些更小的状态推来,注意取 max/min 还是累加)、定边界与遍历顺序(初始化、遍历方向要保证依赖项已经算好)。如果状态维度太高,再考虑滚动数组压空间或用前缀和/单调队列优化转移。