很多时候,初级的前端开发工程师能够理解的web访问过程仅仅是浏览器和服务器之间的数据交互,通过浏览器发送HTTP请求到服务器,等待服务器返回数据,浏览器解析数据渲染页面。
本文我们详细的聊聊用户访问网站应用后的各种web资源是如何返回的,只有了解整个链路中各个过程都在做什么才能够掌握web整体轮廓,在与各个角色的联合开发调试中心中有数,不在惧怕才能快速合作定位出问题。
其实,在用户通过浏览器触发应用访问到看到漂亮的页面展现的过程中发生了很多事情,比如DNS查找过程、 缓存利用过程、通过网络访问目标服务器过程、网关触达以及负载均衡过程、服务端集群应用服务过程等,接下来的章节中我们将详细介绍整个过程各个环节中发生的事情。
DNS查找过程
使用目标IP地址访问,比如你可以直接在浏览器中输入某网站的IP地址直接访问,由于IP地址都是一堆数字不方便人记忆,于是有了域名这种字符型标识。域名解析可以将你访问的目标域名转换成相应的IP地址。当用户打开浏览器输入网址URL发起请求时,浏览器访问DNS服务器,获取域名对应目标主机的IP地址,这个域名解析会涉及到一个递归和迭代。然后使用该IP地址访问服务器。每次都请求网络中的DNS服务器,时间成本较高。操作系统自带的DNS文件,可以用来缓存域名与IP地址的对应关系。此外,浏览器也会缓存部分域名与IP地址的映射。
hosts文件往往用来存储映射关系,windows存储路径:C:\Windows\System32\drivers\etc\hosts,Linux下打开文件:/etc/hosts。
#可以修改域名映射到本机IP
108.177.97.84 accounts.google.com
所以,可以得出域名查找的过程顺序为:浏览器域名缓存->系统域名缓存->域名服务器域名解析。
缓存
在用户访问网站是,浏览器也会根据资源是否被访问过优先利用缓存,缓存包括浏览器缓存、网络代理缓存、数据缓存等。
浏览器缓存
浏览器的存储很多,比如cookie、Local storage、Session Storage 以及Service Worker使用的存储系统,协商缓存和强缓存也是前端经常使用的缓存方案。浏览器通畅判断是否过期的方法有两种:强缓存和协商缓存。
强缓存(Expires、Cache-Control),如果客户端此次发送请求的时间在Expires之前,则会直接触发缓存,不再去发起请求。Cache-Control一般是public、private和max-age,且Cache-Control的优先级要优于Expires。
协商缓存(Etag、Last Modified)就是客户端在没有匹配到强缓存的前提下,向服务端发起了请求,而服务端则会使用两种方式来判断,请求的资源是否在上一次请求和这一次请求之间发生过变化。如果发生了变化则正常发起200响应,反之则发起304响应,直接触发协商缓存。
网络代理缓存
网络代理缓存的原理就是在用户和服务器之间增加 Cache 层,达到资源尽快返回的目的。CDN(内容分发网络)就是比较常见的网络缓存,还有nginx也算是网络代理缓存,通过在现有的Internet中增加一层新的CACHE(缓存)层,将网站的内容发布到最接近用户的网络”边缘“的节点,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等原因,提高用户访问网站的响应速度,要实现这一目的,主要是通过接管DNS 实现。
网络访问目标服务器
访问目标主机可以使用HTTP和HTTPS协议,是在HTTP基础上的基础下加入SSL,通过传输加密和身份认证保证了传输过程的安全性,HTTP又可以使用HTTP/1、HTTP/2和HTTP/3。浏览器通过发起HTTP请求要求服务端提供内容,服务端发送HTTP响应交付索要的内容。
HTTP过程
触发请求进行DNS解析后,客户端就会发起一个http request请求。这个request请求通过路由选择会经过互联网然后到达相应的公网IP(域名)网关上,这个请求往往是GET请求。
在请求传递的过程中HTTP请求由三部分组成,分别是:请求行、消息报头、请求正文。通过解析请求头从中获得客户机想访问的主机名,从请求信息中获取客户机想要访问的web应用(web应用程序指提供浏览器访问的程序),从请求信息中获取客户机要访问的web资源(web资源,即各种文件,图片,视频,文本等)。
# http 请求
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: m.baidu.com
Accept-Language: en, mi
读取相应的主机下的web应用 / web资源,用读取到的web资源数据,创建一个HTTP响应,服务器回送HTTP响应。HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。客户浏览器解析回送的资源,并显示结果。
# http 响应
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
HTTP是无状态的、无连接的,基于TCP / IP通信协议族来传送数据的,每次连接只处理一个请求,对于事务处理没有记忆能力,发现有时候每次访问都需要建立一次 TCP 连接就显得很低效。TCP协议把请求报文分割成报文段,IP协议通过请求中的IP找到对方的地址,并把一个个报文传递过去(包括到达确认和超时重发等安全措施),TCP把报文段按序号重组给服务器,请求结果返回处理也类似。
一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,但是浏览器一般其头信息加入了这行代码 Connection:keep-alive,TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接目的,节省了为每 个请求建立新连接所需的时间,还节约了网络带宽。
根据 HTTP 标准,HTTP 请求可以使用多种请求方法。比如文档文件一般使用GET,异步请求一般使用POST,跨域时一般会发起OPTION请求。
- GET:请求指定的页面信息,并返回实体主体。
- HEAD:类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
- PUT:从客户端向服务器传送的数据取代指定的文档的内容。
- DELETE:请求服务器删除指定的页面。
- CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
- OPTIONS:允许客户端查看服务器的性能。
- TRACE:回显服务器收到的请求,主要用于测试或诊断。
- PATCH:是对 PUT 方法的补充,用来对已知资源进行局部更新 。
TCP过程
TCP是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。TCP建立一个连接需要三次握手,而终止一个连接要经过四次挥手。
第一次握手:客户端向服务端发送 SYN (同步)包,其中 SYN=1,seq=x ,表示客户端希望与服务端建立连接,同时指定自己的初始序号为 x 。此时客户端处于 SYN_SENT 状态。
第二次握手:服务端接收到 SYN 包后,向客户端发送 SYN-ACK 包,其中 SYN=1, ACK=1,ack=x+1,seq=y ,表示服务端已经收到客户端的请求,同意建立连接,同时指定自己的初始序号为 y,确认号为 x+1 。此时服务端处于 SYN_RCVD 状态。
第三次握手:客户端收到 SYN-ACK 包后,向服务端发送 ACK 包,其中 SYN=0,ACK=1,ack=y+1,seq=x+1 ,表示客户端已经收到服务端的确认,连接建立成功。此时客户端处于 ESTABLISHED 状态,服务端也处于 ESTABLISHED 状态。
TCP 通过四次挥手来关闭连接,具体过程如下:
- 客户端向服务端发送 FIN 报文,表示客户端不再发送数据。
- 服务端收到 FIN 报文后,向客户端发送 ACK 报文,表示收到了客户端的 FIN 报文。
- 服务端向客户端发送 FIN 报文,表示服务端不再发送数据。
- 客户端收到 FIN 报文后,向服务端发送 ACK 报文,表示收到了服务端的 FIN 报文。
TCP支持的应用协议主要有:Telnet(远程终端协议)、FTP(File Transfer Protocol文件传输协议)、SMTP(Simple Mail Transfer Protocol简单邮件传输协议)等;
UDP过程
UDP则不为IP提供可靠性、流控或差错恢复功能。TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。
UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。
HTTPS过程
使用HTTPS需要首先拥有CA证书,这个是权威机构放发的认证证书,包括颁发机构、过期时间、服务端的公钥、第三方证书认证机构(CA)的签名、服务端的域名信息等内容,客户端和服务端主机之间通过证书公钥和私钥进行加密解密等一系类动作保证信息安全传输。
具体过程是,客户端向一个需要https访问的网站发起请求;服务器将证书发送给客户端进行校验。证书里面包含了其公钥,校验对方发过来的数字证书是否有效;校验成功之后,客户端会生成一个随机串然后使用服务器证书的公钥进行加密之后发送给服务器;服务器通过使用自己的私钥解密得到这个随机值;服务器从此开始使用这个随机值进行对称加密开始和客户端进行通信;客户端拿到值用对称加密方式使用随机值进行解密。
/**
* 在node.js server 配置私钥和公钥
**/
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
网关过程
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。请求到了web服务器之后就到了里面的web server,这个web server有可能是Nginx有可能是Apache,这个web server本身能够处理一些静态请求。域名可以去购买云服务在申请(得备案),nginx在根据域名进行匹配转给网关,这边主要修改nignx.conf和conf.d里面的文件。https的端口是443,http的端口是80。
# nginx在根据域名进行网关配置
server {
listen 外网IP:443 default_server;
server_name *.xxx.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass https://abcdef;
}
ssl on;
ssl_certificate /etc/apache2/ssl/nginx-cert.crt;
ssl_certificate_key /etc/apache2/ssl/private.key;
ssl_prefer_server_ciphers on;
}
使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。 主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题。
应用服务器过程
应用服务可以是通过网关链接的微服务,也可以是一个自带网关的应用。Web服务器由于要同时为多个客户提供服务,就必须使用某种方式来支持这种多任务的服务方式。一般情况下可以有以下三种方式来选择,多进程方式、多线程方式及异步方式。其中,多进程方式中服务器对一个客户要使用一个进程来提供服务,由于在操作系统中,生成一个进程需要进程内存复制等额外的开销,这样在客户较多时的性能就会降低。为了克服这种生成进程的额外开销,可以使用多线程方式或异步方式。在多线程方式中,使用进程中的多个线程提供服务, 由于线程的开销较小,性能就会提高。事实上,不需要任何额外开销的方式还是异步方式,它使用非阻塞的方式与每个客户通信,服务器使用一个进程进行轮询就行了。
传统的方式
传统的方式是内核提供硬件驱动、通信,资源分配(进程)、系统调用的能力,我们的应用部署在操作系统上。例如,在操作系统上安装启动 apache,通过用 ssh 连接服务器部署具体的应用资源。一般情况下,web server本身能够处理一些静态请求,静态请求通常是一些html、CSS、JavaScript等静态文件,这种文件里面写什么就会显示什么。如果存在内容是有php或者golang写的,web server就会往后端的application(应用服务器)上转,这些application(应用服务器)就会按照微服的一些功能将他划分开,这个就看用户是需要查看图片还是需要上传资料,如果是需要上传资源就存放到后端的数据库服务中,如果是要查看图片我们就直接响应给用户,如果是要生成订单我们还要将这些订单信息写到数据库中,数据库写完之后后端服务器再把这个请求返回给web server。
虚拟化的方式
虚拟化技术是一套解决方案,完整的情况需要CPU、主板芯片组、BIOS和软件的支持,通过虚拟化技术可以将一台计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立的空间内运行而互不影响,从而显著提高计算机的工作效率。
虚拟化的核心是对资源的抽象,目标往往是为了在同一个主机上同时运行多个系统或应用,从而提高系统资源的利用率,并且带来降低成本、方便管理和容错容灾等好处。
但是,使用虚拟机的包括三个缺点:
- 资源效率低下,每个虚拟机都需要一个完全成熟的操作系统;
- 对平台有依赖性。本地机器上运行得很好的功能未必能在产品服务器上正常工作;
- 与容器相比,更重而且规模伸缩较慢。
容器化应用的方式
我印象里,早期的前端工程师都是非常了解操作系统的,因为部署代码和运行应用要设计到很多基础的知识,随着容器化技术的发展,现在部署这部分越来越简单,更加方便弹性扩容,你只需要将你的项目和依赖包(基础镜像)打成一个带有启动指令的项目镜像,然后在服务器创建一个容器,让镜像在容器内运行,就可以实现项目的部署了。
传统虚拟化方式是在硬件层面实现虚拟化,需要有额外的虚拟机管理应用和虚拟机操作系统层。Docker容器化是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,因此更加轻量级。
可以将Docker容器理解为一种轻量级的沙盒,每个容器内运行着一个应用,不同的容器相互隔离,容器之间也可以通过网络互相通信。Docker 包含三个基本概念,分别是镜像(Image)、容器(Container)和仓库(Repository)。
- docker镜像:使用Dockerfile脚本,将你的应用以及应用的依赖包构建而成的一个应用包,它通常带有该应用的启动命令。而这些命令会在容器启动时被执行,也就是说你的应用在启动容器时被启动,一次构建,多处移植使用,再配合shell等脚本语言实现脚本化一键部署。
- 容器:通常是指 linux 容器,采用轻量的虚拟化技术(namespace、cgroup、rootfs)来规划资源,是一组特殊的进程。Docker 提供标准的接口(OCI)来管理和运行容器,核心思想就是如何将应用整合到容器中,并且能在容器中实际运行。
- 仓库:用来存储docker镜像,通过镜像的
ID唯一标识了镜像。你可以把你的docker镜像通过push命令推送到docker仓库,然后就可以在任何能使用docker命令的地方通过pull命令把这个镜像拉取下来。
在容器化方案中,容器是一组特殊的进程,容器共享宿主机的内核,容器通过镜像来实现应用的可移植性和一致性。服务器就是容器的宿主机,docker容器与宿主机之间是相互隔离的。也可以理解成,镜像是基础,容器是镜像使用者,仓库是镜像的管理员。
Docker有这么一种机制,在构建镜像时,它可以依赖一个父镜像作为底层镜像,与当前正要被构建的镜像一起打包,从而构建成一个全新的镜像。而这个被用作依赖的父镜像,就是基础镜像。比如,通常我们开发一个nodejs应用,它不是随处可运行的,它的运行需要依赖操作系统环境和nodejs运行环境。因此,一个单纯的node项目镜像是无法运行起来的,它需要依赖一个基础镜像,这个基础镜像就是nodejs镜像,nodejs镜像内包含了操作系统环境和nodejs环境。
总之,Docker 通过 Dockerfile 来对环境进行描述,通过镜像进行交付,使用时不再需要关注环境不一致相关的问题。这意味着容器可以在任何计算机上运行,甚至是在产品服务器上,都没有任何差别。如火如荼的docker,现已被很多大公司所采用。同时docker也成为了实现serverless(无服务器架构)服务的基础架构。包括阿里云,亚马逊在内的云计算服务商都采用了docker来打造serverless服务平台。
Kubernetes的方式
代应用程序分散在云,虚拟机和服务器之间。手动管理应用程序不再是可行的选择。如果规模变大或者在生产中如何进行容器编排,部署扩容机制如何?
在容器编排领域,比较著名的主要有Kubernetes、Mesos 及 Docker 自家的 Swarm。Kubernetes 是一个可扩展的用于容器化应用程序编排管理的平台,它工作在容器层而不是硬件层,提供复杂作业中集群管理、容器编排、任务调度,还提供了一些与 PasS 类似或者共同的功能,比如部署,扩容,监控,负载均衡,日志记录等。
Kubernetes 架构是一个比较典型的二层架构和 Server-Client 架构。Master 作为中央的管控节点,会去与 Node 进行一个连接。客户端(比如UI/CLI等)只会和 Master 进行连接,把希望的状态或者想执行的命令下发给 Master,Master 会把这些命令或者状态下发给相应的节点,进行最终的执行。Kubernetes 的 Node 是真正运行业务负载的,每个业务负载会以 Pod 的形式运行。一个 Pod 中运行一个或者多个容器,真正去运行这些 Pod 的组件的是叫做kubelet,也就是 Node 上最为关键的组件,它通过 API Server 接收到所需要 Pod 运行的状态。
在 Kubernetes 集群上运行基于微服务的应用程序,起码采用零停机时间部署、回滚到前一个状态更加的容易。
总结
从用户输入域名到收到数据,发生了很多的事情,虽然对于用户而言感觉到的反馈的越少越好,但是对于工程师而言越多的实践越能了解其中的细节。缓存是如何被利用的?缓存失效会导致多少资源的消耗?对于简化部署、提供伸缩性、灵活性,选择怎样的底层基础设施在不同量级的业务应用上遇到的问题也表现不同,知道的越多,遇到问题才越有信心去解决。
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://mi-blog.cn/index.php/2022/07/20/web%e8%ae%bf%e9%97%ae%e8%bf%87%e7%a8%8b%e8%af%a6%e8%a7%a3/