最后的希望,被字节捞起来了!

本文分享一位同学的字节后端面试经历,面试围绕MySQL、Redis、Java、网络等方向。详细介绍了NOSQL和SQL区别、数据库引擎差异、Java集合类等知识,还阐述了TCP可靠性保证及拥塞控制过程,最后提及面试中的算法题,如合并K个有序数组等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虽然到了 12 月,看似秋招结束了,其实还没有,还有很多公司在进行补录的环节,认识到一些同学在这个月被捞起来重新面试了,而且推进的流程都很快,基本上 2 周左右就速通 offer 了,不少同学在这个时间点“捡漏”到了大厂 offer。

这次就来分享一位同学,这个月被捞起来的字节后端面试,同学的技术栈是 Java 后端,面试点主要围绕了 mysql (存储引擎+索引)、redis (持久化)、Java (集合+并发)、网络 (http+ tcp )这几个方向。

因为突然被捞起来面试,有段时间没有复习八股文了,有些比较细节问题,还是没回答好。好在,算法手感还在,勉强在面试中做出来了算法。最终通过了一二面,等三面。

下面就是这位同学的面经分享,项目和实习的问题也有,但是不太通用,就没贴出来了,就汇总了比较通用的面试问题,也给同学们复习一波。

MySQL

NOSQL和SQL的区别?

SQL数据库,指关系型数据库 - 主要代表:SQL Server,Oracle,MySQL(开源),PostgreSQL(开源)。

关系型数据库存储结构化数据。这些数据逻辑上以行列二维表的形式存在,每一列代表数据的一种属性,每一行代表一个数据实体。

图片

NoSQL指非关系型数据库 ,主要代表:MongoDB,Redis。NoSQL 数据库逻辑上提供了不同于二维表的存储方式,存储方式可以是JSON文档、哈希表或者其他方式。

图片

选择 SQL vs NoSQL,考虑以下因素。

ACID vs BASE

关系型数据库支持 ACID 即原子性,一致性,隔离性和持续性。相对而言,NoSQL 采用更宽松的模型 BASE , 即基本可用,软状态和最终一致性。

从实用的角度出发,我们需要考虑对于面对的应用场景,ACID 是否是必须的。比如银行应用就必须保证 ACID,否则一笔钱可能被使用两次;又比如社交软件不必保证 ACID,因为一条状态的更新对于所有用户读取先后时间有数秒不同并不影响使用。

对于需要保证 ACID 的应用,我们可以优先考虑 SQL。反之则可以优先考虑 NoSQL。

扩展性对比

NoSQL数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力。比如 redis 自带主从复制模式、哨兵模式、切片集群模式。

相反关系型数据库的数据之间存在关联性,水平扩展较难 ,需要解决跨服务器 JOIN,分布式事务等问题。

数据库的引擎有哪些?有什么区别?

图片

mysql 的存储引擎主要有myisam,innodb和memory三个存储引擎。

  • 事务支持:InnoDB支持事务,而MyISAM不支持。InnoDB中每一条SQL语言都默认封装成事务,自动提交,这可能会影响执行速度。因此,最好将多条SQL语言放在BEGIN和COMMIT之间,组成一个事务。

  • 外键支持:InnoDB支持外键,而MyISAM不支持。对于一个包含外键的InnoDB表,如果尝试将其转为MyISAM,将会失败。

  • 索引和数据存储:InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件和(主键)索引绑定在一起,必须要有主键,通过主键索引效率很高。MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。

  • 数据文件格式:MyISAM在磁盘存储上有三个文件,每个文件名以表名开头,扩展名指出文件类型,分别是.frm(用于存储表的定义),.MYD(用于存放数据)和.MYI(用于存放表索引)。InnoDB有两种存储方式:共享表空间存储和多表空间存储。两种存储方式的表结构和MyISAM一样,以表名开头,扩展名为.frm。如果使用共享表空间,那么所有表的数据文件和索引文件都保存在一个表空间里。如果使用多表空间,那么每个表都有一个表空间文件用于存储每个表的数据和索引。

  • 数据安全不同:InnoDB 支持崩溃恢复和数据恢复,而 MyISAM 不支持。如果 MySQL 崩溃了或者发生意外故障,InnoDB 可以通过恢复日志来恢复数据。

聚簇索引和非聚簇索引的区别?

图片

回表

在 MySQL 的 InnoDB 引擎中,每个索引都会对应一颗 B+ 树,而聚簇索引和非聚簇索引最大的区别在于叶子节点存储的数据不同,聚簇索引叶子节点存储的是行数据,因此通过聚簇索引可以直接找到真正的行数据;而非聚簇索引叶子节点存储的是主键id,所以使用非聚簇索引还需要回表查询,因此聚簇索引和非聚簇索引的区别主要有以下几个:

  • 聚簇索引叶子节点存储的是行数据;而非聚簇索引叶子节点存储的是聚簇索引(通常是主键 ID)。

  • 聚簇索引查询效率更高,而非聚簇索引需要进行回表查询,因此性能不如聚簇索引。

  • 聚簇索引一般为主键索引,而主键一个表中只能有一个,因此聚簇索引一个表中也只能有一个,而非聚簇索引则没有数量上的限制。

为什么要用b+树?

MySQL 中索引的数据结构就是采用了 B+ 树,B+ 树结构如下图:

图片

图片

B+ 树与 B 树差异的点,主要是以下这几点:

  • 叶子节点(最底部的节点)才会存放实际数据(索引+记录),非叶子节点只会存放索引;

  • 所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表;

  • 非叶子节点的索引也会同时存在在子节点中,并且是在子节点中所有索引的最大(或最小)。

  • 非叶子节点中有多少个子节点,就有多少个索引;

MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:

  • B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。

  • B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;

  • B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。

联合索引 (a, b,c),where条件是 a=2 and c = 1,能用到联合索引吗?

会用到联合索引,但是只有 a 才能走索引,c 无法走索引,因为不符合最左匹配原则。虽然 c 无法走索引, 但是 c 字段在 5.6 版本之后,会有索引下推的优化,能减少回表查询的次数。

Redis

Redis的RDB和AOF持久化有什么区别?

Redis提供的数据持久化方式主要有2种:

  • RDB:产生一个数据快照文件。优点是RDB文件数据是被压缩写入的,因此RDB文件的体积要比整个实例内存要小,当实例宕机恢复时,加载RDB文件的速度很快,能够在很短时间内迅速恢复文件中的数据。缺点是由于是某一时刻的数据快照,因此它的数据并不全。

  • AOF:实时追加命令的日志文件。优点是AOF数据文件更新比较及时,比RDB保存更完整的数据,这样在数据恢复时能够恢复尽量完整的数据,降低丢失数据的风险。缺点是随着时间增长,AOF文件会越来越大、AOF文件刷盘会增加磁盘IO的负担,可能影响Redis的性能。

Java

讲讲Java集合类

图片

图片

List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。

  • ArrayList是容量可变的非线程安全列表,其底层使用数组实现。当几何扩容时,会创建更大的数组,并把原数组复制到新数组。ArrayList支持对元素的快速随机访问,但插入与删除速度很慢。

  • LinkedList本质是一个双向链表,与ArrayList相比,,其插入和删除速度更快,但随机访问速度更慢。

Set不允许存在重复的元素,与List不同,set中的元素是无序的。常用的实现有HashSet,LinkedHashSet和TreeSet。

  • HashSet通过HashMap实现,HashMap的Key即HashSet存储的元素,所有Key都是用相同的Value,一个名为PRESENT的Object类型常量。使用Key保证元素唯一性,但不保证有序性。由于HashSet是HashMap实现的,因此线程不安全。

  • LinkedHashSet继承自HashSet,通过LinkedHashMap实现,使用双向链表维护元素插入顺序。

  • TreeSet通过TreeMap实现的,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。

Map 是一个键值对集合,存储键、值和之间的映射。Key 无序,唯一;value 不要求有序,允许重复。Map 没有继承于 Collection 接口,从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。主要实现有TreeMap、HashMap、HashTable、LinkedHashMap、ConcurrentHashMap

  • HashMap:JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突),JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间

  • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

  • HashTable:数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的

  • TreeMap:红黑树(自平衡的排序二叉树)

  • ConcurrentHashMap:Node数组+链表+红黑树实现,线程安全的(jdk1.8以前Segment锁,1.8以后CAS锁)

HashMap put和get过程是怎么样的?

图片

图片

存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。

获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

为什么HashMap要用红黑树,为什么不用二叉平衡树?

红黑树适用于大量插入和删除;因为它是非严格的平衡树;只要从根节点到叶子节点的最长路径不超过最短路径的2倍,就不用进行平衡调节。

AVL 树是严格的平衡树,上述的最短路径与最长路径的差不能超过 1,AVL 允许的差值小;在进行大量插入和删除操作时,会频繁地进行平衡调整,严重降低效率。

红黑树虽然不是严格的平衡树,但是其依旧是平衡树;查找效率是 O(logn);AVL也是 O(logn);

红黑树舍去了严格的平衡,使其插入,删除,查找的效率稳定在 O(logn),反观 AVL 树,查找没问题 O(logn),但是为了保证高度平衡,动态插入和删除的代价也随之增加,综合效率肯定达不到 O(logn),所以在进行大量插入,删除操作时,红黑树更优一些。

HashMap是线程安全的吗 ?

不是线程安全的,其线程不安全主要体现在。

  • JDK 1.7 HashMap 采用数组 + 链表的数据结构,多线程背景下,在数组扩容的时候,存在 Entry 链死循环和数据丢失问题。

  • JDK 1.8 HashMap 采用数组 + 链表 + 红黑二叉树的数据结构,优化了 1.7 中数组扩容的方案,解决了 Entry 链死循环和数据丢失问题。但是多线程背景下,put 方法存在数据覆盖的问题。

ConcurrentHashMap怎么保证线程安全的?

JDK 1.7 ConcurrentHashMap

在 JDK 1.7 中它使用的是数组加链表的形式实现的,而数组又分为:大数组 Segment 和小数组 HashEntry。Segment 是一种可重入锁(ReentrantLock),在 ConcurrentHashMap 里扮演锁的角色;HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组,一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素。

图片

img

分段锁技术将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

JDK 1.8 ConcurrentHashMap

在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现,具体实现结构如下:

图片

在这里插入图片描述

JDK 1.8 ConcurrentHashMap 主要通过 volatile + CAS 或者 synchronized 来实现的线程安全的。

添加元素时首先会判断容器是否为空:

  • 如果为空则使用  volatile  加  CAS  来初始化,如果容器不为空,则根据存储的元素计算该位置是否为空。

    • 如果根据存储的元素计算结果为空,则利用  CAS  设置该节点;

    • 如果根据存储的元素计算结果不为空,则使用 synchronized  ,然后,遍历桶中的数据,并替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。

如果把上面的执行用一句话归纳的话,就相当于是ConcurrentHashMap通过对头结点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度。

已经用了synchronized,为什么还要用CAS呢?

ConcurrentHashMap使用这两种手段来保证线程安全主要是一种权衡的考虑,在某些操作中使用synchronized,还是使用CAS,主要是根据锁竞争程度来判断的。

比如:在putVal中,如果计算出来的hash槽没有存放元素,那么就可以直接使用CAS来进行设置值,这是因为在设置元素的时候,因为hash值经过了各种扰动后,造成hash碰撞的几率较低,那么我们可以预测使用较少的自旋来完成具体的hash落槽操作。

当发生了hash碰撞的时候说明容量不够用了或者已经有大量线程访问了,因此这时候使用synchronized来处理hash碰撞比CAS效率要高,因为发生了hash碰撞大概率来说是线程竞争比较强烈。

synchronized为什么设计为可重入锁?

synchronized 设计为可重入锁是为了避免死锁。当一个线程已经持有某个锁时,如果该线程再次请求该锁,可以再次获得而不会被阻塞。这允许同一个线程在没有释放锁的情况下多次进入被 synchronized 保护的代码块,简化了编程模型。

synchronized底层是利用计算机系统mutex Lock实现的。每一个可重入锁都会关联一个线程ID和一个锁状态status。

当一个线程请求方法时,会去检查锁状态。

  1. 如果锁状态是0,代表该锁没有被占用,使用CAS操作获取锁,将线程ID替换成自己的线程ID。

  2. 如果锁状态不是0,代表有线程在访问该方法。此时,如果线程ID是自己的线程ID,如果是可重入锁,会将status自增1,然后获取到该锁,进而执行相应的方法;如果是非重入锁,就会进入阻塞队列等待。

在释放锁时,

  1. 如果是可重入锁的,每一次退出方法,就会将status减1,直至status的值为0,最后释放该锁。

  2. 如果非可重入锁的,线程退出方法,直接就会释放该锁。

网络

http 报文格式说一下?

分请求报文和响应报文来说明。

请求报文

图片

在这里插入图片描述

  • 请求行:包含请求方法、请求目标(URL或URI)和HTTP协议版本。

  • 请求头部:包含关于请求的附加信息,如Host、User-Agent、Content-Type等。

  • 空行:请求头部和请求体之间用空行分隔。

  • 请求体:可选,包含请求的数据,通常用于POST请求等需要传输数据的情况。

响应报文

图片

  • 状态行:包含HTTP协议版本、状态码和状态信息。

  • 响应头部:包含关于响应的附加信息,如Content-Type、Content-Length等。

  • 空行:响应头部和响应体之间用空行分隔。

  • 响应体:包含响应的数据,通常是服务器返回的HTML、JSON等内容。

http状态码你知道哪些?

HTTP 状态码分为 5 大类:1XX:表示消息状态码;2XX:表示成功状态码;3XX:表示重定向状态码;4XX:表示客户端错误状态码;5XX:表示服务端错误状态码。

图片

五大类 HTTP 状态码

其中常见的具体状态码有:200:请求成功;301:永久重定向;302:临时重定向;404:无法找到此页面;405:请求的方法类型不支持;500:服务器内部出错。

http 502和 504 的区别?

  • 502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。

  • 504 Gateway Time-out:作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器收到响应。

举一个例子,假设 nginx 是代理服务器,收到客户端的请求后,将请求转发到后端服务器(tomcat 等)。

  • 当nginx收到了无效的响应时,就返回502。

  • 当nginx超过自己配置的超时时间,还没有收到请求时,就返回504错误。

除了get和post,你还了解过什么方式?

  • PUT:用于在服务器上更新一个现有资源。

  • DELETE:用于删除服务器上的资源。

  • HEAD:像GET请求一样获取资源,但是不返回实际内容,只返回HTTP头信息。

  • PATCH:用于在服务器上部分更新资源。

  • OPTIONS:用于获取服务器支持的HTTP请求方法的列表。

tcp怎么保证可靠性?

TCP协议主要通过以下几点来保证传输可靠性:连接管理、序列号、确认应答、超时重传、流量控制、拥塞控制。

  • 连接管理:即三次握手和四次挥手。连接管理机制能够建立起可靠的连接,这是保证传输可靠性的前提。

  • 序列号:TCP将每个字节的数据都进行了编号,这就是序列号。序列号的具体作用如下:能够保证可靠性,既能防止数据丢失,又能避免数据重复。能够保证有序性,按照序列号顺序进行数据包还原。能够提高效率,基于序列号可实现多次发送,一次确认。

  • 确认应答:接收方接收数据之后,会回传ACK报文,报文中带有此次确认的序列号,用于告知发送方此次接收数据的情况。在指定时间后,若发送端仍未收到确认应答,就会启动超时重传。

  • 超时重传:超时重传主要有两种场景:数据包丢失:在指定时间后,若发送端仍未收到确认应答,就会启动超时重传,向接收端重新发送数据包。确认包丢失:当接收端收到重复数据(通过序列号进行识别)时将其丢弃,并重新回传ACK报文。

  • 流量控制:接收端处理数据的速度是有限的,如果发送方发送数据的速度过快,就会导致接收端的缓冲区溢出,进而导致丢包。为了避免上述情况的发生,TCP支持根据接收端的处理能力,来决定发送端的发送速度。这就是流量控制。流量控制是通过在TCP报文段首部维护一个滑动窗口来实现的。

  • 拥塞控制:拥塞控制就是当网络拥堵严重时,发送端减少数据发送。拥塞控制是通过发送端维护一个拥塞窗口来实现的。可以得出,发送端的发送速度,受限于滑动窗口和拥塞窗口中的最小值。拥塞控制方法分为:慢开始,拥塞避免、快重传和快恢复。

tcp的拥塞控制的具体过程?

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大....

所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。

于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

拥塞控制主要是四个算法:

  • 慢启动

  • 拥塞避免

  • 拥塞发生

  • 快速恢复

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?

慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。

这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,下面举个栗子:

  • 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。

  • 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个

  • 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个

  • 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。

慢启动算法的变化过程如下图:

图片

慢启动算法

可以看出慢启动算法,发包的个数是指数性的增长

那慢启动涨到什么时候是个头呢?

有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。

  • 当 cwnd < ssthresh 时,使用慢启动算法。

  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

拥塞避免

当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。

一般来说 ssthresh 的大小是 65535 字节。

那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

接上前面的慢启动的栗子,现假定 ssthresh 为 8

  • 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

拥塞避免算法的变化过程如下图:

图片

拥塞避免

所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。

就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。

当触发了重传机制,也就进入了「拥塞发生算法」。

拥塞发生

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

  • 超时重传

  • 快速重传

当发生了「超时重传」,则就会使用拥塞发生算法。

这个时候,ssthresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2

  • cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)

拥塞发生算法的变化如下图:

图片

拥塞发送 —— 超时重传

接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。

还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。

TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;

  • ssthresh = cwnd;

  • 进入快速恢复算法

快速恢复

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。

正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;

  • ssthresh = cwnd;

然后,进入快速恢复算法如下:

  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);

  • 重传丢失的数据包;

  • 如果再收到重复的 ACK,那么 cwnd 增加 1;

  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

快速恢复算法的变化过程如下图:

图片

快速重传和快速恢复

也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。

算法

  • 合并K个有序数组

  • K个一组反转链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值