快手 Java 面试

95 阅读4分钟

快手 Java 面试复盘分享


面试流程

  1. 自我介绍:按照常规流程介绍了自己的背景、实习项目。
  2. 项目深挖:面试官让我详细讲解实习项目中的架构设计和技术选型,过程中会不断追问一些实现细节。

技术问题汇总与思考

1. Kafka 消息如何保证不丢失?

  • 生产端:开启 acks=all,确保消息被所有 ISR 副本确认;配合 retries 重试。
  • Broker:开启 replication.factor>=3,避免单点失败;开启 min.insync.replicas 保证至少两个副本成功写入。
  • 消费端:先消费再手动提交 offset(避免先提交 offset 再消费导致消息丢失)。

2. LRU 缓存的实现

  • 基于 双向链表 + HashMap
  • HashMap 存储 key -> 节点指针;链表维护访问顺序,最近使用的放在头部,最久未使用的在尾部,淘汰时删除尾部节点。
  • Java 中 LinkedHashMap 自带 LRU 实现。

3. Kafka 一致性检查

  • 依靠副本同步机制,ISR(In-Sync Replicas)保证只有和 leader 保持同步的副本才能参与写入确认。
  • 事务场景下,可以利用 Kafka 的 幂等生产者(idempotent producer)事务生产者(transactional producer) 保证 exactly-once 语义。

4. 分布式存储 & CDN 加速

  • 分布式存储:通过副本、多副本一致性协议(如 Raft、Paxos)保证高可用。
  • CDN 加速:就近访问,边缘节点缓存;源站更新后可通过 TTL 或主动刷新保证一致性。

5. ES 与 MySQL 数据一致性

  • 使用 双写 + 异步同步机制:业务写 MySQL 后,通过 binlog(如 Canal)实时同步到 ES。
  • 问题:可能出现短暂的不一致;解决方法包括幂等更新、定期全量校验。

6. Kafka 异步处理导致延迟

  • 核心逻辑放主线程处理,非核心逻辑异步化。
  • 比如:下单请求 → 主线程写库 + 返回成功;日志/埋点数据异步写 Kafka。

7. Redis 分布式锁

  • SET key value NX EX seconds 实现加锁,value 通常是唯一请求 ID。
  • 解锁时校验 value,避免误删他人锁。
  • 更高级方案:Redisson 提供自动续期和安全解锁机制。

8. 锁未释放问题

  • 若线程崩溃导致锁未释放,可:

    • 设置过期时间(EX seconds);
    • 用 Redisson 的看门狗机制自动续期,保证锁不会过早过期,同时防止死锁。

9. ThreadLocal 弊端

  • ThreadLocal 适合单线程上下文存储,但跨线程(如 RPC 调用)会丢失。
  • 缺陷:内存泄漏(线程池复用场景),以及上下文透传问题。
  • 解决:使用 显式参数传递 或者 TransmittableThreadLocal

10. MySQL 自增算法的弊端

  • 自增 ID 在分布式场景下可能出现热点写入,且主从复制中 binlog 可能导致冲突。
  • 迁移/合并多库时容易出现 ID 冲突。
  • 解决:雪花算法(Snowflake)、UUID、数据库号段模式。

11. 乐观锁 vs 悲观锁

  • 乐观锁:通过版本号/时间戳,适合冲突少的场景。
  • 悲观锁:通过 for update 加锁,适合冲突多的场景。
  • 结合使用:先乐观尝试,失败时回退到悲观锁,兼顾性能和安全。

12. Spring 事务隔离级别

  • 四种隔离级别:读未提交、读已提交、可重复读、串行化。
  • 默认:MySQL InnoDB 使用 可重复读(RR)
  • 可重复读实现:依赖 MVCC + Next-Key Lock,避免幻读。

13. SQL 慢查询分析

  • 开启慢查询日志,定位耗时 SQL。
  • 使用 EXPLAIN 分析执行计划。
  • 关注是否命中索引、是否出现回表、是否有全表扫描。

14. 复杂 SQL 索引走法

SELECT * FROM t WHERE a = ? AND b > ? ORDER BY c;
  • 索引 (a,b,c) 可以全用上。
  • a=? 精确匹配,b>? 范围查询,c 仍可利用索引进行排序(归并排序),比无索引快。

15. 算法题:二叉树层序遍历

  • BFS 思路,用队列实现。
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if (root == null) return res;
    Queue<TreeNode> q = new LinkedList<>();
    q.add(root);
    while (!q.isEmpty()) {
        int size = q.size();
        List<Integer> level = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            TreeNode node = q.poll();
            level.add(node.val);
            if (node.left != null) q.add(node.left);
            if (node.right != null) q.add(node.right);
        }
        res.add(level);
    }
    return res;
}

总结

面试官不仅耐心,还会引导我思考。通过这次复盘,我意识到:

  1. 项目要多准备细节,可能会被深挖。
  2. 分布式、缓存、数据库、消息队列这些高频问题要形成体系化理解。
  3. 算法要多刷,现场实现更熟练。

面试失败不可怕,重要的是积累与总结。