小红书风控研发一面到三面面经:从归因 Agent 到秒杀链路设计
- 轮次
- 一面、二面、三面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 请做一两分钟的自我介绍。
- 你后续的职业规划是什么?想做偏技术基建方向还是业务方向?
- 你自己有什么兴趣点吗?
- 介绍一下风控归因 Agent 这个项目的背景,它主要解决什么问题?你是怎么实现的?
- 你做这块的难点是什么?
- 架构演进过程中,你是怎么发现问题的?怎么判断之前的方案确实需要重构?
- V1、V2、V3 不同版本的架构选型是怎么推导出来的?
- 怎么量化上下文隔离或技术优化带来的成本消耗降低?
- 是否存在「先污染后治理」:一开始设计时就能预判 Token 消耗大,为什么不一开始就选更好的方案,而是先跑起来再降?
- 三级数据查询的历史数据回溯机制,怎么保证数据一致性?
- 你对风控业务有全局了解吗?风控是做什么的?系统架构是什么样的?
- 你们的系统架构有几个系统?每个系统主要负责什么功能?
- 你觉得在风控实习过程中,成长最大的是什么?
- 平时在工作或日常学习中,你是怎么学习 AI 的?
- 你提到 Claude Code 的三层架构,可以详细讲一下它是怎么实现的吗?
- 前段时间比较火的 Harness 概念,有没有了解过?
- 年初比较火的龙虾 OpenCloud,自己有没有尝试过?
- 自己有没有了解过大模型是怎么训练的?有没有训练过模型?
- 电商有个热点福利品,库存几十万,会触发秒杀,你作为秒杀链路负责人,怎么设计优化整条链路保证高 TPS?
- Redis 库存扣减为什么要用 Lua 脚本?原子性你是怎么理解的?
- 缓存一致性:本地缓存和 Redis、Redis 和 DB 之间的一致性怎么保证?
- 数据库单行 TPS 上限:Redis 扣减后异步扣 DB,单行有 TPS 上限,1 万并发怎么解决?
- 请求合并的落地细节:怎么把 100 次请求聚合成一次写 DB?怎么感知 Redis 数据变更?
- 给定链表头节点 head,反转链表并返回新头节点,说一下思路并写代码。
- 反问:团队做什么业务?
- 反问:秋招面试流程是怎样的?
- 反问:AI 时代校招生需要什么能力、该如何提升?
- 请先做一个自我介绍。
- 你拿到字节的 offer 了吗?
- 你了解小红书的业务吗?面试小红书主要吸引你的点是什么?
- 你这一年多的时间,主要的精力分配是什么样子的?分别投入在哪几块?
- 处置平台主要提供什么样的能力?你能大概讲一讲吗?
- 你们这一块治理是服务于哪些业务线?
- 你讲一下你做的核心的工作吧。
- 这个智能体主要解决什么问题?它肯定不是一个在线的风控吧?
- 拿了一个客诉,需要判断为什么广告主被风控了,这个场景你怎么处理?
- 这个智能体是你自己搭建的吗?有没有用什么开源框架?
- 当时为什么没有依托 Codex 和 CC 去开发 Skill 的方式去做,而是自己做一个智能体?
- 为什么基于内部框架,不基于开源的 OpenClaw 或者 Horus 这种框架?
- 这个效果怎么样?从业务的收益角度讲一讲。
- 框架给你的开放程度多高?上下文管理、效果评测,哪些是框架提供的,哪些是你实现的?
- 你有没有调研过像 CC 的 Compact、Old Code Compress 这种机制,它们背后执行命令之后具体做了什么?
- 你有没有调研过 Codex 的压缩方式?他们也是丢进去路径吗?
- 稳定性这一块你是做了什么?我看到你写了个稳定性的技术沉淀。
- 你觉得 AI 时代,你作为工程或计算机专业的同学,和文科生以及没学过计算机原理的同学,未来之间最大的差异是什么?你最大的优势是什么?
- 你觉得你最大的优点和当前阶段需要提升的点是什么?
- 希望 base 在哪边?我们主要是在杭州和上海这边。
- 你这边有没有什么问题?
- 你一面的时候,你觉得有没有回答不好的地方?之后有去看看吗?
- 三面追问:这个项目的收益怎么样?挑战和难点在哪里?
《参考解析》
秒杀链路的整体设计,核心是「把流量挡在数据库之前」。 分层削峰:入口层把静态资源全部 CDN 化,用按钮置灰、答题、验证码把瞬时并发打散,网关按用户维度和商品维度限流;前置校验层在 Redis 里做活动时间、白名单、限购数量、库存是否充足的无状态判断,绝大多数请求在这一层就被拒掉;扣减层在 Redis 里原子扣减库存,完全不碰数据库;异步层把手里的请求投进 MQ,由消费者下单落库;数据层只做最终写入和对账。热点要单独隔离:单个商品用独立队列、独立 key,避免一个 SKU 打满整个集群;更彻底的是库存分桶——把几十万库存拆成 N 份 key,按用户 ID 哈希落桶,单 key 的 QPS 直接降到 1/N。防超卖靠三重保险:Redis 原子扣减、数据库唯一约束、业务幂等键,宁可少卖也不能超卖。最后一定要有兜底:MQ 消费失败要有补偿与对账任务,活动结束用 DB 的准数反向校正 Redis。
Redis 扣减为什么必须用 Lua,以及「原子性」到底指什么。 因为「读库存 → 判断是否大于 0 → 扣减 → 记录该用户已购」是四步逻辑,单条 Redis 命令表达不了,而拆开执行会被并发请求插进来,导致超卖或重复扣。EVAL 把整段脚本交给 Redis 的单线程执行模型,脚本执行期间不会插入其它命令,于是获得了原子性——注意这是「不可分割」意义上的原子,不是数据库事务那种失败可回滚的原子,所以脚本里要尽量避免中途失败留下半截状态。相比 WATCH + MULTI 的乐观锁,脚本方案不用重试、少一个 RTT,高并发下不会因版本冲突大面积失败。工程细节三条:脚本要短,别在大 key 上跑循环或做复杂计算,否则阻塞整个实例;集群模式要用 hash tag 把相关 key 路由到同一个 slot,否则直接报 key 不在同一节点;限购用的去重集合要单独设过期时间,活动结束自动回收。
缓存一致性要分两条链路看,而且目标不是强一致。 一条是本地缓存与 Redis 之间,一条是 Redis 与 DB 之间。Redis 与 DB 主流是 Cache Aside:读走「先读缓存、未命中回源 DB 再回填」,写走「先更新 DB、再删除缓存」——删缓存而不是更新缓存,因为两个并发写时「更新缓存的顺序」很容易和「落库顺序」相反,反而留下旧值。删缓存失败的兜底有三招:延迟双删(更新 DB 后删一次,隔几百毫秒再删一次,覆盖「读线程在写线程删缓存之前读到旧值、之后才回填」的窗口)、订阅 binlog 用异步任务删(Canal 这类,解耦且不怕业务漏删)、给 key 留一个较短的 TTL 作最后防线。本地缓存与 Redis 之间麻烦一些,因为进程内缓存没法被外部主动失效,得靠广播失效(Redis pub/sub 或 MQ 广播)或者把本地 TTL 设得很短再配主动刷新。最实在的判断标准是业务能容忍多久的脏数据:秒杀库存这种不能错的场景就别谈最终一致,直接以 Redis 为准、DB 只当账本。
单行 TPS 上限是热点写入的物理瓶颈,解法是别让所有请求抢同一行。 InnoDB 对同一行的更新要串行加行锁,一个事务还要写 redo/undo,单行热点更新的吞吐通常只有几千 TPS 量级,远低于整库几十万 QPS 的能力上限。所以「Redis 扣完后异步扣 DB」在 1 万并发下依然会堵在那一行:事务排队等锁、连接池被占满、上游超时雪崩。三层解法。第一层是拆行,把一个 SKU 的库存拆成 stock_1 到 stock_N,按用户哈希落桶,写并发度直接乘 N,代价是库存要能按桶再平衡、退库存要能找回原桶。第二层是请求合并,把 N 次扣减聚成一条 UPDATE stock SET amount = amount - #{total} WHERE sku = ? AND amount >= #{total},N 次锁等待变成 1 次。第三层是异步化加顺序化,MQ 按 SKU 分区保证同商品串行,消费者批量刷写,DB 只承担削峰后的量,最终靠对账任务把差额校正回去。
请求合并怎么落地:关键在「攒批」和「变更感知」两个动作。 攒批的常见做法是在应用侧或一个无状态网关维护按 SKU 聚合的队列,用「攒够 N 条」或「等一个几毫秒的时间窗」触发批量提交,前者适合流量平稳,后者适合尖峰,实际会取两者先到。提交时以 DB 的实际影响行数为准:影响行数为 0 说明库存不足,这批里谁成功谁失败要按入队顺序回退并给用户返回失败,所以队列元素必须带请求 ID 才能精确 ack。变更感知有几种路数:Redis 的 keyspace notification 最省事,但它不保证送达、断连期间的变更会丢,只能当触发信号而不是事实来源;可靠一点的是让扣减那一侧把变更写进 Redis Stream 或 List 当变更日志,消费者按消费组读取并 ack,天然支持重放;最粗暴的是消费者按固定频率批量捞取待处理流水。要提醒的是合并会放大单次失败的爆炸半径,批量写失败必须留整批重试或逐条降级的路径,不能一条 SQL 挂了就把整批请求丢掉。
架构演进与「先污染后治理」怎么答才不失分。 这类追问考的是技术判断力,不是让你认错。组织答案的结构是:当时的约束条件(工期、流量规模、团队认知边界)→ 什么信号触发你意识到方案不够用了(要可量化,比如 Token 成本曲线、P99 延迟、错误率、维护人力)→ V1、V2、V3 各自的取舍边界在哪 → 为什么当时不做一步到位。承认「先跑起来再优化」在快速验证期是合理的,但要能说出你为此留的钩子:接口抽象、可替换的实现层、埋点。成本量化的口径建议固定成「单次调用的 token 数 × 调用次数 × 单价」,再往下拆成上下文长度、重试次数、模型档位三个可调项,这样优化前后能直接算成钱,而不是只说一句「快了很多」。