快手Java后端开发暑期一面凉经
面试题目
基本信息
- 面试公司:快手(招聘房产部门)
- 面试时间:2025-03-27
- 面试岗位:Java 后端开发
- 面试时长:约 1 小时,全程问项目,穿插少量八股
自我介绍 & 项目介绍
- 自我介绍。
- 项目介绍。
- 项目大概分成了哪几个领域或模块?
微服务与架构设计
- 你大概知道几种微服务架构?或者说设计理念(DDD)?
- 讲一下你项目里的双拦截器机制,具体指的是什么?
- 校验登录状态是怎么实现的?
- 项目中有用到 AOP 吗?
基础组件与算法
- 雪花算法为什么需要进行改进?你具体改了什么?
- 前置授权和限流、令牌桶。
缓存与数据一致性
- 假如 Redis 里面没有数据,你会去查数据库(DB)吗?如何保证 Redis 和 DB 的一致性?
- 写到 Redis 里的订单信息会设置过期时间吗?
- 缓存三大件(缓存穿透、缓存击穿、缓存雪崩)。
- 你提到引入了本地缓存、Redis 和数据库,这三者之间的一致性怎么保证?
- 你的本地缓存如何实现的?(Caffeine,面试官提到了一个 Google 的中间件)
消息队列
- 为什么不可以全用 RocketMQ?一定要引入 Kafka 吗?
- 你知道 Kafka 和 RocketMQ 的主要区别吗?
手撕代码
SQL 题
有一个表记录用户操作,字段有 user_id、action、time。请查出 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
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 吞吐量 | 极高,适合日志/流处理 | 较高,适合业务消息 |
| 延迟 | 毫秒~百毫秒 | 毫秒级,更低延迟 |
| 事务消息 | 支持(有限) | 原生支持 |
| 顺序消息 | 分区内有序 | 支持全局/分区有序 |
| 延迟消息 | 不原生支持 | 原生支持 |
两者共存的典型原因:日志采集、大数据管道用 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 为链表数量。也可用分治合并,两两合并,时间复杂度相同但常数更优。