快手Java后端开发暑期一面凉经

快手 · Java后端开发 · 一面 · 2026-04

面试题目

基本信息

  • 面试公司:快手(招聘房产部门)
  • 面试时间:2025-03-27
  • 面试岗位:Java 后端开发
  • 面试时长:约 1 小时,全程问项目,穿插少量八股

自我介绍 & 项目介绍

  1. 自我介绍。
  2. 项目介绍。
  3. 项目大概分成了哪几个领域或模块?

微服务与架构设计

  1. 你大概知道几种微服务架构?或者说设计理念(DDD)?
  2. 讲一下你项目里的双拦截器机制,具体指的是什么?
  3. 校验登录状态是怎么实现的?
  4. 项目中有用到 AOP 吗?

基础组件与算法

  1. 雪花算法为什么需要进行改进?你具体改了什么?
  2. 前置授权和限流、令牌桶。

缓存与数据一致性

  1. 假如 Redis 里面没有数据,你会去查数据库(DB)吗?如何保证 Redis 和 DB 的一致性?
  2. 写到 Redis 里的订单信息会设置过期时间吗?
  3. 缓存三大件(缓存穿透、缓存击穿、缓存雪崩)。
  4. 你提到引入了本地缓存、Redis 和数据库,这三者之间的一致性怎么保证?
  5. 你的本地缓存如何实现的?(Caffeine,面试官提到了一个 Google 的中间件)

消息队列

  1. 为什么不可以全用 RocketMQ?一定要引入 Kafka 吗?
  2. 你知道 Kafka 和 RocketMQ 的主要区别吗?

手撕代码

SQL 题

有一个表记录用户操作,字段有 user_idactiontime。请查出 2025 年 3 月份操作次数大于 5 次的用户。

算法题

合并 K 个有序链表。


反问环节

(略)


参考解析

微服务架构 & DDD

常见微服务设计理念:面向服务架构(SOA)、领域驱动设计(DDD)、事件驱动架构(EDA)。DDD 核心是将业务划分为有界上下文(Bounded Context),每个上下文独立部署,通过领域事件通信,强调实体、值对象、聚合根等概念。回答时结合项目模块说明如何划分领域边界更有说服力。

双拦截器机制 & 登录校验

常见实现:第一层拦截器负责 Token 刷新(将 Redis 中 Token 的过期时间续期),第二层拦截器负责校验登录状态(判断 ThreadLocal 中是否存有用户信息)。两层分离的好处是职责单一,未登录用户也能触发 Token 刷新逻辑,避免频繁掉线。

雪花算法改进

原生雪花算法依赖机器时钟,存在时钟回拨问题,可能导致 ID 重复。常见改进方向:①引入等待策略(回拨时间短则等待);②使用扩展位记录回拨次数;③借鉴美团 Leaf、百度 UidGenerator,用数据库或 ZooKeeper 分配 workerId,并在内存中备份上次时间戳。

Redis 与 DB 一致性

推荐使用旁路缓存(Cache Aside):读时先查缓存,未命中再查 DB 并写入缓存;写时先更新 DB,再删除缓存(而非更新)。删除而非更新是为了避免并发写时的脏数据。延迟双删可进一步降低不一致窗口期。订单等关键数据建议设置合理过期时间作为兜底。

缓存三大件

  • 缓存穿透:请求的 key 在缓存和 DB 中都不存在,解决方案:布隆过滤器或缓存空值。
  • 缓存击穿:热点 key 过期瞬间大量请求打到 DB,解决方案:互斥锁(分布式锁)或逻辑过期(不设真实 TTL)。
  • 缓存雪崩:大量 key 同时过期或 Redis 宕机,解决方案:过期时间加随机抖动、Redis 集群高可用、熔断降级。

本地缓存 + Redis + DB 三级一致性

写操作顺序:先更新 DB → 删除/更新 Redis → 使本地缓存失效(可通过消息广播或设置极短 TTL 实现)。本地缓存(Caffeine)通常设置较短的过期时间(秒级),主要用于抗热点读,对强一致性要求高的场景应跳过本地缓存层。

Kafka vs RocketMQ

维度KafkaRocketMQ
吞吐量极高,适合日志/流处理较高,适合业务消息
延迟毫秒~百毫秒毫秒级,更低延迟
事务消息支持(有限)原生支持
顺序消息分区内有序支持全局/分区有序
延迟消息不原生支持原生支持

两者共存的典型原因:日志采集、大数据管道用 Kafka,业务订单、通知等用 RocketMQ 的事务/延迟消息特性。

SQL 题参考答案

SELECT user_id
FROM user_actions
WHERE time >= '2025-03-01'
  AND time < '2025-04-01'
GROUP BY user_id
HAVING COUNT(*) > 5;

合并 K 个有序链表

最优解使用最小堆(优先队列),将每个链表的头节点入堆,每次取堆顶节点接入结果链表,再将该节点的 next 入堆,时间复杂度 O(N log K),N 为总节点数,K 为链表数量。也可用分治合并,两两合并,时间复杂度相同但常数更优。