问麻了!腾讯一面究竟问了什么?从HTTP到算法····

下面我将分享一位同学在腾讯PCG的trpc一面的面试经历,对于这次面试,他的评价是,既全面又深入,涵盖了从网络基础到算法的多个领域,你试试呢?

【提醒】通过这次面试经验,你将可以复习到以下知识点:

  • 浏览器到服务器的请求流程
  • HTTPS与HTTP的差异
  • JWT Token的原理和优势
  • TCP连接的建立和断开流程
  • epoll的工作机制
  • Linux下内存的监控方法
  • GMP调度模型
  • Context的设计理念
  • LevelDB的数据插入过程
  • 数据库缓存一致性问题
  • 查找两个字符串的最大公共子串算法

面试官: 同学你好,欢迎参加今天的面试。我想首先问一下,当你通过chrome浏览器输入一个地址到最终页面内容显示,中间发生了哪些过程?

求职者: 好的,这个过程大致可以分为以下几个步骤:

  1. 浏览器检查缓存,看请求的资源是否已被缓存并且还有效,如果有效直接使用缓存资源。
  2. DNS解析,浏览器将域名解析为服务器的IP地址。
  3. 浏览器与服务器建立TCP连接,这包括了三次握手的过程。
  4. 浏览器发送HTTP请求,请求方法可能是GET、POST等。
  5. 服务器处理请求并返回HTTP响应,响应包含状态码和请求的资源。
  6. 浏览器解析HTTP响应,并根据响应内容如HTML、CSS、JavaScript等构建页面内容。
  7. 浏览器渲染页面,显示给用户。

面试官: 那你能解释一下HTTPS和HTTP之间的区别是什么吗?

求职者: 当然。HTTPS其实是HTTP的安全版本,它通过在HTTP下加入SSL/TLS协议层来增强安全性。主要区别在于:

  • 加密传输:HTTPS利用SSL/TLS进行数据加密,确保数据传输过程中的安全;
  • 身份验证:HTTPS提供了身份验证机制,可以验证访问的网站是否为服务器上的合法网站;
  • 数据完整性:防止传输的内容被中间人篡改。

面试官: 好,那你知道HTTP1.1和HTTP2的区别吗?

求职者: 是的,我了解。HTTP/1.1和HTTP/2的主要区别包括:

  • 多路复用:HTTP/2可以在一个TCP连接中并行处理多个请求,而HTTP/1.1则需要为每个请求/响应建立一个连接或使用队头阻塞;
  • 服务器推送:HTTP/2可以服务器端可以未经请求提前发送资源,提高加载效率;
  • 头部压缩:HTTP/2采用HPACK压缩格式减小了头部大小,减少了延迟;
  • 二进制格式:HTTP/2传输的是二进制格式,而非HTTP/1.1的文本格式,使得解析更加高效。

面试官: 那基本的HTTP加密算法原理,你能解释一下对称加密和非对称加密吗?

求职者: 当然可以。在加密算法中,对称加密使用相同的密钥进行加密和解密,它速度快,适合大量数据的加密,如AES算法。但对称加密的缺点在于密钥的分发,因为如果密钥被泄露,加密信息就不安全了。

非对称加密使用一对密钥,即公钥和私钥。公钥可以公开分享,用于加密数据,而私钥保密,用于解密。非对称加密如RSA算法,更安全,适合少量数据加密,经常用于加密对称加密的密钥,或数字签名。

面试官: 既然提到了安全,你可以讲一下JWT Token是怎么做的吗?

求职者: JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。一个JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

  • 头部通常包括了令牌的类型,即JWT,以及使用的签名算法,如HMAC SHA256或RSA。
  • 载荷包含了声明(Claim),声明是关于实体(通常是用户)和其他数据的陈述。
  • 签名用于验证消息的发送者是谁,以及消息没有被更改过。

JWT通常用于身份验证和信息交换,因为它可以确保在传输过程中的安全性。

面试官: 那JWT的Token相对于普通的Token有什么优势?

求职者: JWT的主要优势在于:

  • 自包含:JWT包含了所有用户需要的信息,避免了多次数据库查询;
  • 跨语言:由于是JSON格式,所以可以被任何支持JSON的语言读取;
  • 安全性:通过签名保证了Token的真实性和完整性;
  • 易于传输:可以通过URL、POST参数或者在HTTP header中传输;
  • 性能:由于不需要查询数据库,所以在验证Token时可以提高性能。

面试官: 那么,refresh Token和access Token之间的关系是什么?

求职者: access Token是用来访问API的短期凭证,而refresh Token通常用来获取新的access Token。由于access Token具有较短的有效期,一旦过期,客户端可以使用refresh Token请求新的access Token,这样可以避免用户频繁地重新认证。这个机制可以提高安全性,因为即使access Token被泄露,它很快就会过期。而refresh Token通常更加安全,存储在应用服务器上,不会经常传输。

面试官: 好的,接下来让我们讨论一下TCP连接的建立和断开。建立和断开TCP连接的流程一般是什么样子的?

求职者: TCP连接的建立使用的是三次握手过程,具体如下:

  1. 客户端发送一个SYN包(synchronize序号)到服务器,并进入SYN_SENT状态。
  2. 服务器接收到SYN包后,回复一个SYN+ACK包(确认加同步),然后服务器进入SYN_RCVD状态。
  3. 客户端收到服务器的SYN+ACK包后,发送一个ACK包(确认)到服务器,然后进入ESTABLISHED状态,这时候连接建立成功。

断开连接则使用四次挥手过程,具体如下:

  1. 当一方完成数据传输后,发送FIN包(结束)以关闭连接的一半。
  2. 另一方接收到FIN后,发送ACK包(确认),确认序列号为接收到的序列号+1。
  3. 另一方如果数据也发送完毕,也发送一个FIN包
  4. 最初发送FIN的一方接收到FIN后,发送ACK包。这时候,双方都可以关闭连接了。

面试官: Close Wait状态是什么意思,Fin Wait和Close Wait之间的区别是什么?

求职者: Close Wait状态是指TCP连接的一方在收到对方的FIN包,发送ACK包后进入的状态。在这个状态下,等待本端的应用程序关闭连接。而Fin Wait状态是指发送FIN包的一方,在等待对方的ACK包和对方的FIN包。

其实,Fin Wait状态主要是发起连接关闭的一方所处的状态,而Close Wait状态是响应连接关闭的另一方所处的状态。

面试官: 如果TCP连接建立好以后往其中写数据,写得太快了会怎么样?

求职者: 如果发送方发送数据太快,而接收方来不及处理,就会造成接收方的缓冲区满了。TCP提供了流量控制机制,通过窗口大小(Window size)来控制发送方的发送速度,防止接收方的缓冲区溢出。

如果接收方的窗口为0,发送方就会停止发送数据,并开始发送窗口探测包。等到接收方处理了一些数据,有空闲的缓冲区后,它会更新窗口的大小并通知发送方,发送方再根据新的窗口大小发送数据。

面试官: 你了解epoll吗,它和FD是什么关系?

求职者: 是的,epoll是Linux下多路复用IO接口之一,它可以监控多个文件描述符(File Descriptor,简称FD)上的IO事件。与select和poll不同,epoll使用一种更有效的方式来处理大量的文件描述符。在epoll使用中,我们需要使用epoll_create创建一个epoll对象,然后通过epoll_ctl将感兴趣的文件描述符添加到这个epoll对象中。当IO事件发生时,可以通过epoll_wait获取这些事件。

面试官: 那你知道边缘触发(Edge Trigger)和条件触发(Level Trigger)的区别是什么吗?

求职者: 边缘触发(Edge Trigger,ET)和条件触发(Level Trigger,LT)是epoll事件的两种触发模式。

  • 条件触发,即Level Trigger,是默认的工作模式。在这种模式下,只要FD上有可读写的数据,epoll_wait就会返回该FD。这意味着,如果不处理,它会不断地通知。
  • 边缘触发,即Edge Trigger,只有数据状态发生变化时,epoll_wait才会返回FD。比如说,只有当从无数据到有数据,或者从不可写到可写时,才会通知一次。

边缘触发通常效率更高,但需要更仔细地控制读写,避免数据丢失。

面试官: Linux进程占用的内存空间怎么查看?

求职者: 在Linux中,我们可以通过top命令来查看进程占用的内存空间。当运行top命令时,它会显示系统的整体资源使用情况,包括CPU、内存等,并列出占用资源最多的进程。

面试官: TOP命令中有三个和内存相关的列,分别是什么意思?

求职者: 在top命令的输出中,与内存相关的三个列通常是:

  • VIRT:虚拟内存的使用量,包括进程使用的所有内存空间,含有进程未直接使用的库文件等。
  • RES:常驻内存的大小,即实际使用物理内存的大小。
  • SHR:共享内存的大小,表示多个进程共享的内存部分。

面试官: 那你对操作系统的虚拟地址空间了解吗?

求职者: 是的,虚拟地址空间是操作系统提供给进程的一种抽象概念,它允许每个进程认为自己是在独占地使用物理内存。实际上,操作系统会通过内存管理单元(MMU)将虚拟地址映射到物理地址上。这样做的好处是,每个进程都有一个统一的地址开始方式,简化了内存访问,并且可以通过分页或分段机制提供内存保护,防止进程间相互干扰。

面试官: Golang的Slice的Size和Cap有什么区别?

求职者: 在Go语言中,Slice是一个引用类型,它的结构包含指向数组的指针、长度(len)和容量(cap)。

  • Size(长度) 指的是Slice中实际包含的元素个数。
  • Cap(容量) 则是指Slice底层数组中从开始元素到数组末尾元素的个数。

一个Slice可以在不超过其容量的前提下动态增长,这通常通过append函数实现。当超过容量时,Slice会自动扩容,这时通常会分配一个新的底层数组,并将原有元素复制到新数组中。

面试官: 那Slice扩容后在原Slice上修改数据,新Slice会发生变化吗?

求职者: 扩容后,如果新的Slice是基于原有Slice创建的,修改原Slice上的数据不会影响新Slice,因为新Slice已经指向了一个新的底层数组。但如果没有发生扩容,新Slice和原Slice共享同一个底层数组,这时在原Slice上的任何修改都会反映到新Slice上。

面试官: 如果在C++ std中执行类似操作,比如对vector取引用然后扩容,会怎么样?

求职者: 在C++中,如果对一个vector取了引用,然后对这个vector进行扩容(比如push_back新元素导致容量不足时),那么原来的引用可能会变得无效,因为vector在扩容时可能会分配一块新的内存空间来存储元素,并将旧元素复制到新的内存空间中,这会改变元素的地址。

面试官: Go语言中关闭Channel有哪些需要注意的事项,怎样判断channel是否已经关闭了呢?

求职者: 在Go语言中,关闭Channel需要注意以下几点:

  • 只有发送方应该关闭channel,接收方不应该关闭,否则会导致panic。
  • 关闭一个已经关闭的channel也会导致panic。
  • 关闭channel后,再往channel发送数据会导致panic。

要判断channel是否已经关闭,可以在接收时使用两个值的接收操作,如果第二个值返回false,那么表示channel已经关闭。例如:

v, ok := <-ch
if !ok {
    // channel已经关闭
}

面试官: Go的interface和Java的interface有什么区别,继承有什么区别?

求职者: Go的interface和Java的interface的主要区别在于:

  • Go的interface是隐式实现的,只要一个类型实现了interface中所有的方法,那么这个类型就实现了该interface,不需要显式声明。
  • Java的interface需要显式实现,一个类必须使用implements关键字来指明它实现了哪个interface。

面试官: 好的,现在我们来做一个算法题。给定两个字符串,你能写出一个函数来找到它们的最大公共子串吗?

求职者: 当然,我们可以使用动态规划的方法来解决这个问题。这里是一个可能的解决方案:

func longestCommonSubstring(s1, s2 string) string {
    dp := make([][]int, len(s1)+1)
    for i := range dp {
        dp[i] = make([]int, len(s2)+1)
    }

    maxLength := 0
    endIndex := 0

    for i := 1; i <= len(s1); i++ {
        for j := 1; j <= len(s2); j++ {
            if s1[i-1] == s2[j-1] {
                dp[i][j] = dp[i-1][j-1] + 1
                if dp[i][j] > maxLength {
                    maxLength = dp[i][j]
                    endIndex = i
                }
            } else {
                dp[i][j] = 0
            }
        }
    }

    return s1[endIndex-maxLength : endIndex]
}

这个函数首先初始化了一个二维切片dp,用于存储最大公共子串的长度信息。然后遍历两个字符串中的每个字符,如果字符相同,就在dp中相应位置累加长度。同时,我们用maxLength变量来记录出现过的最大长度,以及用endIndex来记录当前最大公共子串的结束位置。最终,根据maxLengthendIndex,我们可以获取到最大公共子串。

面试官: 好的,你的方法很不错。那你能解释一下这个算法的时间复杂度和空间复杂度吗?

求职者: 这个算法的时间复杂度是O(nm),其中n和m分别是输入的两个字符串的长度。因为我们需要对这两个字符串的每个字符进行比较。空间复杂度是O(nm),因为我们需要一个二维数组来存储中间结果。不过在某些情况下,我们可以对空间进行优化,比如使用滚动数组来减少空间复杂度到O(min(n, m))。

面试官: 非常详尽的解答,感谢你今天的参与,面试到此结束。我们会尽快给你反馈。祝你好运!

求职者: 非常感谢这次面试的机会,期待您的回复。再见!

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-04 08:32:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-04 08:32:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-04 08:32:01       87 阅读
  4. Python语言-面向对象

    2024-04-04 08:32:01       96 阅读

热门阅读

  1. [动态规划]代码随想录总结(自用)

    2024-04-04 08:32:01       33 阅读
  2. 限制promise并行执行个数

    2024-04-04 08:32:01       39 阅读
  3. Making Anti-Palindromes

    2024-04-04 08:32:01       39 阅读
  4. ‘iostream‘ file not foundclang(pp_file_not_found)

    2024-04-04 08:32:01       38 阅读
  5. Datacom HCIP笔记-BGP协议 之二

    2024-04-04 08:32:01       38 阅读
  6. html根据屏幕分辨率大小字体自动变大缩小

    2024-04-04 08:32:01       35 阅读