字节二面:细节拉满!回去等通知?

字节二面:细节拉满!回去等通知?

首页角色扮演少女通告更新时间:2024-05-07

今天分享一位读者春招的字节二面面经,岗位是后端开发。

一个编程语言都没问,都是问网络 项目 MySQL Redis。

问题记录使用消息中间件降低消息持久化的压力是怎么做的,为什么可以降低?

读者答:在突发大量消息的情况下可以做到流量削峰,在消费者消费能力达不到生产者产生消息的速度时也能够正常运行。

怎么解决消息队列上的消息堆压?

读者答:

小编补充:

如果是线上突发问题,要临时扩容,增加消费端的数量,与此同时,降级一些非核心的业务。通过扩容和降级承担流量,这是为了表明你对应急问题的处理能力。其次,才是排查解决异常问题,如通过监控,日志等手段分析是否消费端的业务逻辑代码出现了问题,优化消费端的业务处理逻辑。

在并发编程时,在需要加锁时,不加锁会有什么问题?

读者答:两个线程使用同一个全局变量会有不一致的问题,比如a线程把全局变量加1,b线程读的时候,如果还是从缓存中读的,那么会没有发现这个更新,就会产生不一致的问题。

如何避免出现死锁?

读者答:在使用之前,考虑死锁产生的条件:互斥访问、占有并保持、循环等待。

针对以上几点,可以:资源一次性分配、占有时可被打断、考虑资源分配顺序。

HTTP状态码有哪些?

读者答:

面试官:307其实是临时没错,他是比较新的版本,旧的是 302,现在业界还是旧的用的比较多。

小编补充:

五大类 HTTP 状态码

1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。

2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。

3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向

301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。

4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。

5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。

HTTP1.0和2.0的区别,或者2.0和3.0的区别?

读者答:

小编补充:

HTTP/2 相比 HTTP/1.1 性能上的改进:

1. 头部压缩

HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分

这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

2. 二进制格式

HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)

HTTP/1 与 HTTP/2

这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率

3. 并发传输

我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。

而 HTTP/2 就很牛逼了,引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。

从上图可以看到,1 个 TCP 连接包含多个 Stream,Stream 里可以包含 1 个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成。Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体)。

针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应

比如下图,服务端并行交错地发送了两个响应:Stream 1 和 Stream 3,这两个 Stream 都是跑在一个 TCP 连接上,客户端收到后,会根据相同的 Stream ID 有序组装成 HTTP 消息。

4、服务器推送

HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。

客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。

比如下图,Stream 1 是客户端向服务端请求的资源,属于客户端建立的 Stream,所以该 Stream 的 ID 是奇数(数字 1);Stream 2 和 4 都是服务端主动向客户端推送的资源,属于服务端建立的 Stream,所以这两个 Stream 的 ID 是偶数(数字 2 和 4)。

再比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,如下图左边部分:

img

如上图右边部分,在 HTTP/2 中,客户端在访问 HTML 时,服务器可以直接主动推送 CSS 文件,减少了消息传递的次数。

HTTP/2 有什么缺陷?

HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似很完美了,但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。

HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。

img

举个例子,如下图:

img

图中发送方发送了很多个 packet,每个 packet 都有自己的序号,你可以认为是 TCP 的序列号,其中 packet 3 在网络中丢失了,即使 packet 4-6 被接收方收到后,由于内核中的 TCP 数据不是连续的,于是接收方的应用层就无法从内核中读取到,只有等到 packet 3 重传后,接收方的应用层才可以从内核中读取到数据,这就是 HTTP/2 的队头阻塞问题,是在 TCP 层面发生的。

所以,一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来

前面我们知道了 HTTP/1.1 和 HTTP/2 都有队头阻塞的问题:

HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!

HTTP/1 ~ HTTP/3

UDP 发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。

QUIC 有以下 3 个特点。

1、无队头阻塞

QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。

QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。

所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。

img

2、更快的连接建立

对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。

HTTP/3 在传输数据前虽然需要 QUIC 协议握手,但是这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。

但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,如下图:

TCP HTTPS(TLS/1.3) 和 QUIC HTTPS

甚至,在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 TLS 信息)一起发送,达到 0-RTT 的效果。

如下图右边部分,HTTP/3 当会话恢复时,有效负载数据与第一个数据包一起发送,可以做到 0-RTT(下图的右下角):

img

3、连接迁移

基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。

TCP 四元组

那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。

而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

口述回答题目:MySQL联合索引

一开始给了个写sql语句的题,我说我平时用orm框架访问数据库,很少写sql,于是换了个索引的问题。

题目:

index(abc)

  1. select * from T where a=x and b=y and c=z
  2. select * from T where a=x and b>y and c=z
  3. select * from T where c=z and a=x and b=y
  4. select (a,b) from T where a=x and b>y
  5. select count(*) from T where a=x
  6. select count(*) from T where b=y
  7. select count(*) form T

读者答:

小编补充:

  1. a、b、c三个字段都可以走联合索引
  2. a和b都会走联合索引,但是由于最左匹配原则, 范围查找后面的字段是无法走联合索引的,但是在 mysql 5.6 版本后,c 字段虽然无法走联合索引,但是因为有索引下推的特性,c 字段在 inndob 层过滤完满足查询条件的记录后,才返回给server 层进行回表,相比没有索引下推,减少了回表的次数。
  3. 查询条件的顺序不影响,优化器会优化,所以a、b、c三个字段都可以走联合索引
  4. a和b都会走联合索引,查询是覆盖索引,不需要回表
  5. a 可以走联合索引
  6. 只有b,无法使用联合索引,由于表存在联合索引,所以 count(*) 选择的扫描方式是扫描联合索引来统计个数,扫描的方式是type=index
  7. 由于表存在联合索引,所以 count(*) 选择的扫描方式是扫描联合索引来统计个数,扫描的方式是type=index

关于 count(*) 为什么选择扫描联合索引(二级索引),而不扫描聚簇索引的原因:这是因为相同数量的二级索引记录可以比聚簇索引记录占用更少的存储空间,所以二级索引树比聚簇索引树小,这样遍历二级索引的 I/O 成本比遍历聚簇索引的 I/O 成本小,因此「优化器」优先选择的是二级索引。

Redis怎么实现分布式锁

读者答:应用程序获取redis的连接,然后发送一个命令获取锁。(面试官:什么命令?)SETNX。因为这个指令是Redis里的原子命令,可以把一个key设置其对应的value。然后这个时候这个应用程序就就开始去临界区进行一些操作。如果说如果说他看来运他执行那个命令失败了。就说明这个锁已经被其他的户端持有了,这样的话他就需要等待。成功获取锁并且执行完临界区操作之后,便可以用del命令在redis删除这个key,实现释放锁的目的。

小编补充:

Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入」,所以可以用它来实现分布式锁:

基于 Redis 节点实现分布式锁时,对于加锁操作,我们需要满足三个条件。

满足这三个条件的分布式命令如下:

SET lock_key unique_value NX PX 10000

而解锁的过程就是将 lock_key 键删除(del lock_key),但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。

可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。

// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。

思考题:抛硬币,先抛到正面算赢,否则轮流抛。问先抛的人获胜的概率。

读者答:阿巴阿巴……考虑一轮,(正,-)(反,正)(反,反),其中,(正,-)可以还原成(正,正)(正,)两份,而(反,反)相当于再来一次,这样(正,-)占2份,(反,正)占1份,赢的概率就算2/3

算法题:寻找峰值

给定一个输入数组nums,其中nums[i] != nums[i 1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设nums[-1]= nums[n]= -∞。

读者答:

反问

(1)在面试的过程中,我存在什么问题,需要在哪些方面加强学习?或者说想要从事后端开发,我还欠缺什么知识?

面试官:数据库的部分你可以多研究一下。微服务的部分可能也可以研究一下,因为像我刚刚其实问你能不能讲微服务,我其实是希望你能懂的,你懂的话我就问这方面的问题。

(2)假如我有机会加入进行实习,大概会做些什么样的内容?

面试官:后端这块,我们做的B2B的,我们的产品形态会跟数据库有一点像。如果能够深度的去,了解一下数据库的实现、怎么设计的,那可能会比较好一点。

面试总结感觉

感觉整体沟通比较顺畅,面试官很和蔼,在我回答有问题或者卡壳的时候能很好的引导,给人感觉很舒服。

不足之处
  1. 有些没准备的问题回答的不是很好,比如对比http1.0和2.0,答的很没有条理。
  2. 回答过程中不必要的语气词和废话过多,对于自己不确定的问题给人感觉印象很差。

来源:https://mp.weixin.qq.com/s/ZvRS6bmB64nihfzz72c46g

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved