南京某小厂 Java 后端实习一面:Lua 秒杀、本地消息表与 RAG 链路
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 你这个社区生活服务平台是个人项目吗?
- 先不用讲业务功能,直接说这个后端项目分了哪些模块?
- 简历里写用 Lua 脚本实现秒杀,为什么不直接用 MySQL 扣库存?
- 如果库存只有 1,两个用户同时请求你的 Lua 脚本,怎么保证只有一个人成功?
- 简历写了「条件更新扣减库存」,这个条件更新的 SQL 怎么写?
- 订单变更同事务写入本地消息表,为什么要多建一张消息表?
- 项目里的 Spring AI 加 RAG,用户提问之后,从收到问题到生成回答,完整经过哪些步骤?
- 压测把平均响应时间降下来了,具体改了哪部分代码?为什么能提升?
《参考解析》
为什么用 Lua 而不是 MySQL 扣库存:秒杀的特点是瞬时高并发集中在一个商品上,用 MySQL 扣库存会让所有请求在同一行上排队拿行锁,连接池很快被打满、响应时间雪崩。把扣减放进 Redis,靠单线程执行保证原子性,Lua 脚本把「读库存、判断、扣减」合成一次执行,数据库只承接扣减成功的少量请求。代价是要处理 Redis 与数据库的最终一致(预扣加异步落库、失败回滚)。
库存只剩 1 时怎么保证只成一单:Lua 脚本在 Redis 里原子执行,脚本内先判断库存大于 0 再减,把结果返回给客户端,只有拿到成功返回的请求算抢到。关键是不能在脚本外用 GET 再 DECR。多实例部署时扣减结果要与订单号绑定。
条件更新 SQL:update stock set count = count - 1 where id = ? and count > 0,再看影响行数是否为 1,为 0 就说明已经扣完。这一句把判断和更新合成一个原子操作,靠行锁加 where 条件,比先查再改安全。
本地消息表为什么单独建一张:业务数据和消息在同一个本地事务里写,事务提交就代表消息一定落库,再由定时任务异步投递并重试,从而避免「业务成功、消息丢了」和「消息发了、业务回滚了」两种不一致。单独建表是为了不动业务表结构、能独立重试和补偿,也方便查积压和人工介入。
RAG 链路与压测优化:问题进来先做改写和意图判断,走向量与关键词两路召回,融合后重排取 top-k,拼进 prompt 连同历史对话一起交给模型,流式返回并附引用;工程上要处理切块策略、embedding 模型版本、结果缓存和超时降级。压测优化则要说清瓶颈原来在哪(慢查询、锁竞争、序列化、连接池大小)、改了哪段代码、指标从多少降到多少,只给「优化了接口」这种结论会立刻被追问到答不上来。