diff --git a/docs/cs-basics/network/tcp-connection-and-disconnection.md b/docs/cs-basics/network/tcp-connection-and-disconnection.md index 01f34f3e..63bc97f8 100644 --- a/docs/cs-basics/network/tcp-connection-and-disconnection.md +++ b/docs/cs-basics/network/tcp-connection-and-disconnection.md @@ -13,12 +13,21 @@ tag: 建立一个 TCP 连接需要“三次握手”,缺一不可: -- **一次握手**:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 **SYN_SEND** 状态,等待服务器的确认; -- **二次握手**:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 **SYN_RECV** 状态 -- **三次握手**:客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入**ESTABLISHED** 状态,完成 TCP 三次握手。 +- **一次握手**:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 **SYN_SEND** 状态,等待服务端的确认; +- **二次握手**:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 **SYN_RECV** 状态; +- **三次握手**:客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务端都进入**ESTABLISHED** 状态,完成 TCP 三次握手。 当建立了 3 次握手之后,客户端和服务端就可以传输数据啦! +### 什么是半连接队列和全连接队列? + +在 TCP 三次握手过程中,Linux 内核会维护两个队列来管理连接请求: + +1. **半连接队列**(也称 SYN Queue):当服务端收到客户端的 SYN 请求时,此时双方还没有完全建立连接,它会把半连接状态的连接放在半连接队列。 +2. **全连接队列**(也称 Accept Queue):当服务端收到客户端对 ACK 响应时,意味着三次握手成功完成,服务端会将该连接从半连接队列移动到全连接队列。如果未收到客户端的 ACK 响应,会进行重传,重传的等待时间通常是指数增长的。如果重传次数超过系统规定的最大重传次数,系统将从半连接队列中删除该连接信息。 + +这两个队列的存在是为了处理并发连接请求,确保服务端能够有效地管理新的连接请求。另外,新的连接请求被拒绝或忽略除了和每个队列的大小限制有关系之外,还和很多其他因素有关系,这里就不详细介绍了,整体逻辑比较复杂。 + ### 为什么要三次握手? 三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。 @@ -35,7 +44,13 @@ tag: 服务端传回发送端所发送的 ACK 是为了告诉客户端:“我接收到的信息确实就是你所发送的信号了”,这表明从客户端到服务端的通信是正常的。回传 SYN 则是为了建立并确认从服务端到客户端的通信。 -> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。 +> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务端之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务端使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务端之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务端之间传递。 + +### 三次握手过程中可以携带数据吗? + +在 TCP 三次握手过程中,第三次握手是可以携带数据的(客户端发送完 ACK 确认包之后就进入 ESTABLISHED 状态了),这一点在 RFC 793 文档中有提到。也就是说,一旦完成了前两次握手,TCP 协议允许数据在第三次握手时开始传输。 + +如果第三次握手的 ACK 确认包丢失,但是客户端已经开始发送携带数据的包,那么服务端在收到这个携带数据的包时,如果该包中包含了 ACK 标记,服务端会将其视为有效的第三次握手确认。这样,连接就被认为是建立的,服务端会处理该数据包,并继续正常的数据传输流程。 ## 断开连接-TCP 四次挥手 @@ -43,8 +58,8 @@ tag: 断开一个 TCP 连接则需要“四次挥手”,缺一不可: -1. **第一次挥手**:客户端发送一个 FIN(SEQ=x) 标志的数据包->服务端,用来关闭客户端到服务器的数据传送。然后客户端进入 **FIN-WAIT-1** 状态。 -2. **第二次挥手**:服务器收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (ACK=x+1)标志的数据包->客户端 。然后服务端进入 **CLOSE-WAIT** 状态,客户端进入 **FIN-WAIT-2** 状态。 +1. **第一次挥手**:客户端发送一个 FIN(SEQ=x) 标志的数据包->服务端,用来关闭客户端到服务端的数据传送。然后客户端进入 **FIN-WAIT-1** 状态。 +2. **第二次挥手**:服务端收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (ACK=x+1)标志的数据包->客户端 。然后服务端进入 **CLOSE-WAIT** 状态,客户端进入 **FIN-WAIT-2** 状态。 3. **第三次挥手**:服务端发送一个 FIN (SEQ=y)标志的数据包->客户端,请求关闭连接,然后服务端进入 **LAST-ACK** 状态。 4. **第四次挥手**:客户端发送 ACK (ACK=y+1)标志的数据包->服务端,然后客户端进入**TIME-WAIT**状态,服务端在收到 ACK (ACK=y+1)标志的数据包后进入 CLOSE 状态。此时如果客户端等待 **2MSL** 后依然没有收到回复,就证明服务端已正常关闭,随后客户端也可以关闭连接了。 @@ -61,17 +76,17 @@ TCP 是全双工通信,可以双向传输数据。任何一方都可以在数 3. **第三次挥手**:于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了” 4. **第四次挥手**:A 回答“知道了”,这样通话才算结束。 -### 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手? +### 为什么不能把服务端发送的 ACK 和 FIN 合并起来,变成三次挥手? -因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务器到客户端的数据传送。 +因为服务端收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务端到客户端的数据传送。 -### 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样? +### 如果第二次挥手时服务端的 ACK 没有送达客户端,会怎样? 客户端没有收到 ACK 确认,会重新发送 FIN 请求。 ### 为什么第四次挥手客户端需要等待 2\*MSL(报文段最长寿命)时间后才进入 CLOSED 状态? -第四次挥手时,客户端发送给服务器的 ACK 有可能丢失,如果服务端因为某些原因而没有收到 ACK 的话,服务端就会重发 FIN,如果客户端在 2\*MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。 +第四次挥手时,客户端发送给服务端的 ACK 有可能丢失,如果服务端因为某些原因而没有收到 ACK 的话,服务端就会重发 FIN,如果客户端在 2\*MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。 > **MSL(Maximum Segment Lifetime)** : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。 @@ -83,4 +98,6 @@ TCP 是全双工通信,可以双向传输数据。任何一方都可以在数 - TCP and UDP Tutorial: +- 从一次线上问题说起,详解 TCP 半连接队列、全连接队列: + diff --git a/docs/database/mysql/mysql-questions-01.md b/docs/database/mysql/mysql-questions-01.md index d0f0568e..a4a7dad0 100644 --- a/docs/database/mysql/mysql-questions-01.md +++ b/docs/database/mysql/mysql-questions-01.md @@ -263,13 +263,13 @@ MySQL 5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。 言归正传!咱们下面还是来简单对比一下两者: -**1.是否支持行级锁** +**1、是否支持行级锁** MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。 也就说,MyISAM 一锁就是锁住了整张表,这在并发写的情况下是多么滴憨憨啊!这也是为什么 InnoDB 在并发写的时候,性能更牛皮了! -**2.是否支持事务** +**2、是否支持事务** MyISAM 不提供事务支持。 @@ -277,7 +277,7 @@ InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别, 关于 MySQL 事务的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。 -**3.是否支持外键** +**3、是否支持外键** MyISAM 不支持,而 InnoDB 支持。 @@ -291,19 +291,19 @@ MyISAM 不支持,而 InnoDB 支持。 总结:一般我们也是不建议在数据库层面使用外键的,应用层面可以解决。不过,这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。 -**4.是否支持数据库异常崩溃后的安全恢复** +**4、是否支持数据库异常崩溃后的安全恢复** MyISAM 不支持,而 InnoDB 支持。 使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 `redo log` 。 -**5.是否支持 MVCC** +**5、是否支持 MVCC** MyISAM 不支持,而 InnoDB 支持。 讲真,这个对比有点废话,毕竟 MyISAM 连行级锁都不支持。MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提高性能。 -**6.索引实现不一样。** +**6、索引实现不一样。** 虽然 MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是两者的实现方式不太一样。 @@ -311,12 +311,16 @@ InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索 详细区别,推荐你看看我写的这篇文章:[MySQL 索引详解](./mysql-index.md)。 -**7.性能有差别。** +**7、性能有差别。** InnoDB 的性能比 MyISAM 更强大,不管是在读写混合模式下还是只读模式下,随着 CPU 核数的增加,InnoDB 的读写能力呈线性增长。MyISAM 因为读写不能并发,它的处理能力跟核数没关系。 ![InnoDB 和 MyISAM 性能对比](https://oss.javaguide.cn/github/javaguide/mysql/innodb-myisam-performance-comparison.png) +**8、数据缓存策略和机制实现不同。** + +InnoDB 使用缓冲池(Buffer Pool)缓存数据页和索引页,MyISAM 使用键缓存(Key Cache)仅缓存索引页而不缓存数据页。 + **总结**: - InnoDB 支持行级别的锁粒度,MyISAM 不支持,只支持表级别的锁粒度。 diff --git a/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md b/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md index 2ec754ac..ed4abf66 100644 --- a/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md +++ b/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md @@ -763,11 +763,11 @@ springApplication.setLazyInitialization(false); springApplication.run(args); ``` -如非必要,尽量不要用全局懒加载。全局懒加载会让Bean第一次使用的时候加载会变慢,并且它会延迟应用程序问题的发现(当Bean被初始化时,问题才会出现)。 +如非必要,尽量不要用全局懒加载。全局懒加载会让 Bean 第一次使用的时候加载会变慢,并且它会延迟应用程序问题的发现(当 Bean 被初始化时,问题才会出现)。 -如果一个Bean没有被标记为懒加载,那么它会在Spring IoC容器启动的过程中被创建和初始化。如果一个 Bean 被标记为懒加载,那么它不会在Spring IoC容器启动时立即实例化,而是在第一次被请求时才创建。这可以帮助减少应用启动时的初始化时间,也可以用来解决循环依赖问题。 +如果一个 Bean 没有被标记为懒加载,那么它会在 Spring IoC 容器启动的过程中被创建和初始化。如果一个 Bean 被标记为懒加载,那么它不会在 Spring IoC 容器启动时立即实例化,而是在第一次被请求时才创建。这可以帮助减少应用启动时的初始化时间,也可以用来解决循环依赖问题。 -循环依赖问题是如何通过`@Lazy` 解决的呢?这里举一个例子,比如说有两个 Bean,A 和 B,他们之间发生了循环依赖,那么 A 的构造器上添加 `@Lazy` 注解之后(延迟 Bean B的实例化),加载的流程如下: +循环依赖问题是如何通过`@Lazy` 解决的呢?这里举一个例子,比如说有两个 Bean,A 和 B,他们之间发生了循环依赖,那么 A 的构造器上添加 `@Lazy` 注解之后(延迟 Bean B 的实例化),加载的流程如下: - 首先 Spring 会去创建 A 的 Bean,创建时需要注入 B 的属性; - 由于在 A 上标注了 `@Lazy` 注解,因此 Spring 会去创建一个 B 的代理对象,将这个代理对象注入到 A 中的 B 属性;