小红书全栈开发一面:分片上传与向量数据库设计
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 项目中数据链路怎么设计的?
- 分片上传怎么实现?
- 怎么解决重复上传的问题?
- 如果数据库的上传记录和实际文件不符合怎么办呢?数据库没记录,但是文件已经上传了。
- Redis 的 ZSet 底层怎么实现的?查询性能怎么样?
- ES 是实时级的吗?延迟有多少?
- 向量数据库怎么设计的?怎么优化查询准确率?
- 用户反映页面慢了,怎么排查?
《参考解析》
分片上传要一次讲清「切片—并发—合并—校验—续传」。 客户端按固定大小切片(常见 1–5 MB,按网络状况可调),每片带序号与校验和;上传前先请求初始化接口,服务端用文件指纹(内容 hash + 大小)生成唯一 uploadId 并返回已上传的分片列表,客户端只传缺的那几片,这就是断点续传的基础;分片并发直传对象存储(用预签名 URL,避免流经应用服务器),全部完成后按序号合并。校验放在两处:单片上传后比对分片校验和,合并后比对整文件 hash,「服务端重算 hash 与客户端声明一致」才算成功——既防丢片也防传输损坏。最后要清掉临时分片与过期未完成的 uploadId(定时任务),否则临时空间会被垃圾占满。
重复上传与「有文件无记录」,本质是同一个幂等与对账问题。 重复上传用两级拦:秒传——先查文件指纹,已存在就直接把已有的文件引用挂到新业务记录上,不再传字节;幂等键——上传会话以「业务 ID + 文件指纹」为唯一键,重复初始化直接返回同一个 uploadId。至于「数据库没记录但文件已上传」,要区分两种情况:其一是合并成功但业务落库那一步失败了(进程崩了、事务回滚),其二是上传完成但客户端从没调用完成回调。工程上的处理是让状态可自愈,而不是靠人工:把上传会话做成状态机(初始化 → 上传中 → 已合并 → 已绑定业务),用最终一致的方式推进——落库与「标记已绑定」放进同一个事务、合并成功后发消息驱动幂等消费来补记录、再加一个定期对账任务扫描「对象存储有文件但会话未绑定」并补写或清理。查询接口则按「记录没有就回源对象存储判断,并顺手修复元数据」来处理,保证用户侧不出现「文件明明在却提示不存在」。
Redis ZSet 底层是跳表加字典的组合。 一个 ZSet 同时维护一个 dict(成员到分数的映射,保证 O(1) 查分数)和一个 skiplist(按分数有序,支持范围与排名操作),所以「按成员查分数」是 O(1),「按分数或排名取范围」是 O(log N + M)。跳表通过多级索引把链表的线性查找降到对数级,插入与删除期望 O(log N);相比平衡树,跳表实现更简单、范围查询天然按链表顺序走、并发实现也更容易。元素少且成员都是短字符串时,Redis 会用 ziplist(列表压缩编码,新版是 listpack)省内存,超过阈值才转成跳表。基于这些能力,ZSet 最典型的用法是排行榜(ZADD/ZREVRANGE)、延时队列(score 存到期时间戳,轮询 ZRANGEBYSCORE 取到期任务)和滑动窗口限流(score 存时间戳,先删窗口外的再统计 ZCARD)。常见坑是别拿它做大对象的存储(value 只存 ID),以及延时队列不要用 ZRANGE 全量轮询、要配 ZREMRANGEBYSCORE 清理。
ES 不是实时的,它是近实时(NRT)。 写入路径是:数据先进内存缓冲与 translog,默认每秒执行一次 refresh 生成新的 segment 使其可被搜索,所以默认延迟约 1 秒;refresh_interval 调小可以更实时但会产生更多小 segment、加重合并压力,测试或对实时性要求高的写入场景可以临时调低甚至手动调 _refresh。要跟面试官区分两层延迟:写入到可搜索的延迟(refresh,默认 1s 级)与写入到持久化的延迟(translog 落盘,默认每个请求 fsync,index.translog.durability 可配成异步牺牲一部分安全性换吞吐)。另外主分片写入到副本可读还有复制延迟,?refresh=wait_for 可以等一次刷新再返回。顺带补一句 ES 不适合当强一致的事务型主库,业务上通常「MySQL 存事实、ES 做检索」,用 binlog 或消息同步并接受秒级延迟。
向量数据库的设计分「存储与索引」「检索策略」「评估」三块。 存储上要定:向量维度与类型(float32 或半精度、量化以省内存)、随向量一起存的元数据(用于权限与范围过滤)、以及是否与业务文本分开存(原文放对象存储、向量库存 ID 与向量)。索引上常见的近似最近邻是 HNSW(图结构,查询快、内存占用大、构建慢)与 IVF 系列(倒排聚类,内存友好、召回依赖 nprobe 调参),选型看数据规模、内存预算与召回要求。准确率的主要抓手在检索策略:查询改写与同义扩展、混合检索(向量管语义、关键词管专有名词,再做融合)、多路召回后加交叉编码器 rerank、控制 Top-K 与相似度阈值并对结果去重。评估要有带标准答案的测试集,看 Recall@K、MRR、NDCG,调参都在这套集子上对比,同时盯 P95 延迟与成本。
「页面慢了怎么排查」要有分层顺序,别一上来就说优化。 第一层先确认现象与范围:是所有用户还是特定地区、机型、版本,是首次加载慢还是交互卡,什么时候开始,有没有对应的发布或配置变更——先排除「我们自己最近改了什么」。第二层按链路切:前端看请求瀑布与主线程(接口慢、资源大、还是渲染卡),网络看 DNS、首字节与 CDN 命中率,服务端看接口耗时分布与缓存命中率。第三层用数据定位:RUM 与 APM 按接口和版本切片,用 trace 串起一次请求各段耗时,再用回滚、灰度、加缓存做对照确认因果。最后是修复与防回归:加索引或改写慢查询、补缓存与限流、首屏资源瘦身与懒加载,并把关键指标接进监控、设预算与告警。