字节跳动 后台实习三面面经
阅读原文时间:2021年04月20日阅读:1

    先说一下我的情况,国内top10 小硕,成绩中等,半年小厂实习经验,主要用的语言是java。总共是一轮电话面和三轮视频面,三轮视频面原本打算一天面完,当时二面面的不是很好,而且面完也没有让我进行下一轮面试,当时就以为已经凉了,都开始自抱自泣了。没想到后面hr竟然来和我约三面时间,说是那天三面面试官有事没法面试,当时很震惊。三面面完当晚就收到了hr的口头offer,头条效率还是很高的。

一.第0面 电话简历面(21分钟)

因为我是投简历给hr后直接面试,没经过笔试,所以加了一轮电话简历面,问的都很基础,只有20分钟,基本不会问的太深。
1.面试官:说一下进程与线程的区别
我:进程是比线程大的一个概念,进程参与cpu调度和资源分配,线程只参与cpu调度,共享进程的资源。
面试官:线程都共享进程资源,那线程有什么自己独占的资源
我:线程有自己的栈,pc计数器,还有一些线程的缓存

2.面试官:volatile了解吗,说一下它有什么作用
我:因为线程都有自己的缓存,线程修改数据之后,其他线程不能马上知道。有了volatile之后,线程修改数据之后需要马上刷新缓存到内存,其他线程读的时候也要从内存中读最新值。volatile主要就是用来保证内存的可见性。
面试官:还有什么别的作用
我:volatile还可以防止指令的重排序。比如给两个变量a,b赋值,先给a赋值,再给b赋值。指令重排序后可能会先给b赋值,再给a赋值。volatile可以避免这样的重排序的出现。
面试官:防止重排序是防止所有的指令重排序吗?比如后面有一个c变量,只给c加volatile,那a,b可以重排序吗
我:应该可以,volatile只管c的

3.面试官:atomic包用过吗,原子性怎么保证
我:使用底层的cas指令实现修改的原子性,修改前判断一下是否还是修改前的那个值,是的话就修改,不是就修改失败

4.面试官:数据库的隔离级别说一下
读未提交,可以读到事务未提交的数据,有脏读的问题
读提交,解决脏读问题,只能读到事务已提交的数据,但是有不可重复读的问题
可重复读,解决不可重复读问题,可能会丢失更新
序列化,事务串行执行

5.面试官:下面问一些数据结构的问题,树的前序遍历非递归实现
我:(我这里说的不是很清楚,面试官应该是自己脑补出了过程)因为是前序遍历,访问到哪个节点就可以马上访问了。不断访问树的左子节点,把它加入栈中。左子节点为null时出栈,把它的右子节点压栈。
面试官:树的层次遍历
我:先进先出,这里使用队列。把根节点放入队列,然会不断放入左右子节点即可。
面试官:刚才用到了栈和队列,那如果要用栈来实现队列,怎么实现
我:用两个栈来实现(这道题以前做过,但是记不清了,只记得这个,然后想了很久)
我:不知道了
面试官:刚才你说用两个栈,这个思路是对的。你想象一下有两个栈,先往左边这个栈加数据比如1,2,3,然后要取的时候怎么办
我:把1,2,3出栈加入到右边这个栈,这样顺序就倒回来了
面试官:对,然后,这时候再加入1个4,怎么加
我:再把右边的数据倒回来(这个时间复杂度太高了)
面试官:你可以把右边的栈看成一个读缓存,读的时候就往右边读,写的时候往左边写
我:懂了,右边的栈空的时候,再一次性从左边读取就行了,左边可以继续加
面试官:对。
面试官看还有时间,又问了一道题:还是关于栈的,实现一个最大栈,每次都可以获取到栈中所有数字的最大值
我:用两个栈来实现,左边是一个正常的栈,右边把最大值压栈,就是说这个数字比栈顶元素大就说明他是最大值,需要压栈(比较经典的做法,但是面试官的答案好像不是这个)
面试官:你这个做法不对吧,肯定是有问题的,我给你找个反例。(然后时间到了也没找到反例,就不了了之了)

二.第1面 技术基础面(50分钟)

1.面试官:说一下进程创建的过程。(我当时一脸懵逼,我简历也没写熟悉操作系统啊)
我:这个不是很清楚,操作系统都不熟
面试官:操作系统都不熟吗,内存管理,文件管理,cpu这些都不熟吗
我:内存管理知道一点。内存都是按页来分配,有一个页表记录了哪些页分配给了哪个进程。
面试官:了解了(应该是了解我操作系统有多菜了),那linux文件管理熟吗
我:不熟不熟
面试官:那你们linux课都上了什么
我:就讲了一些基本操作
面试官笑了:没有吧,文件管理肯定有讲过,node,inode
我:这些早忘了

2.面试官终于不问了:看你简历有写到netty,多路复用原理有了解吗
我:阻塞io是每个io用一个线程来等待,多路复用是把这些等待集中到一个线程,这样可以节省线程。(本来原理挺清楚的,但是说的不是很清楚,这个也需要多练习)
面试官:是这样吗
我:是把等待io操作是否就绪集中到一个线程
面试官:那你对io多路复用涉及的linux系统调用有了解吗(怎么又回到操作系统了)
我:不清楚
面试官:select和epoll了解吗,说一下他们的区别
我:select通过轮询的方式实现,需要遍历每一个io操作,判断是否就绪;epoll基于事件驱动,io操作就绪时会通知epoll
面试官:linux有哪几种io
bio,nio,aio

3.面试官:网络这一块熟悉吗,比如我在浏览器输入一个链接,过程是怎么样的
我:首先从dns服务器获取域名对应的ip地址,然后通过该地址建立起tcp连接,然后在这个连接之上发送http请求给web服务器
没说完就被打断了,面试官:http请求的格式是怎样的
我:第一行是请求行,标明请求方法get,post这些,还有一些其他的信息;然后是请求头,可以设置一些属性;最后是请求体
面试官:http请求的方法有哪些,刚才说了有get,post
我:get,post,put,delete,head
面试官:刚才说把http请求发送给web服务器,netty是怎么处理的
我:eventloop会监听到有读请求,然后把数据读到缓存里面,然后交给我们的handler处理
面试官:netty的线程模型了解吗
我:每个eventloop代表一个线程,它负责监听io操作是否就绪(selector是否在其他线程有待考究),就绪就执行相应的io操作,处理handler的执行。handler也是在eventloop线程执行,所以业务如果有耗时操作,需要建新的业务线程执行。(说的比较乱)

4.面试官:redis的数据结构说一下
我:字符串,列表,map,set,有序集合
面试官:有序集合是怎么实现的
我:用跳跃表实现排序,用map来快速定位某一个节点
面试官:描述一下跳跃表长什么样
我:是一个多级链表,但是层数是随机的,层数越高概率越低
面试官:跳跃表查询,插入,删除的复杂度是多少
我:查询平均复杂度lgn(插入过程不是很熟,想了一会儿,只说了查询复杂度)
面试官:了解了。redis主从复制,集群部署这些有了解吗
我:不是很清楚(以前有看过,但是面试基本不问这么难,所以慢慢的就忘了)
面试官:没关系

5.面试官:kafka了解吗,说一下他的架构
我:(这个问题太大了,我都不知道当时说了什么)
面试官:好了,你说一下kafka是怎么存储的吧
我:是顺序读写,每次写都是追加到末尾,读的话根据offset顺序读取
面试官:kafa有什么作用
我:异步和解耦
面试官:kafka分区有什么作用
我:分区可以提高消费者的并发度,提高读消息的性能
面试官:一个分区最多能被几个消费者消费
我:一个消费者组里面只能有一个
面试官:kafka有哪些机制来提高它的可用性
我:多副本的机制,使用冗余来提高可用性
面试官:还有吗?算了,就说多副本吧,每个副本都可以读写吗
我:不是,只有leader副本可以读写
面试官:那leader副本怎么选出来的
我:broker也会选出一个leader,由这个leader来选择leader副本
面试官笑了:哦,broker也选一个leader是吧(确实挺有意思)

6.面试官:看你简历说zookeeper很熟(只是写了了解原理),说一下zookeeper怎么保证数据的一致性
我:zookeeper有自己的一致性协议来保证一致性(这个说了等于没说,可能是太紧张了,还好面试官继续问)
面试官:这个协议是怎么样的
我:zookeeper会选举一个leader,由这个leader发起提案,交给他的follower投票。有一半以上的票数就表示提案成功,leader负责把数据同步到follower上。
面试官:kafka和zookeeper有什么关系,或者说kafka在哪些地方用到了zookeeper
我:kafka会存储一些元数据在zookeeper上,比如有哪些分区,broker,消费者;还有broker集群的监控
面试官:你刚才说broker集群的监控是什么意思
我:就是监听哪些broker上线,哪些broker下线
面试官:了解了,我们下面来做一道题

7.头条例行手撕算法题
求矩阵中最长上升路径,是一道比较难的动态规划题
https://blog.csdn.net/liangzhaoyang1/article/details/50973400

面完之后,面试官让等一会儿,马上二面。

三.第2面 技术深度面(70分钟)

二面还是挺难的,问的问题很有深度,很多都没答出来,一度以为要挂了。
1.面试官:介绍一下你们实习的项目
我:blabla~
面试官:你们项目数据库的隔离级别是什么
我:不知道(没想过这个问题)
面试官:那你说一下数据库有哪些隔离级别
我:(前面说过了)
面试官:不可重复读的问题什么时候会出现
我:比如一个事务读一条记录读两次,中间有另一个事务修改了这个数据,那两次读的数据就不一致
面试官:有一个请假的业务,必须保证每天至少有一个人不请假,请假是通过修改用户的状态来实现,这个时候用什么隔离级别我:可重复读。
面试官:这个能保证吗
我:(想了一会儿)应该用序列化,因为请假操作前需要先查是否只有自己没请假,在查完之后可能就有别的事务修改了请假状态,导致没有人不是请假状态
面试官:mysql一般怎么实现序列化
我:在查询的时候加写锁,select….For Update(innodb 好像就是这么实现的)
面试官:那请假如果不是修改状态,而是插入一条请假记录呢,写锁能锁住吗
我:那就用表锁(除了innodb以外,其他应该是表锁)

2.面试官:看你的项目用zookeeper实现分布式锁,那用redis能不能实现
我:redis有一个方法setnx,保证只有一台机器能操作成功,设置成功即获得锁
面试官:对,那redis实现放到线上去用,需要注意什么问题
我:可能需要保证redis的可用性
面试官:还有吗(好像不是面试官想要的答案)
我:想不出来了

3.介绍一下你那个分布式日志系统
我:blabla~
面试官:收集到日志以后,你是怎么存储的
我:收集日志之后,把日志发送到kafka,然后elasticsearch从kafka上获取日志,搜索通过elasticsearch实现
面试官:elasticsearch熟悉吗
我:不是很熟悉,搜索模块不是我做的
面试官:说一下发送消息到kafka的过程
我:发送的时候会添加到一个buffer里面,有一个sender线程会检查这个buffer是不是已经满了,满了的话就发送到相应的分区的leader副本上
面试官:客户端怎么知道要发到哪里去呢
我:它会维护一个元数据,根据这个元数据可以知道有哪些分区,leader副本在哪
面试官:对,那这些元数据是从哪来的
我:zookeeper上应该有
面试官:如果我要回溯kafka,访问某天的消息,这个要怎么实现
我:可以找到那天第一条消息的offset,然后从那条offset开始消费
面试官:kafka3有一个新特性,可以根据时间回溯
我:….
面试官:kafka扩容怎么实现,要保证顺序不变
我:不清楚

4.面试官:tcpip协议熟吗,如果a和b建立了tcp连接,后来因为中间路由器坏了,a收不到b的消息,b能收到a的消息,这时候a,b的状态怎么改变
我:不清楚
面试官:a发消息给b,中间可能会经过很多节点,要怎样才能知道讲过了哪些节点
我:不清楚
面试官:http和https区别了解吗
我:不是很熟
面试官:那http2和http3呢
我:http2了解一些,它允许多个http请求在一个tcp连接上并行发送。像tcp协议一样,是基于流的,每个数据包都分配一个序列号,这样就不怕数据乱序了。

5.后面是一些尬聊,问业余干啥,遇到问题怎么解决

6.例行手撕算法题
把一个长度为n的数组分成k段,让每段和的最大值最小
https://blog.csdn.net/sunjilong/article/details/8655316

三.boss面(40分钟)

boss面可能更关注个人的综合能力,主要是聊项目,谈人生,技术问题不是很难。

1.面试官:说一下归并排序和快速排序的过程。
我:归并排序按照下标把数组分成大小相等的两个子数组,然后递归调用将子数组排序,排序之后将两个子数组合并
面试官:这个合并的过程是怎样的
我:就是比较两个子数组第一个数字的大小,取小的那个到新数组,取了之后指针往后移,直到有一个数组全部读完
面试官:那再说一下快排的过程
我:归并排序是按照下标来划分成两个子数组,快排是按照大小来划分子数组。首先选一个基数字来比较大小,可以随机选,一般是选最后一个。有一个指针用来遍历数组,另一个指针指向小于基数字的最大下标。经过一个数字时,如果比基数字小,指向小数字的指针往前移,交换这两个指针指向的数字。最后把数组分成两个子数组,一个是大于基数字的,一个是小于基数字的,然后再递归将两个子数组排序。
面试官:这个排序相比,各有什么优缺点
我:快速排序平均性能更好,但是最坏时间复杂度是n平方。归并排序需要额外申请存储。
面试官:除了这两个还有哪些至少nlgn的排序
我:堆排序
面试官:说一下堆排序的过程
我:先把数组建成一个最小堆,然后堆顶的数字就是最小的,然后取出堆顶的数字。这时随便取一个数字替换堆顶,然后再维护最小堆的性质。这样不断从堆中取,就排好序了。
面试官:这需要首先建好一个堆吧,这个建堆的过程是怎样的
我:最小堆的性质是父节点比子节点小,所以我只需要为前面一半的父节点维护最小堆性质就行了,从下层开始不断往上维护最小堆性质。

2.面试官:介绍一下你们实习的项目
我:blabla~
面试官:详细介绍一下你实现的一个模块
我:blabla~
面试官:如果给你现在来做,你能想到哪些改进方案
我:用消息队列来实现成功的通知
面试官:用消息队列有什么好处
我:异步,解耦,缓冲
面试官:你们调用其他服务是怎么实现的
我:是内部的rpc框架
面试官:说一下这个rpc框架
我:有一个注册中心,我们和其他服务都往这上面注册地址,我们要调用的时候就从注册中心获取地址,然后发起http请求
面试官:你们应该是有多台服务器,怎么选调哪一台
我:这个不了解

3.面试官:你的项目用netty干什么的
我:用来发送和接收消息
面试官:说一下netty处理的流程
我:(一面说过)
面试官:使用netty的时候有没有遇到什么困难
我:tcp传输的时候是没有消息边界的,处理这个问题比较困难
面试官:那你是怎么解决这个问题的
我:我在每条消息前都加入一个消息长度,每次从缓存中读取相应长度的字节,要是不够说明消息还不完整
面试官:要是连长度字段的长度都不够怎么办
我:我是用int来表示长度,就是四个字节,读之前我会先判断是否足够四个字节,不够就不读
面试官:要是缓存长度大于你的长度怎么办
我:那我就从缓存中读取这么长的字节就好,剩下的是下一条消息

4.后面也是尬聊阶段,问了职业规划,发展方向,优势

5.例行手撕算法题
将k个有序链表合并成一个有序链表
 

四.总结

1.前期知识储备
主语言(比如java)相关的基础必须很熟,虽然面试不一定能用上
计算机基础:数据库,网络,操作系统,算法
互联网常用技术:redis,kafka,zookeeper,netty,面试官一般对这些很感兴趣,如果熟悉的话就有更多共同话题(如果都不熟的话,那计算机基础估计会问的很深)

2.项目一定要想好怎么介绍
项目简单介绍,业务流程
难点,亮点
怎么改进
项目相关技术

3.面试的时候想好再说,说的时候要有条理,确保面试官能听懂。知道答案是一回事,能在面试的时候说清楚是另一回事,平时也要注意联系把自己理解的东西说出来。

4.每个公司都有不同面试风格,需要提前了解做好准备。比如头条注重算法,每轮面试都有算法题,并且都是白板写代码,面试前就得做好相应的准备。