面灵AI→

小红书后端开发岗面经合集(三):JVM 调优、Spring 循环依赖与 Redis 大 Key 治理

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

面经 01 · 小红书后端开发岗(2025-12-25)

  1. 你实习中实现的 RPC 接口的限流过程是怎样的?怎么在问题出现前避免?
  2. 消息队列信息一致性怎么保证?
  3. 消息队列都有什么,各自都有什么区别?
  4. 实习中 MySQL 涉及到的锁?说说 next-key lock?SQL 语句的 left join、right join、inner join 区别?
  5. 讲讲消息队列加 zookeeper 的实现过程?zookeeper 注册过程?zk 的树状节点?Redis 优点?Redis 分布式锁、分布式 ID?
  6. 说说死锁?如何避免死锁?
  7. Redis 有几种数据结构?每种数据结构能有什么应用?底层实现?
  8. MySQL 分库分表?
  9. Redis 缓存雪崩、击穿、穿透?
  10. 在你的实习或项目中有什么有挑战的工作?相关工程代码有什么印象深刻的实现方法?
  11. 为什么选择从原公司转到小红书实习?
  12. 做两道题:左连接 SQL 语句 + 最长有效括号

面经 02 · 小红书后端开发岗(2025-12-22)

  1. 手撕:字符串相乘
  2. Redis 常用命令
  3. MySQL MVCC
  4. Spring AOP
  5. JVM 垃圾回收机制
  6. Redis 实现分布式锁
  7. synchronized 锁升级机制

面经 03 · 小红书后端开发岗(2025-12-12)

  1. 线程池的核心参数
  2. synchronized 和 volatile 的区别
  3. 手写单例模式,要求双重检查锁
  4. 索引是什么结构,为什么用 B+ 树
  5. 手写 SQL:查出所有学生的学号、姓名,还有总分,然后按总分从高到低排序
  6. 小 B 希望你能够帮到她,你是否愿意?

面经 04 · 小红书后端开发岗(2025-12-10)

  1. 介绍一下你最熟悉的后端项目,你在其中负责了什么?遇到了什么技术挑战,怎么解决的?
  2. 为了保证审核系统的「高可用」,你会从哪些方面设计和考虑?
  3. 如果线上一个审核服务 CPU 占用率突然达到 100%,你的排查思路是什么?
  4. 在审核系统中,如何用 Redis 缓存审核规则?如果规则有更新,如何保证缓存和数据库的一致性?
  5. 谈谈你对 Kafka、Flink 在内容安全中应用的理解
  6. 什么是数据库事务?在审核系统中审核通过后需要更新内容状态并记录审核日志,如何保证这两个操作的事务性?
  7. 假设让你设计一个接口,防止恶意用户高频提交内容来绕过审核,你会怎么做?
  8. 为什么使用微服务架构?它带来了什么挑战?在审核系统中,服务间调用超时了怎么办?
  9. 你如何理解这个岗位中提到的「数据 sense」?能举例说明吗?
  10. 写一个 SQL 查询:找出最近 7 天内被审核员驳回次数最多的前 10 个用户

面经 05 · 小红书后端开发岗(2025-12-08)

  1. ThreadLocal 在小红书的用户会话管理应用中如何避免内存泄漏?结合弱引用和线程池场景分析
  2. 手写无锁化点赞计数器,对比 AtomicLong、LongAdder、Redis INCR 的性能差异
  3. ConcurrentHashMap 扩容原理:多线程环境下如何保证迁移线程安全?画图说明 transfer() 逻辑
  4. 小红书评论列表实现:如何用 CopyOnWriteArrayList 保证读写并发安全?分析其适用性和缺陷
  5. 虚拟线程(Loom)实战:如何改造小红书 IO 密集型服务?对比线程池方案
  6. JVM 内存泄漏排查:小红书 App 后台频繁 Full GC,如何用 MAT 定位 byte 泄漏?
  7. G1 调优实战:给定一个高并发的推荐服务 JVM 参数,MaxGCPauseMillis、InitiatingHeapOccupancyPercent 如何设置?
  8. StringTable 性能问题
  9. 小红书动态生成大量短链接导致 YGC 变慢,如何优化?
  10. Native 内存泄漏:DirectByteBuffer 未释放导致容器 OOM,如何用 NMT 加 pmap 追踪?
  11. JIT 编译优化
  12. 小红书某个热点方法被频繁调用,如何通过 -XX:CompileThreshold 调整触发阈值?
  13. Spring 循环依赖解决:小红书商品服务与库存服务相互注入,三级缓存如何破环?画图说明 Bean 创建过程
  14. 动态数据源切换:小红书多租户架构中,如何基于 AbstractRoutingDataSource 实现分库路由?
  15. Spring 事务失效场景:小红书优惠券服务中 @Transactional 为何不回滚?
  16. Redis 大 Key 治理:小红书热帖评论列表超过 10MB,如何拆分?

面经 06 · 小红书后端开发岗(2025-12-06)

  1. 你写的两个线程交替打印 1-100 的代码,先打印一下结果我看一眼
  2. 你为什么会这么对 Java 的锁进行分类?有这样分类的理由吗?
  3. 你理解的悲观锁就是 synchronized 加 monitor,乐观锁就是 CAS 相关的东西,是这样吗?
  4. 你刚刚说的 synchronized 的底层流程,能再具体说一下吗?
  5. 你刚刚说 synchronized 性能比较差,是这样吗?它的性能一定比较差吗?
  6. 我们聊一下 Lock 的底层数据结构是怎样的
  7. Lock 怎么实现公平与非公平锁呢?
  8. 你说 Lock 的底层结构是「state 变量加队列」,它是怎么通过这个结构实现锁的?
  9. 既然你不清楚节点存储的内容,那如果让你自己实现一把并发锁,队列里需要保存什么信息才能实现锁的功能?多线程下怎么通过这个队列控制线程竞争锁?
  10. 你了解 Java 的集合吗?从底层介绍一下 Java 集合
  11. 你说集合分为 Collection 和 Map 两大类,那 Collection 的结构、分层是怎样的?Collection 里除了 List,还有什么其他类型?
  12. 你了解 Map 吗?
  13. HashMap 中当链表长度达到阈值后会转红黑树,为什么一定要用红黑树?为什么不用其他二叉树?
  14. HashMap 是线程安全的吗?多线程环境下会有什么问题?
  15. 你提到多线程下 HashMap 的操作无法保证原子性,能具体说一下吗?比如 HashMap 扩容时多线程下会出现什么问题?
  16. 你有想过 HashMap 扩容时多线程下出现问题的具体场景吗?两个线程分别处于什么状态、在做什么操作,才会导致问题?
  17. 你说的「分段锁」(JDK 8 之前)和「局部锁」(JDK 8 之后)具体指的是什么?在 HashMap 的数据结构里,这个代码块锁的是什么对象或资源?
  18. 你觉得实现一个高性能的 HashMap,最重要的是什么?
  19. 你了解 HashMap 的哈希函数吗?它是怎样的?怎么保证哈希值分布均匀?
  20. 我们聊一下 Redis,Redis 支持的数据结构有哪些?
  21. 你了解过 Redis 中 HashMap 的扩容机制吗?
  22. Redis 的部署模式你了解吗?哨兵模式怎么部署?哨兵节点是单个还是多个?作用是什么?
  23. 你觉得 Redis 哨兵这种分布式部署模式会有什么问题吗?
  24. Redis 主从节点的数据同步是怎样的?每个节点的数据如何保持一致?
  25. 那有什么办法能做到 Redis 数据不丢失吗?
  26. 你用 Redis 实现过分布式锁吗?是怎么保证原子性的?设置值和设置有效期这两步怎么保证原子性?
  27. 在 Redis 集群模式下,用 Redis 实现分布式锁会有什么问题?
  28. 你用 Redis 读数据时,读的是主节点还是从节点?如果读从节点,可能会出现什么问题?

面经 07 · 小红书后端开发岗(2025-11-21)

  1. 介绍一个你熟悉的项目
  2. 当时为什么做这个项目?上线了吗?
  3. 你认为什么是 RAG?它跟微调有什么区别?效果上会有什么区别?为什么有两种方式?
  4. 有实际做过微调吗?
  5. RAG 去做判卷,你的检索内容是什么?
  6. 用的是什么向量数据库?为什么最后选了它?
  7. Redis 的缓存策略:为什么要去设计一个热点题目缓存这样的东西?
  8. RocketMQ 在正常的发送和消费时,怎么保证消息不丢失?
  9. 消息发送写到 broker 的时候,在你的发送里面要做什么样的设置才可以保证一定会写入?
  10. 你在做哪些开源的事情?
  11. 你对哪一个技术中间件是最熟悉的?
  12. Redis 为什么很快?为什么单线程还会比多线程快,感觉这有点反直觉?多线程比单线程执行会多一些成本吗?
  13. 上下文切换有哪些具体的开销?
  14. 什么是程序计数器?它是跟线程绑定的吗?
  15. Redis 多路复用
  16. 你觉得你对 Redis 的掌握程度是怎么样的?评价一下
  17. 你现在学习是通过什么方式去学习新知识的?有什么感兴趣的方向吗?
  18. 假设你跟你的 mentor 在一件事情上意见完全相反,你会如何处理?
  19. 算法题:不含重复字符的最长子串的长度
  20. 你实际做项目里面,碰到过最难的问题是什么?如何解决?

面经 08 · 小红书后端开发岗(2025-11-19)

  1. 接口和抽象类的区别
  2. 说一下线程池的核心参数
  3. 动态线程池你说的是个什么概念?
  4. 不是说你做了一个动态线程池吗?那 K8s 或者阿里云的服务器其实都有自动扩容功能,比如根据 QPS 自动多开几个 Pod 或者自动加机器,那不就相当于线程池的线程数也变多了吗?
  5. 刚才你说的那个动态线程池,先说默认线程池的工作原理:我有一个任务加到线程池里边,它是怎么一个升级,核心线程数和最大线程数怎么升级的?

《参考解析》

面经 05 / 06 · Java 并发与容器

  1. 锁的升级、AQS 与「分类的理由」:synchronized 的锁升级路径是无锁 → 偏向锁 → 轻量级锁 → 重量级锁:偏向锁把线程 ID 记在对象头 Mark Word 里,同一个线程再进来不用 CAS;出现竞争时升级为轻量级锁,线程在栈上建 Lock Record 并 CAS 抢 Mark Word,失败就自旋;自旋失败或竞争激烈时膨胀为重量级锁,靠 monitor(ObjectMonitor)与操作系统互斥量挂起线程。要能解释为什么「性能一定差」是错的:JDK 6 之后的优化让无竞争场景几乎零成本,差的是高竞争下陷入内核态的挂起与唤醒。ReentrantLock 这一支的底座是 AQS:一个 volatile 的 state 表示同步状态、一个 CLH 变体的双向等待队列存等待线程,抢锁就是 CAS 改 state 失败后把当前线程包成节点入队并挂起,释放时唤醒后继。公平与非公平的差别只在「入队前是否先看一眼队列有没有人排队」——非公平允许插队,吞吐更高但可能饿死;公平按队列顺序,吞吐低但不会有线程长期得不到锁。被追问「自己实现一把锁,队列里要存什么」时,答案是要存线程引用(用于唤醒)、前驱/后继指针(维护顺序与取消)、等待状态(区分取消与正常等待),并且要能在节点取消时把前驱的 next 指向后继,避免唤醒链断裂。

  2. HashMap、红黑树与并发问题:为什么用红黑树——链表长度到 8 且容量到 64 就转树,把最坏查询从 O(n) 降到 O(log n),防的是哈希碰撞攻击(人为构造大量同桶 key)。为什么不选普通二叉搜索树:退化后仍是 O(n);不选 AVL:平衡更严格、插入删除的旋转更多,而 HashMap 的读写更在意插入与删除的均摊成本,红黑树用近似平衡换取更少的旋转。HashMap 的哈希函数是 h = key.hashCode() ^ (h >>> 16),把高 16 位异或到低位,因为桶下标只取低位((n-1) & hash),这样能让高位也参与运算,分布更均匀——「均匀」靠的是扰动函数加容量为 2 的幂这两件事配合。线程不安全的具体表现:并发 put 可能丢更新(两个线程同时判断桶为空,后者覆盖前者);JDK 7 的头插法在并发扩容时可能形成环形链表导致 get 死循环;JDK 8 改成尾插修掉了环,但 resize 时仍可能丢数据、size 计数不准、并发写同一个桶覆盖。所以并发场景要用 ConcurrentHashMap。它 JDK 7 是分段锁(Segment 继承 ReentrantLock,锁的是段),JDK 8 改成 CAS + synchronized 锁单个桶的头节点,锁粒度更细、并发度更高,扩容时多线程可以协同迁移(用 ForwardingNode 标记已迁移的桶并让后续线程帮忙搬)。「实现高性能 HashMap 最重要的是什么」这类开放题,答到「哈希分布均匀 + 冲突处理策略 + 扩容代价 + 内存布局友好」就够了。

  3. 线程池的原理与动态调整:任务提交后的判断顺序是——核心线程未满就新建核心线程;核心线程满了就尝试入队;队列满了才创建非核心线程直到 maximumPoolSize;再满就走拒绝策略。为什么先入队再扩线程:线程创建与销毁成本高,而队列能起到削峰与复用作用,直接扩线程会让 CPU 在高并发下被线程切换拖垮。用无界队列(默认的 LinkedBlockingQueue 不传容量就是近似无界)会让 maximumPoolSize 形同虚设、任务无限堆积直到 OOM,所以生产上一定要有界并配明确拒绝策略(AbortPolicy 快速失败、CallerRunsPolicy 反向施压、或自定义落库重试)。「动态线程池」的意义是在不重启的前提下按监控指标调 core/max/队列容量,并让队列里已有的任务也能被感知(有版本号或直接重建队列并迁移任务);被追问「K8s 自动扩容不是也能加线程吗」时要答出区别:扩 Pod 是横向加实例,能解决总吞吐与故障隔离,但单实例内的线程数、队列长度、拒绝策略仍然是静态的,请求在单机内排队过长时扩 Pod 并不能改善单个请求的排队延迟,且扩容有冷启动延迟;动态线程池管的是单机内的并发度适配,两者互补而非替代。

面经 05 · JVM 与线上问题排查

  1. 内存泄漏、GC 调优与 Native 内存:MAT 定位 byte 数组泄漏的标准流程是拿 heap dump(OOM 时自动 dump 或 jmap)、看 Dominator Tree 找占用最大的对象、用 Path to GC Roots(排除弱引用)确认是哪条引用链把它留住的——常见来源是缓存没有上限、ThreadLocal 未 remove、监听器未注销、以及大数组被静态集合持有。G1 调优要先明确目标:MaxGCPauseMillis 设的是停顿目标(不是保证),设太小会让 G1 选更小的回收集、Young GC 更频繁、吞吐下降,通常从 200ms 量级起按 SLO 调;InitiatingHeapOccupancyPercent 决定何时启动并发标记周期,设太高会等堆快满才开始、容易触发 Full GC,设太低会让并发标记跑得太勤吃 CPU,一般按堆使用率的观测曲线调而不是照搬默认值。DirectByteBuffer 的泄漏要靠 NMT(-XX:NativeMemoryTracking=detail + jcmd VM.native_memory)看内部与映射内存的增长,再用 pmap 对照进程实际 RSS,确认是直接内存而不是堆内存;DirectByteBuffer 由 Cleaner 在 GC 时释放,堆很小但直接内存很大时 GC 不频繁,就会出现「堆正常、容器 OOM」——解法是显式限制 -XX:MaxDirectMemorySize、及时释放或用池化。StringTable 与 YGC 的关系在「大量短链接生成」这类场景里很典型:intern 或大量字符串拼接会让 StringTable 膨胀、Young GC 时要扫描并清理,表现为 YGC 时间随字符串数量增长;优化方向是别滥用 intern、用 StringBuilder 拼接、必要时调 -XX:StringTableSize。

  2. Spring 循环依赖与事务失效:三级缓存破环的关键是提前暴露引用:一级 singletonObjects 放成品,二级 earlySingletonObjects 放未填充属性的早期引用,三级 singletonFactories 放能产出早期引用的工厂(用于在需要代理时提前生成 AOP 代理)。A 创建时把工厂放进三级缓存,注入 B 时 B 又回头要 A,就从三级缓存拿到早期引用(并升级到二级),从而打破环。要主动说明边界:构造器注入的循环依赖破不了(对象还没实例化就需要对方),prototype 作用域的循环依赖也破不了(没有缓存),Spring Boot 2.6 之后默认禁止循环依赖,鼓励用重构或 @Lazy 解决。@Transactional 失效的典型场景要能列举:方法不是 public、同类内部直接调用(this 调用绕过代理)、类没有被 Spring 管理、异常被 catch 掉没抛出、抛出的是受检异常而默认只回滚 RuntimeException 与 Error、事务传播行为设置成 NOT_SUPPORTED 或 REQUIRES_NEW 用错、以及多数据源没配对应的事务管理器——更根本的一句话是「事务是基于代理的,没走代理就没有事务」。

面经 01 / 04 / 07 · 中间件、系统设计与 AI

  1. MQ 一致性、幂等与不丢失:消息不丢要三段都保证:生产者侧用同步发送或事务消息,确认 broker 落盘成功再算发送成功;broker 侧开同步刷盘加重多副本(主从同步复制)避免主挂丢数据;消费侧处理完业务再提交位点,失败则重试并配死信队列。一致性的通用解法是「本地事务 + 消息」:把业务写入与消息记录放在同一个本地事务里(本地消息表)或直接用事务消息,再由投递侧保证至少一次投递,消费侧靠唯一键(业务 ID + 消息 ID)或状态机做幂等,最终一致靠对账补偿。RocketMQ 与 Kafka 的差异答在语义层面:Kafka 强在吞吐与流处理生态、分区顺序、但延迟消息要自己实现;RocketMQ 有事务消息、定时/延迟消息、消息回溯与更完善的死信重试,业务消息场景更顺手。消息队列选型判据是「要不要顺序、要不要事务、延迟级别、堆积能力、运维成本」。死锁部分要能说清四个必要条件(互斥、占有并等待、不可抢占、循环等待)与破坏方式:统一加锁顺序破坏循环等待、一次性申请全部资源或超时释放破坏占有等待、用可中断的 tryLock 与带超时的锁、以及把大锁拆小。排查死锁看线程栈(jstack 会直接标出 Found one Java-level deadlock)与锁等待监控。审核系统那道设计题的核心是「先定位瓶颈再谈优化」:CPU 100% 的排查顺序是看线程栈找热点方法、看 GC 频率(可能是 GC 线程吃满)、看是否有正则回溯/序列化/加密这类 CPU 密集操作、看是不是流量突增或死循环。

  2. Redis 与 RAG:Redis 快的原因要答全:纯内存操作、单线程避免锁竞争与上下文切换、IO 多路复用(epoll)让一个线程管理大量连接、精简高效的数据结构与协议。为什么单线程反而比多线程快:多线程的收益在于并行计算与利用多核,但 Redis 的瓶颈通常在内存与网络 IO,多线程反而带来锁竞争、上下文切换与缓存失效的成本;Redis 6 引入的多线程也只用于网络读写,命令执行仍是单线程,正是这个道理。上下文切换的开销包括寄存器与程序计数器保存恢复、栈与地址空间切换导致 TLB 与 CPU 缓存失效、内核调度开销。程序计数器是线程私有的、记录当前线程执行的字节码行号,所以它与线程绑定。RAG 与微调的区别要说在「改不改模型参数」:RAG 把知识放在外部检索、成本低、易更新、可溯源,但受限于检索质量与上下文长度;微调改参数、更擅长风格与格式适配、知识注入的效果不如检索且更新要重训。做判卷这类任务,检索内容应该是评分标准、参考答案与历史判例,而不是整本书——这决定了切块粒度与元数据设计。向量数据库选型看维度、数据量、过滤能力(前置过滤 vs 后置过滤)、是否要混合检索与 rerank、以及运维复杂度。RocketMQ 不丢失与上面的三段一致,尤其要强调「同步发送 + 同步刷盘 + 主从同步复制」这三个开关对可靠性的影响,以及消费端幂等是终局保障。