本文转载自:Java后端四面字节跳动,唬住面试官你也能拿30K+;附面经+面试题
面试前
头条每次面试前会有 HR 约时间,并提前发一个 zoom 地址过来,三场技术面与一场 HR 面全都是视频面试。不得不说视频面试体验比电话面试好很多(尤其是对我这种很关注面试官反应的),假如有 HR 同学看到这篇文章,推荐考虑一下用视频面试取代电话面试,效率会更高。
头条的三场技术面风格都很类似:
- 问项目,抓出一些你擅长的领域或场景
- 问系统设计题,每题都会不断深化需求让你应变和权衡
- 问一道算法题(不难不偏),先看思路,再要求写一下伪代码看边界条件能不能一次过
这个面试流程我自己也一直在用,尤其是系统设计加上不断的需求变更,能比较全面地考察后端的基本功和工程思维。因此头条的面试套路很对我胃口,甚至好多类似的问题我自己也都问过候选人。
一面
- 介绍一下自己, 为什么选择出来看看机会
- 聊项目, 警报怎么做的, 统一接入监控项怎么做的
- 聊项目, 配置中心项目, 问实时配置推送怎么做
- 讨论为什么选择所有的组件依赖放在配置中心中控制
- 我现在要做一个限流功能, 怎么做?
- 令牌桶
- 这个限流要做成分布式的, 怎么做?
- 令牌桶维护到 Redis 里,每个实例起一个线程抢锁,抢到锁的负责定时放令牌
- 怎么抢锁?
- Redis setnx
- 锁怎么释放?
- 抢到锁后设置过期时间,线程本身退出时主动释放锁,假如线程卡住了,锁过期那么其它线程可以继续抢占
- 加了超时之后有没有可能在没有释放的情况下, 被人抢走锁
- 有可能,单次处理时间过长,锁泄露
- 怎么解决?
- 换 zk,用心跳解决
- 不用 zk 的心跳, 可以怎么解决这个问题呢?
- 每次更新过期时间时,Redis 用 MULTI 做 check-and-set 检查更新时间是否被其他线程修改了,假如被修改了,说明锁已经被抢走,放弃这把锁
- 假如这个限流希望做成可配置的, 需要有一个后台管理系统随意对某个 api 配置全局流量, 怎么做?
- 在 Redis 里存储每个 API 的令牌桶 key,假如存在这个 key,则需要按上述逻辑进行限流
- 某一个业务中现在需要生成全局唯一的递增 ID, 并发量非常大, 怎么做
- snowflake (这个其实答得不好,snowflake 无法实现全局递增,只能实现全局唯一,单机递增,面试结束后就想到了类似 TDDL 那样一次取一个 ID 段,放在本地慢慢分配的策略)
- 算法题, M*N 横向纵向均递增的矩阵找指定数
- 只想到 O(M+N)的解法 补充: 这几天刷 leetcode 碰到这题了, 240. Search a 2D Matrix II. 办法是从左下角或右下角开始查找.
- 有什么想问我的?
限流,分布式锁,UUID 都属于后端的经典面试题,这轮面试的参考价值挺大的。
二面
- 平时用的工具链和技术栈是什么
- golang 踩过坑吗?
- 答了之前 PingCAP 面试时面试官问的 for-range 里的 go-routine 闭包捕获问题
- 这段 golang 代码有没有 bug(还是一个 for-range 的坑)
- 有 bug,for-range 的 value 引用拷贝问题
- Java 中 HashMap 的存储, 冲突, 扩容, 并发访问分别是怎么解决的
- Hash 表,拉链法(长度大于8变形为红黑树),扩容*2 rehash,并发访问不安全
- 拉链法中链表过长时变形为红黑树有什么优缺点?
- 优点:O(LogN) 的读取速度更快;缺点:插入时有 Overhead,O(LogN) 插入,旋转维护平衡
- HashMap 的并发不安全体现在哪?
- 拉链法解决冲突,插入链表时不安全,并发操作可能导致另一个插入失效
- HashMap 在扩容时, 对读写操作有什么特殊处理?
- 不知道
- ConcurrentHashMap 是怎么做到并发安全的?
- segment 分段锁
- Java 有哪些锁机制, 分别有什么特点?
- Synchronized、可重入锁
- 知道 CAS 吗? Java 中 CAS 是怎么实现的?
- Compare and Swap,一种乐观锁的实现,可以称为"无锁"(lock-free),CAS 由于要保证原子性无法由 JVM 本身实现,需要调用对应 OS 的指令(这块其实我不了解细节)
- MySQL 的存储引擎用的是什么?(InnoDB)为什么选 InnoDB?
- 几乎所有公司用 MySQL 都用 InnoDB,降低踩坑成本;聚簇索引,MVCC
- MySQL 的聚簇索引和非聚簇索引有什么区别?
- 聚簇索引的叶子节点是数据节点(比如定义了主键时的主键索引),非聚簇索引叶子节点是指向数据块的指针
- B+树和二叉树有什么区别和优劣?
- B+树是多叉树,深度更小,B+树可以对叶子节点进行顺序遍历,B+树能够更好地利用磁盘扇区;二叉树:实现简单
- 针对一个场景设计索引,具体场景忘记了,反正考察的是联合索引与列选择性的知识
- 现有一个新的查询场景, 要怎么解决?
- 假如要查 A in () AND B in (), 怎么建索引?
- 只给选择性高的一列建索引,这里因为两个都是范围查询所以另一个是走不到索引的(这里答的不好,其实也可以建联合索引然后用 (A,B) in ((1,2),(3,4)) 的方式去查)
- 查 A in () AND B in () 时, MySQL 是怎么利用索引的?
- 先走一个非聚簇索引,查询出行数据后再用另一列回表做筛选
- 假如查询 A in (), MySQL 是针对 N 个值分别查一次索引, 还是有更好的操作?
- 不知道,有了解的同学可以留言 (补充, @BillyLu 贴出了文档 equality-range-optimization, 大意是对非唯一索引 MySQL 会使用 index dive 的方式估算这个 range index 涉及的行数, 结合where optimization 中说明的在走 index 时假如涉及行数过多会走 full table scan, 那么假如 estimation 认为这次 IN 不够好, 是会走全表扫描的. 不知道除此之外, 面试官还有没有想考察的点)
- 用过 Redis 的哪几种数据结构? (都用过) ZSET 是怎么实现的?
- 跳表
- zrange start, stop, 总长度为 n, 复杂度是多少?
- O(logN) (答得不好,实际是 O(M+log(N)), M 是结果集基数 stop-start)
- Kafka 的消费者如何做消息去重?
- MySQL 去重、Redis 去重、假如场景量极大且允许误判,布隆过滤器也可以
- 介绍一下 Kafka 的 ConsumerGroup
- 挺长的,略
- Kubernetes 和 Docker 用得怎么样? (我:在公司推行布道)
- 给它们贡献过代码吗?(我:没有…)
- 时序型数据库的存储结构是怎么样的?
- 讲了 prometheus 1.x 和 2.x 的存储结构
- LSM 树了解吗? 是一种什么存储结构?
- Log-Structured Merge Tree,牺牲读性能换取性能,RocksDB、HBase、Cassandra 都在用,结构有点忘了,只说了先写 memtable 再刷盘成 sstable
- 在生产中用过 Cassandra 和 RocksDB 吗? 量有多大?
- 用过,Cassandra 存调用链,RocksDB 做 flink 和 Kafka Stream 的本地状态存储
- Cassandra 的墓碑机制是什么?
- 不知道,对 Cassandra 停留在使用阶段
二面问了好多中间件的基础知识,最后都没有时间问算法了。面完之后心里就想:头条的面试真是耿直啊,Java 的 HashMap、锁机制、CAS 到 MySQL 的索引,Redis 的 zset,再到 LSM 树,全都是后端或中间件相关的热门面试题。当然这些问题热门也是有原因的,即使候选人准备过,多扣一点细节也能很快就能看出来候选人是真的理解还是仅仅只是看了相关资料。
三面
- 聊项目和工作经验
- 用 Kubernetes 的过程中踩过哪些坑?
- 考虑一个业务场景: 头条的文章的评论量非常大, 比如说一篇热门文章就有几百万的评论, 设计一个后端服务, 实现评论的时序展示与分页
- 我: 需不需要支持页码直接跳转?
- 面试官: 支持和不支持两种场景都考虑一下
- 我: 不需要支持页码翻页就传评论 id 用 offset 翻页
- 假如用 id 翻页的方式, 数据库表如何设计? 索引如何设计?
- (文章id, 评论id) 建联合索引,评论 id 需递增
- 假如量很大, 你觉得需要分库分表吗? 怎么分?
- 需要分,分表有个权衡,按文章 id 分表,读逻辑简单,但写有热点问题;按评论 id 分表,读逻辑复杂,但写压力就平均了。写是要首先保证的,而读总是有缓存等方案来折中,因此按评论 id 分表好。
- 分库分表后怎么查询分页?
- 每张表查 N 条数据由 client 或 proxy merge
- 分库分表后怎么保证主键仍然是递增的?
- 讲了 TDDL 的办法:有一张专门用于分配主键的表,每次用乐观锁的方式尝试去取一批主键过来分配,假如乐观锁失败就重试
- 现在需要支持深分页, 页码直接跳转, 怎么实现?
不能做精准深分页,否则压力太大,找产品进行妥协,在50或100页后数- 据分页是否可以不完全精确,假如可以,那么缓存深页码的起始评论 id
- 瞬时写入量很大可能会打挂存储, 怎么保护?
- 断路器
- 断路器内部怎么实现的?
- 可以用 ringbuffer
- 断路器会造成写入失败, 假如我们不允许写入失败呢?
- 先写进消息队列,削峰填谷异步落库
- 算法题: N 场演唱会, 以 [{startTime, endTime}…] 的形式给出, 计算出最多能听几场演唱会
- 先讲了思路, 按 endTime 升序排列,再顺序取最多场次
- (讲完思路之后)屏幕共享给我, 用你最熟悉的语言把这个算法实现
- 用 go 实现了一版
- 你用了贪心法, 贪心可能会存在什么问题?
- 局部最优,在这个问题里,只能找到一个可能解,无法找到所有排列方式
我觉得三面这个架构设计问得还不错,一个问题把后端的工程能力考的很全面了。
HR 面
大同小异,问经历,问离职原因,问职业规划,问待遇,问期望。
小结
- 面试难度:正常
- 面试体验:挺好
- 问题偏向:架构设计,算法
头条面试流程很专业:每轮都会提前约好时间,面试时长都在40~50分钟,按时开始面,每轮之后发反馈短信邀请候选人评价面试,精准地过两天再约下一轮。整个像一台精密运作的机器。头条的面试我个人挺欣赏的,考察得比较全面,面试官会抓住你没有说清楚的地方来深入或者变换场景让你应变,大家可以试试看去面一下,即使不打算去也可以作为一次免费的能力评定。
再说说面试官,每位面试官都听得出来是在一线写代码的,而且很认真地在听我说话(这当中有视频的功劳,我可以看到面试官在认真听),感觉工作中也都会是好相处好合作的类型。
总结
回头看面试的过程,有好多不尽如人意的地方,不过最后能够拿到offer ,还是很幸运。最后再做一些补充性的小结:
一些经验:
简历里写了的项目,以及熟练程度在"掌握"以上的领域与中间件要好好准备,当面试官问你一个偏门的问题时,他内心其实也没希望你能答上来。而当面试官问你简历上涉及的问题时,假如你答不上来,那面试官就觉得这个人要么是眼界太低,会了一点就觉得自己掌握了,要么是简历造假在胡吹,这两种都非常不利;
在上一条的基础上,可以准备一个最得意的项目,在简历上和面试过程中引导面试官往这块聊;
面试前心里可以准备一个方法论:明确面试官想招怎样的人有哪些特质,在面试过程中努力表现出这些特质。这听起来是句正确的废话,但面试的过程不可控因素太多,有一个清晰的目标在脑子里能帮你在手足无措时想到说什么。举个例子,有一轮中面试官问我有什么问题时,我就问贵司的对应岗位会面临哪些技术挑战(当然要先说清楚这不是在质疑他们没有挑战,只是自己渴望挑战);