极客大学架构师训练营 网络通信协议 非阻塞网络 I/O NIO 数据库架构原理 第 16 课 听课总结

用户头像
John(易筋)
关注
发布于: 2020 年 07 月 29 日

说明

讲师:李智慧



网络通信协议



Web 请求的一次网络通信历程

客户端 > 域名访问 > DNS 解析为 IP地址 >

  • 获取静态资源 jpg, css, javascript, 通过CDN

  • 获取动态资源,基于TCP访问到负载均衡服务器 (比如订单、商品详情等)

反向代理服务器 > 负载均衡服务 > 网关服务器 > 微服务服务器 >

  • 数据库服务器 (基于TCP协议访问 )

  • 缓存服务器(基于TCP协议访问 )



OSI 七层模型和TCP/IP四层模型

  1. 链路层(Mac地址),互联网有很多种媒介,比如光纤,网线等,要定义好什么是0,什么是1. 比如大于多少电压是1。

  2. 网络互联层,根据Router路由选择找到目标服务器,根据迪杰斯特拉算法得到最短路径。

  3. 传输层,主要看监听的端口,socket。

  4. 应用层,把二进制返回给应用,应用需要反序列化为程序可识别的数据。HTTP协议。



  • 应用层 HTTP

  • 传承层 TCP/IP 协议

  • 互联网 IP 数据

  • 网络接口 Mac地址



每一层都包了一个头和数据,传到下一层。

解析的话,就是把头都脱掉,剩下的就是下一层的数据。



网络数据包格式

  1. HTTP协议头,方法(POST), path, 编码(utf-8)

  2. TCP 协议头,HTTP进程监听端口

  3. IP协议头,服务器IP

  4. 链路层协议头,服务器Mac地址



物理层

物理层负责数据的物理传输,计算机输入输出的只能是0 1 这样的二进制数据,但是在真正的通信线路里有光纤、电缆、无线各种设备。光信号和电信号,以及无线电磁信号在物理上是完全不同的,如何让这些不同的设备能够理解、处理相同的二进制数据,这就是物理层要解决的问题。

链路层

链路层就是将数据进行封装后交给物理层进行传输,主要是将数据封装成数据帧,以帧为单位通过物理层进行通信,有了帧,就可以在帧上进行数据校验,进行流量控制。



链路层会定义帧的大小,这个大小也被称为最大传输单元。像HTTP要在传输的数据上添加一个HTTP头一样,数据链路层也会将封装好的帧添加一个帧头,帧头里记录的一个重要信息就是发送者和接收者的 MAC地址。MAC地址是网卡的设备标识符,是唯一的,数据帧通过这个信息确保数据送达到正确的目标机器。



数据链路层负载均衡

负载均衡服务器直接改Mac地址,达到负载均衡。



网络层

网络层 IP 协议使得互联网应用根据 IP 地址就能访问到目标服务器,请求离开 App 后,到达运营服务商的交换机,交换机会根据这个 IP 地址进行路由转发,可能中间会经过很多个转发节点,最后数据到达目标服务器。



网络层的数据需要交给链路层进行处理,而链路层帧的大小定义了最大传输单元,网络层的 IP 数据包必须要小于最大传输单元才能进行网络传输,这个数据包也有一个 IP 头,主要包括的就是发送者和接收者的 IP 地址。



IP负载均衡

负载均衡服务器通过修改IP地址,到达内网的应用服务器。



传输层(TCP协议)

IP 协议不是一个可靠的通信协议,不会建立稳定的通信链路,并不会确保数据一定送达。要保证通信的稳定可靠,需要传输层协议 TCP。



TCP协议是一种面向连接的、可靠的、基于字节流的传输层协议。TCP作为一个比较基础的通讯协议,有很多重要的机制保证了 TCP 协议的可靠性和健壮性:

  1. 使用序号,对收到的 TCP 报文段进行排序和检测重复的数据。

  2. 无错传输,使用校验和检测报文段的错误。

  3. 使用确认和计时器来检测和纠正丢包或者延迟。

  4. 流量控制,避免主机分组发送得过快而使接收方来不及完全收下。

  5. 拥塞控制,发送方根据网络承载情况控制分组的发送量,以获得高性能同时避免拥塞崩溃。

  6. 丢失包的重传。



TCP 建立连接的3次握手过程

  1. App 先发送 SYN=1Seq=X 的报文,表示请求建立连接,X是一个随机数;

  2. 服务器收到这个报文后,应答 SYN=1ACK=X+1Seq=Y 的报文,表示同意建立连接;

  3. App 收到这个报文后,检查 ACK 的值为自己发送的 Seq 值 +1,确认建立连接,并发送 ACK = Y + 1 的报文给服务器;服务器收到这个报文后检查 ACK 值为自己发送的 Seq值 + 1,确认建立连接。至此,App 和服务器建立 TCP 连接,就可以进行数据传输。

TCP 关闭连接4次挥手

  1. 客户端向服务器发送一个 FIN,请求关闭数据传输。

  2. 当服务器收到客户端的 FIN 时,向客户端发送一个 ACK,其中 ACK 的值等于 FIN + SEQ

  3. 然后服务器向客户端发送一个 FIN,告诉客户端应用程序关闭。

  4. 当客户端收到服务器的 FIN 时,回复一个 ACK 给服务器。其中 ACK 的值等于 FIN + SEQ



注意:客户端和服务端都可以发起关闭连接。



应用层 HTTP 协议

而互联网应用需要在全球范围为用户提供服务,将全球的应用和全球的用户联系在一起,需要一个统一的应用层协议,这个协议就是HTTP协议。



HTTP请求的7种方法

  1. Get: 只读请求,请求处理过程不应该产生副作用,即web应用不应该因为get请求而发生任何状态改变。

  2. Head:和get方法一样,但是只返回响应头。

  3. Post:提交请求。

  4. Put:上传请求。

  5. Delete:删除URL标识的资源。

  6. Trace:回显服务器收到的请求,用以测试或者诊断。

  7. Options:请求服务器返回支持的所有HTTP请求方法,测试服务器是否正常。

HTTP 响应的5种状态

  • 1XX 消息 -- 请求已被服务器接收,继续处理。

  • 2XX 成功 -- 请求已成功被服务器接收、理解、并接受。

  • 3XX 重定向 -- 需要后续操作才能完成这一请求。

  • 4XX 请求错误 -- 请求含有词法错误或者无法被执行。

  • 5XX 服务器错误 -- 服务器在处理某个正确请求时发生错误。



HTTP 协议版本

HTTP 1.0

1996 年发布了 HTTP/1.0, 在HTTP/1.0中,客户端和服务端之间交换的每个请求 / 响应都会创建一个新的 TCP 连接,这意味着所有请求之前都需要进行 TCP 握手连接,因此所有请求都会产生延迟。



HTTP 1.1

HTTP/1.1 试图引入保持连接的概念来解决这些问题,它允许客户端复用 TCP 连接,从而分摊了建立初始连接和针对多个请求缓慢启动的成本。但任意时点上每个连接只能执行一次请求 / 响应交换。



随着网络的发展,网站所需资源(CSS、JavaScript和图像等)不断增长,浏览器在获取和呈现网页时需要越来越多的并发性。但由于HTTP/1.1 只允许客户端进行一次 HTTP 请求 / 响应交换,因此在网络层上获得并发能力的唯一方法是并行使用多个 TCP 连接。



HTTP/2

HTTP/2 引入了 HTTP ”流“ 的概念,允许将不同的 HTTP 并发地复用到同一个 TCP 连接上,使浏览器更高效地复用 TCP 连接。 HTTP/2解决了 TCP 连接的使用效率低的问题,现在可以通过同一个连接同时传输多个请求 / 响应。



但是,TCP并不理解 HTTP流,当多个HTTP 请求复用一个 TCP 连接,如果前面的请求/ 响应没有处理完,后面的请求/ 响应也无法处理,也就是会出现头堵塞现象。

HTTP/3

HTTP/3 不是使用 TCP 作为会话的传输层,而是使用 QUIC (一种新的互联网传输协议)。该协议在传输层将流作为一等公民引入。多个 QUIC 流共享相同的 QUIC 连接,因此不需要额外的握手和慢启动来创建新的 QUIC 流。但 QUIC 流是独立的,因此在大多数情况下,只影响一个流的丢包不会响应其它流,这是因为 QUIC 数据包封装在 UDP 数据包。



非阻塞网络 I/O

计算机之间如何进行网络请求?

Socket、端口





服务器 -- 客户端

一共有两个Socket

  1. 监听端口的Socket

  2. 通信的时候又创建一个Socket



多个客户端请求,就会排队、阻塞等待。

多线程服务器 -- 客户端

线程创建的开销成本比较高。



线程池服务器

阻塞式的I/O,

  • 读的时候阻塞,

  • 写的时候缓存满的情况下阻塞



BIO Blocking I/O 阻塞 I/O

阻塞 I/O: 进行 I/O 操作时,用户线程会一直阻塞,直到读操作或者写操作完成。





Socket 接收数据,系统内核的处理过程



应用程序主要处理蓝色的部分 TCP 数据部分,其它部分都被操作系统处理掉了。



非阻塞 I/O ( Non-Blocking I/O )

非阻塞 I/O: I/O 操作立即返回,发起线程不会阻塞等待。

非阻塞 read 操作:

  • Socket 接收缓冲区有数据,读 n 个(不保证数据被读完整,因此有可能需要多次读)。

  • Socket 接收缓冲区没有数据,则返回失败(不会等待)。



非阻塞write 操作:

  • Socket 发送缓冲区满,返回失败(不会等待)。

  • Socket 发送缓冲区不满,写 n 个数据(不保证一次性全部写入,因此可能需要多次写)。

Java NIO (New I/O)

核心角色:

  • Selector 选择器

  • Buffer

  • SelectableChannel

  • SelectionKey





一个Channel就是对应一个Socket。



Java IO 与 NIO 比较

Java IO核心代码

ServerSocket serverSocket = new ServerSocket(port);
while(true) {
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] content = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = in.read(content)) != -1) {
sb.append(new String(content, 0, len));
}
out.write("Receive Success!\n".getBytes());
out.flush();
socket.shutdoownOutput();
...
}
}).start();
}

Java IO 会阻塞的地方:

  1. accept()

  2. new Thread(...)

  3. Read

  4. Write



NIO 核心代码

InetSocketAddress address = new InetSocketAddress(8888);
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(address);
server.configureBlocking(false);
Selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
ByteBuffer buffer = ByteBuffer.allocate(1024);
if (key.isAcceptable()) {
serverSocketChannel serverChannel = (ServerSocketChannel)key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel)key.channel();
channel.read(buffer);
...
channel.register(selector.SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
SocketChannel channel = (SocketChannel)key.channel();
channel.write(buffer);
...
channel.register(selector, SelectionKey.OP_READ);
}
keys.remove();
}
}



NIO 会阻塞的地方

  • selector.select() 实现这里很关键 (接下来重点分析)



系统 I/O 复用方式: select, poll, epoll



Select (poll) 管理下的read过程

epoll 管理下的read 过程



无活动连接时,Selector.select 方法被阻塞



数据库架构原理与性能优化

PrepareStatement 预编译

// style 1, not recommend. There is an explain in the below.
statment.executeUpdate("UPDATE Users SET status = 2 where userID = 2333");
// style 2, recommend
PreparedStatement updateUser = con.prepareStatment("UPDATE Users SET status = ? where userID = 2333");
updateUser.setInt(1, 2);
updateUser.executeUpdate();



数据库架构

  1. 连接器,建立TCP/IP连接

  2. 语法分析器分析SQL语法树

  3. 语义分析与优化器,优化语义树

  4. 执行引擎 InnoDB



架构的设计,目标在于组件的复用,方便升级,高内聚低耦合。



MySQL有很多执行引擎,很容易替换,这是MySQL火🔥的原因之一。



连接器

数据库连接器会为每个连接请求分配一块专用的内存空间用于会话上下文管理。建立连接对数据库而言相对比较重,需要花费一定的时间,因此应用程序启动的时候,通常会初始化建立一些数据库连接放在连接池里,这样当处理外部请求执行 SQL 操作的时候,就不需要花费时间建立连接了。



语法分析器

select s_grade from staff where s_city not in (select p_city from proj where s_empname=p_pname)





mysql> explain select * from users whee id=1;
Error 1064 (42000): You have an error in your SQL syntax;check the manual that corresponds to your MySQL server version for the right syntax to use near 'id=1' at line 1



语义分析与优化器

语义分析与优化器就是要将各种复杂嵌套的 SQL 进行语义等价转化,得到的有限几种关系代数计算结构,并利用索引等信息进一步进行优化。

// before optimizing
select f.id from orders f where f.user_id = (select id from users);
// after optimizing
select f.id from orders f join users u on f.user_id = u.id;



执行计划

  • Key : 索引类型,NULL无索引。

  • Rows:需要处理的行数。

  • Possible_keys: 潜在可以利用的索引。



为什么 PrepareStatement更好

PrepareStatement 会预先提交带占位符的 SQL 到数据库进行预处理,提前生成执行计划,当给定占位符参数,真正执行 SQL 的时候,执行引擎可以直接执行,效率更好一点。



PrepareStatement 可以防止 SQL 注入攻击。被攻击例子

select * from users where username = 'Frank';
Frank';drop table users;--
select * from users where username = 'Frank';drop table users;--';
// safe way
select * from users where username = ?;



后果很严重:一条查询语句,被删了用户表!!!

B+树

聚簇索引

聚簇索引: 聚簇索引的数据库记录和索引存储在一起。

MySQL 数据库的主键就是聚簇索引,主键 ID 和所在的记录行存储在一个 B+ 树中。



非聚簇索引

非聚簇索引在叶子节点记录的就不是数据行记录,而是聚簇索引,也就是主键。

通过非聚簇索引找到主键索引,再通过主键索引找到行记录的过程也被称作回表。



添加必要的索引优化 SQL 查询性能

在几百万行的数据库中查找一条记录,如果没有索引,就需要权标扫描,检索所有的行记录,才能找到需要的记录。

合理使用索引

不要盲目添加索引,尤其在生产环境中:

  • 添加索引的 alter 操作会消耗较长的时间(分钟级)。

  • Alter 操作期间,所有数据库的增删改操作全部阻塞,对应用而言,因为连接不能释放,事实上,查询也被阻塞。



删除不用的索引,避免不必要的增删开销。



使用更小的数据类型创建索引:

  • int 4字节, bigint 8 字节, Timestamp 4 字节; Datetime 8 字节。



数据库事务

事务特性 ACID

  • 原子性(Atomicity):事务要么全部完成,要么全部取消。如果事务崩溃,状态回到事务之前(事务回滚)。

  • 隔离性(Isolation): 如果2个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1 和 T2 谁先结束,隔离性主要依靠锁实现。

  • 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。

  • 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。

数据库事务日志

进行事务操作时,事务日志文件会记录更新前的数据记录,然后再更新数据库中的记录,如果全部记录都更新成功,那么事务正常结束。如果过程中某条记录更新失败,那么整个事务全部回滚,已经更新的记录根据事务日志中记录的数据进行恢复,这样全部数据都回复到事务提交前的状态,仍然保持数据一致性。



专业名词解析:

  • LSN : 一个按时间顺序分配的唯一事务记录日志序列号。

  • TransID: 产生操作的事务ID。

  • PageID: 被修改的数据再磁盘上的位置。

  • PrevLSN: 同一个事务产生的上一条日志记录的指针。

  • UNDO:取消本次操作的方法,按照此方法回滚。

  • REDO: 重复本次操作的方法。



总结

所有的软件都是人开发的,Linux、MySQL、Java JVM 开发的主程序员,也是关心晋升、吃喝拉撒睡,跟普通人没啥区别。关注的架构设计都是一样的,高内聚低耦合。



发布于: 2020 年 07 月 29 日 阅读数: 66
用户头像

John(易筋)

关注

问渠那得清如许?为有源头活水来 2018.07.17 加入

工作10+年,架构师,曾经阿里巴巴资深无线开发,汇丰银行架构师/专家。开发过日活过亿的淘宝Taobao App,擅长架构、算法、数据结构、设计模式、iOS、Java Spring Boot。易筋为阿里巴巴花名。

评论

发布
暂无评论
极客大学架构师训练营 网络通信协议 非阻塞网络I/O NIO 数据库架构原理 第16课 听课总结