极客时间-罗剑锋-《透视HTTP协议》总结

协议分层

TCP/IP的四层模型和OSI的七层模型之间的对应关系:

20200526四层协议和七层协议的对应关系.png

详细说一下TCP/IP的四层模型:

  • 第一层叫“链接层”(link layer),负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标记网络上的设备,所以有时候也叫 MAC 层。
  • 第二层叫“网际层”或者“网络互连层”(internet layer),IP 协议就处在这一层。因为 IP 协议定义了“IP 地址”的概念,所以就可以在“链接层”的基础上,用 IP 地址取代 MAC 地址,把许许多多的局域网、广域网连接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再“翻译”成 MAC 地址就可以了。
  • 第三层叫“传输层”(transport layer),这个层次协议的职责是保证数据在 IP 地址标记的两点之间“可靠”地传输,是 TCP 协议工作的层次,另外还有它的一个“小伙伴”UDP。
  • 协议栈的第四层叫“应用层”(application layer),由于下面的三层把基础打得非常好,所以在这一层就“百花齐放”了,有各种面向具体应用的协议。例如 Telnet、SSH、FTP、SMTP 等等,当然还有我们的 HTTP。

MAC 层的传输单位是帧(frame),IP 层的传输单位是包(packet),TCP 层的传输单位是段(segment),HTTP 的传输单位则是消息或报文(message)。但这些名词并没有什么本质的区分,可以统称为数据包。

有了上面的概念,才可能理解下面的术语:

  • 四层负载均衡:指工作在传输层上,基于 TCP/IP 协议的特性,例如 IP 地址、端口号等实现对后端服务器的负载均衡。
  • 七层负载均衡:指工作在应用层上,看到的是 HTTP 协议,解析 HTTP 报文里的 URI、主机名、资源类型等数据,再用适当的策略转发给后端服务器。

下面以TCP/IP的四层模型来说明请求数据的发送和接收过程:

20200526四层模型工作过程模型.png

发送过程就是一层一层包裹的过程,接收的过程就是一层一层拨开的过程。

DNS

DNS访问过程

域名必须转换成IP才能进行网络访问。这个过程需要DNS(Domain Name System)。

DNS 的核心系统是一个三层的树状、分布式服务,基本对应域名的结构:

  1. 根域名服务器(Root DNS Server):管理顶级域名服务器,返回“com”“net”“cn”等顶级域名服务器的 IP 地址;
  2. 顶级域名服务器(Top-level DNS Server):管理各自域名下的权威域名服务器,比如 com 顶级域名服务器可以返回 apple.com 域名服务器的 IP 地址;
  3. 权威域名服务器(Authoritative DNS Server):管理自己域名下主机的 IP 地址,比如 apple.com 权威域名服务器可以返回 www.apple.com 的 IP 地址。

DNS三层结构示意图如下:

20200526域名服务器层级结构.png

有了这个系统以后,任何一个域名都可以在这个树形结构里从顶至下进行查询,就好像是把域名从右到左顺序走了一遍,最终就获得了域名对应的 IP 地址。

例如,你要访问“www.apple.com”,就要进行下面的三次查询:

  1. 访问根域名服务器,它会告诉你“com”顶级域名服务器的地址;
  2. 访问“com”顶级域名服务器,它再告诉你“apple.com”域名服务器的地址;
  3. 最后访问“apple.com”域名服务器,就得到了“www.apple.com”的地址。

DNS的缓存

虽然核心的 DNS 系统遍布全球,服务能力很强也很稳定,但如果全世界的网民都往这个系统里挤,即使不挤瘫痪了,访问速度也会很慢。

所以在核心 DNS 系统之外,还有两种手段用来减轻域名解析的压力,并且能够更快地获取结果,基本思路就是“缓存”。

首先,许多大公司、网络运行商都会建立自己的 DNS 服务器,作为用户 DNS 查询的代理,代替用户访问核心 DNS 系统。这些“野生”服务器被称为“非权威域名服务器”,可以缓存之前的查询结果,如果已经有了记录,就无需再向根服务器发起查询,直接返回对应的 IP 地址。

这些 DNS 服务器的数量要比核心系统的服务器多很多,而且大多部署在离用户很近的地方。比较知名的 DNS 有 Google 的“8.8.8.8”,Microsoft 的“4.2.2.1”,还有 CloudFlare 的“1.1.1.1”等等。

其次,操作系统里也会对 DNS 解析结果做缓存,如果你之前访问过“www.apple.com”,那么下一次在浏览器里再输入这个网址的时候就不会再跑到 DNS 那里去问了,直接在操作系统里就可以拿到 IP 地址。

另外,操作系统里还有一个特殊的“主机映射”文件,通常是一个可编辑的文本,在 Linux 里是“/etc/hosts”,在 Windows 里是“C:\WINDOWS\system32\drivers\etc\hosts”,如果操作系统在缓存里找不到 DNS 记录,就会找这个文件。

DNS新玩法

除了解析得到IP地址,DNS还可以用来做一些其他事情。

  1. 第一种,也是最简单的,“重定向”。因为域名代替了 IP 地址,所以可以让对外服务的域名不变,而主机的 IP 地址任意变动。当主机有情况需要下线、迁移时,可以更改 DNS 记录,让域名指向其他的机器。实现停机维护期间服务不中断。
  2. 第二种,因为域名是一个名字空间,所以可以使用 bind9 等开源软件搭建一个在内部使用的 DNS,作为名字服务器。这样我们开发的各种内部服务就都用域名来标记,比如数据库服务都用域名“mysql.inner.app”,商品服务都用“goods.inner.app”,发起网络通信时也就不必再使用写死的 IP 地址了,可以直接用域名,而且这种方式也兼具了第一种“玩法”的优势。
  3. 第三种“玩法”包含了前两种,也就是基于域名实现的负载均衡。在计算IP的时候考虑哪台主机资源充足,更适合提供服务,就返回哪台服务器的IP地址。

键入网址再按下回车,后面究竟发生了什么?

当用户在浏览器地址栏输入http://127.0.0.1/,并按下回车之后,浏览器和服务器之间的行为如下:

20200526http请求全过程.png

  1. 首先是TCP连接的建立:经过 SYN、SYN/ACK、ACK 的三个包之后,浏览器与服务器的 TCP 连接就建立起来了。
  2. 有了可靠的 TCP 连接通道后,HTTP 协议就可以开始工作了。于是,浏览器按照 HTTP 协议规定的格式,通过 TCP 发送了一个“GET / HTTP/1.1”请求报文,也就是 Wireshark 里的第四个包。
  3. 随后,Web 服务器回复了第五个包,在 TCP 协议层面确认:“刚才的报文我已经收到了”,不过这个 TCP 包 HTTP 协议是看不见的。
  4. Web 服务器收到报文后在内部就要处理这个请求。同样也是依据 HTTP 协议的规定,解析报文,看看浏览器发送这个请求想要干什么。它一看,原来是要求获取根目录下的默认文件,好吧,那我就从磁盘上把那个文件全读出来,再拼成符合 HTTP 格式的报文,发回去吧。这就是第六个包“HTTP/1.1 200 OK”,底层走的还是 TCP 协议。
  5. 同样的,浏览器也要给服务器回复一个 TCP 的 ACK 确认,“你的响应报文收到了,多谢。”,即第七个包。
  6. 这时浏览器就收到了响应数据,但里面是什么呢?所以也要解析报文。一看,服务器给我的是个 HTML 文件,好,那我就调用排版引擎、JavaScript 引擎等等处理一下,然后在浏览器窗口里展现出了欢迎页面。
  7. 这之后还有两个来回,共四个包,重复了相同的步骤。这是浏览器自动请求了作为网站图标的“favicon.ico”文件,与我们输入的网址无关。但因为我们的实验环境没有这个文件,所以服务器在硬盘上找不到,返回了一个“404 Not Found”。
  8. 最后是TCP关闭连接的“四次挥手”。但是当你通过抓包软件抓包的时候可能看不到这几个包,因为HTTP/1.1 长连接特性,默认不会立即关闭连接。

至此,“键入网址再按下回车”的全过程就结束了。

当然,上面只是一个极简网络模型。真实世界中的应用复杂很多。

  1. 上面是直接输入了IP地址,如果输入的是域名,那么在上面所有步骤之前还有DNS解析过程。
  2. 如果用的是https通信协议,在TCP连接建立之后还要有TLS连接的建立。
  3. 别忘了互联网上还有另外一个重要的角色 CDN,它也会在 DNS 的解析过程中“插上一脚”。DNS 解析可能会给出 CDN 服务器的 IP 地址,这样你拿到的就会是 CDN 服务器而不是目标网站的实际地址。
  4. 如果你的请求跳过CDN,到达目标服务器,这时发生的事情如下:目标网站的服务器对外表现的是一个 IP 地址,但为了能够扛住高并发,在内部也是一套复杂的架构。通常在入口是负载均衡设备,例如四层的 LVS 或者七层的 Nginx,在后面是许多的服务器,构成一个更强更稳定的集群。
  5. 负载均衡设备会先访问系统里的缓存服务器,通常有 memory 级缓存 Redis 和 disk 级缓存 Varnish,它们的作用与 CDN 类似,不过是工作在内部网络里,把最频繁访问的数据缓存几秒钟或几分钟,减轻后端应用服务器的压力。
  6. 如果缓存服务器里也没有,那么负载均衡设备就要把请求转发给应用服务器了。这里就是各种开发框架大显神通的地方了,例如 Java 的 Tomcat/Netty/Jetty,Python 的 Django,还有 PHP、Node.js、Golang 等等。它们又会再访问后面的 MySQL、PostgreSQL、MongoDB 等数据库服务,实现用户登录、商品查询、购物下单、扣款支付等业务操作,然后把执行的结果返回给负载均衡设备,同时也可能给缓存服务器里也放一份。
  7. 应用服务器的输出到了负载均衡设备这里,请求的处理就算是完成了,就要按照原路再走回去,还是要经过许多的路由器、网关、代理。如果这个资源允许缓存,那么经过 CDN 的时候它也会做缓存,这样下次同样的请求就不会到达源站了。
  8. 最后网站的响应数据回到了你的设备,它可能是 HTML、JSON、图片或者其他格式的数据,需要由浏览器解析处理才能显示出来,如果数据里面还有超链接,指向别的资源,那么就又要重走一遍整个流程,直到所有的资源都下载完。

综合考虑上面的因素,整体的workflow可以表示如下:

NetworkRequestWorkflow.svg

TLS握手过程

关于“TLS如何保证安全”这个话题,参考知乎回答。上面这篇文中的“编程指北”的回答最清晰。身份认证可以直接看3.5和3.6小节。真正申请一个SSL证书需要知道的常识:一篇文章让你搞懂 SSL 证书

简要过程图:

20200526TLS链接简要过程.png

详细过程图:

20200526TLS链接详细过程.png

详细的分析解说参考原文:26丨信任始于握手:TLS1.2连接过程解析.html

http性能优化

关于http性能优化更详细的介绍,参见Web性能优化综述的章节https请求性能优化

HTTP性能优化面面观(上)

  1. 性能优化是一个复杂的概念,在 HTTP 里可以分解为服务器性能优化、客户端性能优化和传输链路优化;
  2. 服务器有三个主要的性能指标:吞吐量、并发数和响应时间,此外还需要考虑资源利用率;
  3. 客户端的基本性能指标是延迟,影响因素有地理距离、带宽、DNS 查询、TCP 握手等;
  4. 从服务器到客户端的传输链路可以分为三个部分,我们能够优化的是前两个部分,也就是“第一公里”和“中间一公里”;
  5. 有很多工具可以测量这些指标,服务器端有 ab、top、sar 等,客户端可以使用测试网站,浏览器的开发者工具。

HTTP性能优化面面观(下)

  1. 花钱购买硬件、软件或者服务可以直接提升网站的服务能力,其中最有价值的是 CDN;
  2. 不花钱也可以优化 HTTP,三个关键词是“开源”“节流”和“缓存”;
  3. 后端应该选用高性能的 Web 服务器,开启长连接,提升 TCP 的传输效率;
  4. 前端应该启用 gzip、br 压缩,减小文本、图片的体积,尽量少传不必要的头字段;
  5. 缓存是无论何时都不能忘记的性能优化利器,应该总使用 Etag 或 Last-modified 字段标记资源;
  6. 升级到 HTTP/2 能够直接获得许多方面的性能提升,但要留意一些 HTTP/1 的“反模式”。

试验环境搭建(Mac)

  1. 安装homebrew
  2. 使用homebrew安装OpenResty brew install openresty/brew/openresty
  3. clone项目源码 git clone https://github.com/chronolaw/http_study
  4. 启动项目
    • cd http_study/www/ 脚本必须在www目录下运行,才能找到nginx.conf
    • ./run.sh start 启动实验环境
    • ./run.sh list 列出实验环境的Nginx进程
    • ./run.sh reload 重启实验环境
    • ./run.sh stop 停止实验环境
  5. 启动 OpenResty 之后,就可以用浏览器或者 curl 来验证课程里的各个测试 URI,但之前不要忘记修改“/etc/hosts”添加域名解析。