头条后台开发面经

头条后台开发面经

先说下背景。投的岗位是头条基础架构部门的后台开发的内推,但是部门实际上的需求是需要一个能写前端的实习生来做适配。

听到这个需求之后的我一惊。

“这TMD不就是招全栈吗!”

这样的想法突然在我脑中浮现。

我不禁想起了一位师兄说过的话:“公司招全栈都是骗小孩的。”

再看看自己,前端不精通,后台也不懂,只能做做全栈搬砖才能维持得了生活的样子,于是没技术的泪水从我的双眼汩汩流出。

没办法了,就让我来做这个被骗的小孩子吧。

简历投递

有了上次投网易的经验,我意识到了一个很重要的地方。

首先简历是面试官了解你的第一渠道,他的提问肯定是跟着你的简历来的,你简历怎么写他就怎么问。

上次我的简历里面写的项目情况太过详细,直接提到了RabbitMQRedisDocker等技术名词。这就导致了面试官可以直接从这些技术上开始发问,也是我翻车的直接原因(面试官问到了RabbitMQ的消息确认机制,但是我只记得一个consumernoAck参数,没讲出producerconfirmChanneltransaction)。

所以这次我的简历写的就很简单,只讲了项目是什么,没讲到细节。

事实证明这是一个正确的决定,因为这让我主导了整个面试的过程。

面试过程

一面

面试是通过牛客网的面试平台来做的。不得不说头条的效率很高,我投递简历之后一小时HR小姐姐就打电话来跟我约时间了,挂电话之后不到一分钟就收到了面试通知的邮件。

到了约定的时间,面试官进入了频道,面试流程正式开始:

简单自我介绍一下就开始了提问。因为是技术岗,自我介绍这方面并不是很重要的部分,所以没必要太严肃。

  1. TCP相关问题

    Q: TCP有了解吧,讲一下四次挥手的过程

    A: 嗯,我们都知道TCP是一个全双工的协议,断开连接的请求可以由任意一方发起。比如说断开的发起者A,接受者B。这时先由A发出FIN包表示我要中断这个连接,B接收到之后发送一个FIN ACK,表示我收到了这个请求。之后B把读写任务完成之后再发送一个FIN包给A,表示我已经OK了,可以关了。A收到这个FIN后发送一个FIN ACK表示我已经知道你OK了。B收到ACK之后就可以关闭连接,但是A会进入一个TIME_WAIT状态(这个一定要提到,是重点)。

    Q: 那这个TIME_WAIT是什么呢?(果然)

    A: TIME_WAIT主要是因为TCP里面不会对ACK包进行ACK,所以A无法确定自己的ACK是否被B收到。这时候有两种情况,一种是B收到了ACK,那么没问题,B关闭了连接,A等待一个TIME_WAIT之后关闭连接。另一种是B没收到ACK,过了CLOSE_WAIT之后重新发送一个FIN。如果这时候A的连接已经关闭,并且该socketfd被其他进程使用的话,这个FIN就可能会影响到其他的进程。所以需要设置一个等待时间,这个时间一般是2MSL(ip包的存活时间),以此保证连接安全。
    (但是因为TIME_WAIT会导致fd利用率下降,所以一般有两种解决方式,一种是使用socket选项对TIME_WAIT状态的fd进行复用,或者是客户端发起断开,让TIME_WAIT由客户端处理)

    Q: 拥塞控制和流量控制呢?

    A: 吧啦吧啦吧啦……

  1. 数据库相关

    Q: 你们有学数据库吧

    A: 额,有(其实我很想说没学。。因为我数据库确实没学好,让我讲事务锁的实现,查询优化之类的我肯定是讲不出来的。。。)

    Q: 那能讲一下SQL里面GROUP BY是做什么用的吗?
    
    A: 啊,这个(虚惊一场)。GROUP BY是把查询出来的表按照字段分组的一个指令,比如说count()之类的函数,GROUP BY可以把它们合起来。

    (这个时候我发现面试官好像还想问点别的,心里那叫一个慌啊。但是好在我灵机一动,转移了话题)

    A: 说到SQL,我之前有做过一个很有意思的查询优化。

    Q: 哦?说来听听 (太好了,上套了)

    A: 嗯嗯(此时我的头点的像磕头机一样)。情况是这样的吧啦吧啦吧啦……(此处省略一万字,大致就是一个有两个嵌套子查询JOIN时因为表过大触发临时表存储造成索引丢失,之后导致百万级表全表扫描的问题,解决方案是手动加了一个查询时的索引,估计是MySQL优化器的问题。反正不管怎样,把面试官忽悠的得不要不要的)

    Q: 嗯,这个可以有(赞赏

  2. 写代码环节

    面试的流程很快,之前只讲了十几分钟的样子,就到最后的环节了。

    Q: 来做一下这道题吧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    题目描述:
    输入一个数组,找到其中第一个缺失的正整数
    时间O(n),空间O(1) (指复杂度)

    样例:
    input: 1 2 3 5 -1
    output: 4
    input: 2 4 3 0
    output: 1
    input: 7 8 9
    output: 1

    我想了大约一分钟,有了思路。

    大致思路就是用空间换时间(没办法。。我真的很久没写算法题了。。),使用一个bitmap来存储插入的记录,标记一遍之后扫描一遍找到第一个没被标记的位就可以。

    但是尴尬的是,我几乎不会写C++了。。。本来想用vector来解决,结果被扩容的问题给恶心到了,只能跟面试官讲了思路。面试官看出了我的窘迫,又给我换了道题。

    接下来的题就比较中规中矩,写一个函数来把一段代码里的注释删掉。(c++的单行多行注释//, /* ... */

    不过我还是没写出来,又是只讲了思路(尴尬到极点,因为面试官出题前有说他们很看重代码风格。。),之后解释了自己的窘迫情况。

    不过好在面试官表示理解(可能是对我之前的表现比较满意,而且我也把思路讲出来了)。

一面就到此结束了,面试官问我有啥要问的,我就例行公事地问了下对自己表现的满意程度。

“挺牛逼的,就是编码习惯不太好,回去还是多写点。”

“嗯嗯。”

可真把我给牛逼坏了。

二面

之前的面试官面完我后叫我等等,他去叫来了leader,之后就是第二轮面试。

之前给我内推的师兄曾经说过他们的leader会把一些东西挖的很深,果然名不虚传。。。

首先也是自我介绍,不过这次我讲的详细了一些,把项目经历也讲了几遍,提到了一些自己做过比较重要的事情。

  1. WebServer相关

    Q: 你平常写这个项目的话,应该还是比较了解HTTP吧?能讲讲WebServer主要是做什么的吗?有哪些WebServer产品?

    A: WebServer的话,Apache, Nginx这些比较常见,我们是用Nginx搭的一个反向代理。吧啦吧啦吧啦(此处省略一万字,大致讲了我们整个网络拓扑)。此外Nginx还可以做静态文件代理之类的,还有负载均衡。

    Q: 嗯,那你如果要写一个WebServer的话要做些什么呢?

    A: 额,我不是很明白您的意思……(真的没搞懂,不懂的时候一定要问清楚)

    Q: 比如一个HTTP请求过来,你要怎么处理呢?

    A: 哦哦,明白了(原来他想问的是HTTP底层连接调度之类的)。吧啦吧啦吧啦……(大概讲了下TCPServer的一些东西,比如说bind, listen, accept这些术语,然后又讲了从fork & accept到多线程和线程池,再到reactor这些I/O多路复用模型 )

    Q: 嗯。(一本满足)

  2. HTTP相关

    Q: 讲一下HTTP包具体的报文结构

    A: 嗯,吧啦吧啦吧啦……

    Q: HTTP的Cookies是做什么用的

    A: 吧啦吧啦吧啦……

    Q: 如果没有Cookies怎么实现

    A: 也可以用jwt,在Authentication里面放上token

    Q: Session是什么原理?

    A: 吧啦吧啦吧啦……

    Q: 你们的Session是怎么实现的?

    A: 就是用户登录成功后给他一个session_id,作为key,然后把用户的一些相关信息的JSON字符串存进去。

    Q: Session_id怎么生成的?

    A: (我靠,这都要问)我们用了一个第三方的包,里面是直接用redisincr实现ID。

    Q: 那怎么保证用户自己无法伪造一个Session_id?

    A: 我们会基于时间戳之类的给用户返回一个MD5+salt加密的tokenid和里面存的token都对应session对象才能通过校验。

此处突然转弯

  1. Redis相关

    Q: 你们是用Redis做这个事,那么Redis能做什么?

    A: (心中暗喜,这个我熟)可以做缓存,也可以做Pub/Sub的一个消息队列。

    Q: 常用的数据结构有哪些呢?

    A: 简单的kvmap呀,hash, list之类的(内置的ziplist, zipmap这些没讲)。

    Q: Pub/Sub是什么样的?

    A: 大致就是先declare一个queueproducer负责往里面丢东西,consumer订阅之后就可以从里面获取消息。

    Q: 如果Redis撑不住了怎么办?

    A: 这就触及到我的知识盲区了。

又突然转弯

  1. 数据库相关

    Q: 你们的数据库是单机是吧?

    A: 嗯。(虽然很难为情,但确实是的)

    Q: 那假如现在我突然给你很高的并发请求,比如百万级,数据库单机肯定撑不住的,这时候怎么做?

    A: 可以分库分表,分布式存储。

    Q: 那我查询的时候要怎么查询呢?

    A: 这个时候就需要把查询包装成一个服务,用一个query proxy做这个事情。

    Q: 这个query proxy具体是做了什么事情呢?

    A: 主要是内置一个MySQLquery parser吧,可以先检查SQL的语法问题,并且查下各节点数据库的metadata,看看应该给它接到哪个数据库,之后分配一条查询连接(以上都是我猜的,如果让我实现一个代理我会这么做。。。)。

    Q: 嗯,差不多吧。

接下来又讲了几个之前遇到过的救灾业务场景,就进入了福利的编码时间。

  1. 写代码

    1
    2
    题目描述:
    输入一个数组,把0放到左边,其他放到右边

    用一个cursor标记一下0元素的最后位置,遇到0交换一下就行,注意更新cursor。

面试总结

面试结束了,leader大概给我讲了一下工作的情况,不出意外应该是稳了。

整个面试流程大致是这样,有些细节记不太清了。

总结下来,有几点比较重要:

  • 理论基础,计网,OS,数据库,这些都应该能够融会贯通。
  • 有项目的一定要把项目里的每个细节都抠到,多思考。(我是背了多年的锅才能回答上leader的问题)
  • 理论基础和工程经验都不太行的同学一定要多练习写代码,可以写一些TCPServer,上leetcode刷刷题。(像我这样写题用C++结果STL都忘记怎么用的肯定是不行的。。。)
  • 面试不要太紧张,要尽可能去把自己懂得东西都讲出来。

现在想到的大概就这些,有机会会再写一篇博客讲一下MySQL查询优化的过程。