http代理
代理相关头字段
代理服务器需要用字段“Via ”标明代理的身份。
Via是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾,就像是经手人盖了一个章。
Via字段只解决了客户端和源服务器判断是否存在代理的问题,还不能知道对方的真实信息。
但服务器的IP地址应该是保密的,关系到企业的内网安全,所以一般不会让客户端知道。不过反过来,通常
服务器需要知道客户端的真实IP地址,方便做访问控制、用户画像、统计分析。
可惜的是HTTP标准里并没有为此定义头字段,但已经出现了很多“事实上的标准”,最常用的两个头字段是“X-Forwarded-For”和“X-Real-IP”。
“X-Forwarded-For”的字面意思是“为谁而转发”,形式上和“Via”差不多,也是每经过一个代理节点就会在字段里追加一个信息。但“Via”追加的是代理主机名(或者域名),而“X-Forwarded-For”追加的是 请求方的IP地址。所以,在字段里最左边的IP地址就客户端的地址。
“X-Real-IP”是另一种获取客户端真实IP的手段,它的作用很简单,就是记录客户端IP地址,没有中间的代理信息,相当于是“X-Forwarded-For”的简化版。如果客户端和源服务器之间只有一个代理,那么这两个字段的值就是相同的
X-Forwarded-For 并不完全可信。
缓存代理
在没有缓存的时候,代理服务器每次都是直接转发客户端和服务器的报文,中间不会存储任何数据,只有最简单的中转功能。加入了缓存后就不一样了。代理服务收到源服务器发来的响应数据后需要做两件事。第一个当然是把报文转发给客户端,而第二个就是 把报文存入自己的Cache里。下一次再有相同的请求,代理服务器就可以直接发送304或者缓存数据,不必再从源服务器那里获取。这样 就降低了客户端的等待时间,同时节约了源服务器的网络带宽。
“Cache-Control”属性:max-age、no_store、no_cache和must-revalidate, 这4种缓存属性可以约束客户端,也可以约束代理。
我们要区分客户端上的缓存和代理上的缓存,可以使用两个新属性“private p ”和“public p ”。 “private”表示缓存只能在客户端保存,是用户“私有”的,不能放在代理上与别人共享。而“public”的,意思就是缓存完全开放,谁都可以存,谁都可以用。
“proxy-revalidate”只要求代理的缓存过期后必须验证,客户端不必回源,只验证到代理这个环节就行了。
再次,缓存的生存时间可以使用新的“s-maxage”(s是share的意思,注意maxage中间没有“-”),只限定在代理上能够存多久,而客户端仍然使用“max_age”。
还有一个代理专用的属性“no-transform”。代理有时候会对缓存下来的数据做一些优化,比如把图片生成png、webp等几种格式,方便今后的请求处理,而“no-transform”就会禁止这样做,不许“偷偷摸摸搞小动作。
用“便利店冷柜”来举例理解:
水果上贴着标签“private, max-age=5”。这就是说水果不能放进冷柜,必须直接给顾客,保鲜期5天,过 期了还得去超市重新进货。
冻鱼上贴着标签“public, max-age=5, s-maxage=10”。这个的意思就是可以在冰柜里存10天,但顾客那里 只能存5天,过期了可以来便利店取,只要在10天之内就不必再找超市。
排骨上贴着标签“max-age=30, proxy-revalidate, no-transform”。因为缓存默认是public的,那么它在便 利店和顾客的冰箱里就都可以存30天,过期后便利店必须去超市进新货,而且不能擅自把“大排”改成“小排”
nginx中的 proxy 模块就可以利用缓存代理功能。
https
https =http+ssl/tls
TLS由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称 加密、非对称加密、身份认证等许多密码学前沿技术。
浏览器和服务器在使用TLS建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称 为“密码套件”(cipher suite,也叫加密套件)
- 因为HTTP是明文传输,所以不安全,容易被黑客窃听或窜改;
- 通信安全必须同时具备机密性、完整性,身份认证和不可否认这四个特性;
- HTTPS的语法、语义仍然是HTTP,但把下层的协议由TCP/IP换成了SSL/TLS;
- SSL/TLS是信息安全领域中的权威标准,采用多种先进的加密技术保证通信安全;
- OpenSSL是著名的开源密码学工具包,是SSL/TLS的具体实现。
打个比方协商选定的是“ECDHE-RSA-AES256-GCM-SHA384”。
这么长的名字看着有点晕吧,不用怕,其实TLS的密码套件命名非常规范,格式很固定。基本的形式是“密 钥交换算法+签名算法+对称加密算法+摘要算法”,比如刚才的密码套件的意思就是:
“握手时使用ECDHE算法进行密钥交换,用RSA签名和身份认证,握手后的通信使用AES对称算法,密钥长 度256位,分组模式是GCM,摘要算法SHA384用于消息认证和产生随机数。”
加密可以分为两大类:对称加密和非对称加密 。
对称加密
“对称加密”很好理解,就是指加密和解密时使用的密钥都是同一个,是“对称”的。只要保证了密钥的安全,那整个通信过程就可以说具有了机密性。
TLS里有非常多的对称加密算法可供选择,比如RC4、DES、3DES、AES、ChaCha20等,但前三种算法都被认为是不安全的,通常都禁止使用,目前常用的只有AES和ChaCha20。
非对称加密
对称加密看上去好像完美地实现了机密性,但其中有一个很大的问题:如何把密钥安全地传递给对方,术语叫“密钥交换”。
因为在对称加密算法中只要持有密钥就可以解密。如果你和网站约定的密钥在传递途中被黑客窃取,那他就可以在之后随意解密收发的数据,通信过程也就没有机密性可言了。
混合加密
在TLS里使用的混合加密,因为非对称加密太慢了。
在通信刚开始的时候使用非对称算法,比如RSA、ECDHE,首先解决密钥交换的问题。 然后用随机数产生对称算法使用的“会话密钥”(session key),再用公钥加密。因为会话密钥很短,通 常只有16字节或32字节,所以慢一点也无所谓。
对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。
- 加密算法的核心思想是“把一个小秘密(密钥)转化为一个大秘密(密文消息)”,守住了小秘密,也 就守住了大秘密;
- 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换,常用的有AES和 ChaCha20;
- 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度 慢,常用的有RSA和ECC;
- 把对称加密和非对称加密结合起来就得到了“又好又快”的混合加密,也就是TLS里使用的加密方式。
数字签名与证书
摘要算法
实现完整性的手段主要是摘要算法 摘 (Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)
对消息内容进行哈希后,得到一个固定的值,不可逆,这个值其实等价于消息 。
完整性
对端拿到消息后 再用哈希函数(摘要算法)比对 是否一致,当然黑客也可以更改消息内容和 摘要数据。
真正的完整性必须要建立在机密性之上,在混合加密系统里用会话密钥加密消息和摘要,这样黑客无法得知明文,也就没有办法动手脚了。
数字签名
通过非对称加密的私钥加密, 公钥解密
签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的。
ca 认证机构
公钥不是所有人都可以颁发的,需要认证机构颁发的才是可以信任的。
https 握手
在HTTP协议里,建立连接后,浏览器会立即发送请求报文。但现在是HTTPS协议,它需要再用另外一个“握手”过程,在TCP上建立安全连接,之后才是收发HTTP报文。
握手为了拿会话密码, 拿到会话密码后,就是利用对称加密,进行消息加解密通信即可。
HTTPS建立连接的过程:先是TCP三次握手,然后是TLS一次握手。
连接太慢该怎么办
HTTPS连接大致上可以划分为两个部分,第一个是建立连接时的非对称加非密握手 ,第二个是握手后的对称加密报文传输 。
cpu密集型,买更好的 cpu。
使用 tls1.3协议
https证书
第一,申请证书时应当同时申请RSA和ECDSA两种证书,在Nginx里配置成双证书验证,这样服务器可以自 动选择快速的椭圆曲线证书,同时也兼容只支持RSA的客户端。
第二,如果申请RSA证书,私钥至少要2048位,摘要算法应该选用SHA-2,例如SHA256、SHA384等。
第三,出于安全的考虑,“Let’s Encrypt”证书的有效期很短,只有90天,时间一到就会过期失效,所以 必须要定期更新。你可以在crontab里加个每周或每月任务,发送更新请求,不过很多ACME客户端会自动 添加这样的定期任务,完全不用你操心。
http2
HTTP/1⾥可以⽤头字段“Content-Encoding”指定Body的编码⽅式,⽐如⽤gzip压缩来节约带宽,但报⽂的另⼀个组成部分⸺Header却被⽆视了,没有针对它的优化⼿ 段。
头部压缩
开发了专⻔的“HPACK”算法,在客⼾端和服务器两端建⽴“字典”,⽤索引号表⽰重复的字符串,还⾤⽤哈夫曼编码来压缩整数和字符串,可以达到50%~90%的 ⾼压缩率。
⼆进制格式
HTTP/2不再使⽤⾁眼可⻅的ASCII码,⽽是向下层的TCP/IP协议“靠拢”,全⾯采⽤⼆进制格式。
它把TCP协议的部分特性挪到了应⽤层,把原来的“Header+Body”的消息“打散”为数个⼩⽚的⼆进制“帧” (Frame),⽤“HEADERS”帧存放头数据、“DATA”帧存放实体数据。
HTTP/2数据分帧后“Header+Body”的报⽂结构就完全消失了,协议看到的只是⼀个个的“碎⽚”
“流”
消息的“碎⽚”到达⽬的地后应该怎么组装起来呢?
HTTP/2为此定义了⼀个“流”(Stream)的概念,它是⼆进制帧的双向传输序列,同⼀个消息往返的帧会 分配⼀个唯⼀的流ID。你可以想象把它成是⼀个虚拟的“数据流”,在⾥⾯流动的是⼀串有先后顺序的数据 帧,这些数据帧按照次序组装起来就是HTTP/1⾥的请求报⽂和响应报⽂。
因为“流”是虚拟的,实际上并不存在,所以HTTP/2就可以在⼀个TCP连接上⽤“流”同时发送多个“碎 ⽚化”的消息,这就是常说的“多路复⽤”( Multiplexing)⸺多个往返通信都复⽤⼀个连接来处理。
在“流”的层⾯上看,消息是⼀些有序的“帧”序列,⽽在“连接”的层⾯上看,消息却是乱序收发 的“帧”。多个请求/响应之间没有了顺序关系,不需要排队等待,也就不会再出现“队头阻塞”问题,降 低了延迟,⼤幅度提⾼了连接的利⽤率。
为了更好地利⽤连接,加⼤吞吐量,HTTP/2还添加了⼀些控制帧来管理虚拟的“流”,实现了优先级和流 量控制,这些特性也和TCP协议⾮常相似。
HTTP/2还在⼀定程度上改变了传统的“请求-应答”⼯作模式,服务器不再是完全被动地响应请求,也可以 新建“流”主动向客⼾端发送消息。⽐如,在浏览器刚请求HTML的时候就提前把可能会⽤到的JS、CSS⽂ 件发给客⼾端,减少等待的延迟,这被称为“服务器推送”(Server Push,也叫Cache Push)。
安全强化
由于HTTPS已经是⼤势所趋,⽽且主流的浏览器Chrome、Firefox等都公开宣布只⽀持加密的HTTP/2, 所以“事实上”的HTTP/2是加密的。也就是说,互联⽹上通常所能⻅到的HTTP/2都是使⽤“https”协议 名,跑在TLS上⾯。
为了区分“加密”和“明⽂”这两个不同的版本,HTTP/2协议定义了两个字符串标识符:“h2”表⽰加密 的HTTP/2,“h2c”表⽰明⽂的HTTP/2,多出的那个字⺟“c”的意思是“clear text”。
协议栈
下⾯的这张图对⽐了HTTP/1、HTTPS和HTTP/2的协议栈,你可以清晰地看到,HTTP/2是建⽴ 在“HPack”“Stream”“TLS1.2”基础之上的,⽐HTTP/1、HTTPS复杂了⼀些。
http2剖析
头部压缩
头部压缩,静态表,动态表
下⾯的这个表格列出了“静态表”的⼀部分,这样只要查表就可以知道字段名和对应的值,⽐如数 字“2”代表“GET”,数字“8”代表状态码200。
随着在HTTP/2连接上发送的报⽂越来越多,两边的“字典”也会越来越丰富,最终每 次的头部字段都会变成⼀两个字节的代码,原来上千字节的头⽤⼏⼗个字节就可以表⽰了,压缩效果⽐gzip 要好得多
⼆进制帧
HTTP/2的帧结构有点类似TCP的段或者TLS⾥的记录,但报头很⼩,只有9字节,⾮常地节省(可以对⽐⼀ 下TCP头,它最少是20个字节)。
帧开头是3个字节的⻓度⻓ (但不包括头的9个字节),默认上限是2^14,最⼤是2^24,也就是说HTTP/2的帧 通常不超过16K,最⼤是16M。
⻓度后⾯的⼀个字节是帧类型 帧 ,⼤致可以分成数据帧和控制帧两类,HEADERS帧和DATA帧属于数据帧,存 放的是HTTP报⽂,⽽SETTINGS、PING、PRIORITY等则是⽤来管理流的控制帧。
第5个字节是⾮常重要的帧标志 帧 信息,可以保存8个标志位,携带简单的控制信息。常⽤的标志位 有END_HEADERS 表⽰头数据结束,相当于HTTP/1⾥头后的空⾏(“\r\n”),END_STREAM 表⽰单⽅向数据发送结束(即EOS,End of Stream),相当于HTTP/1⾥Chunked分块结束标志(“0\r\n\r\n”)
报⽂头⾥最后4个字节是流标识符 流 ,也就是帧所属的“流”,接收⽅使⽤它就可以从乱序的帧⾥识别出具有相同流ID的帧序列,按顺序组装起来就实现了虚拟的“流”.
流与多路复⽤
流是⼆进制帧的双向传输序列。
在HTTP/2连接上,虽然帧是乱序收发的,但只要它们都拥有相同的流ID,就都属于⼀个流,⽽且在这个流⾥帧不是⽆序的,⽽是有着严格的先后顺序。
HTTP/2的流有哪些特点呢?
- 流是可并发的,⼀个HTTP/2连接上可以同时发出多个流传输数据,也就是并发多请求,实现“多路复 ⽤”;
- 客⼾端和服务器都可以创建流,双⽅互不⼲扰;
- 流是双向的,⼀个流⾥⾯客⼾端和服务器都可以发送或接收数据帧,也就是⼀个“请求-应答”来回;
- 流之间没有固定关系,彼此独⽴,但流内部的帧是有严格顺序的;
- 流可以设置优先级,让服务器优先处理,⽐如先传HTML/CSS,后传图⽚,优化⽤⼾体验;
- 流ID不能重⽤,只能顺序递增,客⼾端发起的ID是奇数,服务器端发起的ID是偶数;
- 在流上发送“RST_STREAM”帧可以随时终⽌流,取消接收或发送;
- 第0号流⽐较特殊,不能关闭,也不能发送数据帧,只能发送控制帧,⽤于流量控制。
下载⼤⽂件的时候想取消接收,在HTTP/1⾥只能断开TCP连接重新“三次握⼿”,成本很⾼,⽽在HTTP/2⾥就可以简单地发送⼀个“RST_STREAM”中断流,⽽⻓连接会继续保持。
因为客⼾端和服务器两端都可以创建流,⽽流ID有奇数偶数和上限的区分,所以⼤多数的流ID都会是奇数,⽽且客⼾端在⼀个连接⾥最多只能发出2^30,也就是10亿个请求。
所以就要问了:ID⽤完了该怎么办呢?这个时候可以再发⼀个控制帧“GOAWAY”,真正关闭TCP连接。
流状态转换
最开始的时候流都是“空闲空 ”(idle)状态,也就是“不存在”,可以理解成是待分配的“号段资源”。
当客⼾端发送HEADERS帧后,有了流ID,流就进⼊了“打开打 ”状态,两端都可以收发数据,然后客⼾端发送⼀个带“END_STREAM”标志位的帧,流就进⼊了“半关闭 半 ”状态。
这个“半关闭”状态很重要,意味着客⼾端的请求数据已经发送完了,需要接受响应数据,⽽服务器端也知道请求数据接收完毕,之后就要内部处理,再发送响应数据。
响应数据发完了之后,也要带上“END_STREAM”标志位,表⽰数据发送完毕,这样流两端就都进⼊了“关闭关 ”状态,流就结束了。
- HTTP/2必须先发送⼀个“连接前⾔”字符串,然后才能建⽴正式连接;
- HTTP/2废除了起始⾏,统⼀使⽤头字段,在两端维护字段“Key-Value”的索引表,使⽤“HPACK”算 法压缩头部;
- HTTP/2把报⽂切分为多种类型的⼆进制帧,报头⾥最重要的字段是流标识符,标记帧属于哪个流;
- 流是HTTP/2虚拟的概念,是帧的双向传输序列,相当于HTTP/1⾥的⼀次“请求-应答”;
- 在⼀个HTTP/2连接上可以并发多个流,也就是多个“请求-响应”报⽂,这就是“多路复⽤”。
服务端是不是要为每⼀个客⼾端都单独维护⼀份索引表?连接的客⼾端多了的话内存不就OOM了嘛
是的,不过动态表也有淘汰机制,服务器可以⾃⼰定制策略,不会过度占⽤内存。
http/2的请求是乱序的,彼此不依赖,所以没有队头阻塞。
⼩帧好,如果多个流的帧可以在同⼀个tcp数据段发送的话,就可以提⾼⽹络利⽤率
http3
HTTP/2的“队头阻塞”
基本解决对头阻塞,但没有全解决
打个比方: 客⼾端⽤TCP发送了三个包,但服务器所在的操作系统只收到了后两个包,第⼀个包丢了。那么内核⾥的TCP协议栈就只能把已经收到的包暂存起来,“停下”等着客⼾端重传那个丢失的包,这样就⼜出现了“队 头阻塞”。
由于这种“队头阻塞”是TCP协议固有的,所以HTTP/2即使设计出再多的“花样”也⽆法解决。
http3基于 udp 实现,还在探索中。
nginx
Nginx 是一个高性能的 Web 服务器,它非常的轻量级,消耗的 CPU、内存很少;
Nginx 采用“master/workers”进程池架构,不使用多线程,消除了进程、线程切换的成本;
Nginx 基于 epoll 实现了“I/O 多路复用”,不会阻塞,所以性能很高;
Nginx 使用了“职责链”模式,多个模块分工合作,自由组合,以流水线的方式处理 HTTP 请求。
Nginx 的“流水线”,在 Nginx 里的术语叫“阶段式处理”(Phases),一共有 11 个阶段,每个阶段里又有许多各司其职的模块。
waf
waf功能:
- IP 黑名单和白名单,拒绝黑名单上地址的访问,或者只允许白名单上的用户访问;
- URI 黑名单和白名单,与 IP 黑白名单类似,允许或禁止对某些 URI 的访问;
- 防护 DDoS 攻击,对特定的 IP 地址限连限速;
- 过滤请求报文,防御“代码注入”攻击;
- 过滤响应报文,防御敏感信息外泄;
- 审计日志,记录所有检测到的入侵操作。
nginx 中 ip 限制其实就是 waf 的一种
1map $remote_addr $blocked {
2 default 0;
3 "1.2.3.4" 1;
4 "5.6.7.8" 1;
5}
6
7if ($blocked) {
8 return 403 "you are blocked.";
9}
WebSocket
HTTP/2 针对的是“队头阻塞”,而 WebSocket 针对的是“请求 - 应答”通信模式。
“请求 - 应答”是一种“半双工”的通信模式,虽然可以双向收发数据,但同一时刻只能一个方向上有动作,传输效率低。更关键的一点,它是一种“被动”通信模式,服务器只能“被动”响应客户端的请求,无法主动向客户端发送数据。
WebSocket出现之前, “轮询”(polling)就是比较常用的的一种
WebSocket 是一个真正“全双工”的通信协议
WebSocket 采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,但因为它的主要运行环境是浏览器,为了便于推广和应用,就不得不“搭便车”,在使用习惯上尽量向 HTTP 靠拢,这就是它名字里“Web”的含义。
服务发现方面,WebSocket 没有使用 TCP 的“IP 地址 + 端口号”,而是延用了 HTTP 的 URI 格式,但开头的协议名不是“http”,引入的是两个新的名字:“ws”和“wss”,分别表示明文和加密的 WebSocket 协议。
1ws://www.chrono.com
2ws://www.chrono.com:8080/srv
3wss://www.chrono.com:445/im?user_id=xxx
WebSocket 的帧结构
WebSocket 用的也是二进制帧
第一个字节的第一位“FIN”是消息结束的标志位,相当于 HTTP/2 里的“END_STREAM”,表示数据发送完毕。一个消息可以拆成多个帧,接收方看到“FIN”后,就可以把前面的帧拼起来,组成完整的消息。
“FIN”后面的三个位是保留位,目前没有任何意义,但必须是 0。
第一个字节的后 4 位很重要,叫“Opcode”,操作码,其实就是帧类型,比如 1 表示帧内容是纯文本,2 表示帧内容是二进制数据,8 是关闭连接,9 和 10 分别是连接保活的 PING 和 PONG。
第二个字节第一位是掩码标志位“MASK”,表示帧内容是否使用异或操作(xor)做简单的加密。目前的 WebSocket 标准规定,客户端发送数据必须使用掩码,而服务器发送则必须不使用掩码。
第二个字节后 7 位是“Payload len”,表示帧内容的长度。它是另一种变长编码,最少 7 位,最多是 7+64 位,也就是额外增加 8 个字节,所以一个 WebSocket 帧最大是 2^64。
长度字段后面是“Masking-key”,掩码密钥,它是由上面的标志位“MASK”决定的,如果使用掩码就是 4 个字节的随机数,否则就不存在。
这么分析下来,其实 WebSocket 的帧头就四个部分:“结束标志位 + 操作码 + 帧长度 + 掩码”,只是使用了变长编码的“小花招”,不像 HTTP/2 定长报文头那么简单明了。
WebSocket 的握手
“Connection: Upgrade”,表示要求协议“升级”;
“Upgrade: websocket”,表示要“升级”成 WebSocket 协议。
另外,为了防止普通的 HTTP 消息被“意外”识别成 WebSocket,握手消息还增加了两个额外的认证用头字段(所谓的“挑战”,Challenge):
Sec-WebSocket-Key:一个 Base64 编码的 16 字节随机数,作为简单的认证密钥;
Sec-WebSocket-Version:协议的版本号,当前必须是 13。
服务器收到 HTTP 请求报文,看到上面的四个字段,就知道这不是一个普通的 GET 请求,而是 WebSocket 的升级请求,于是就不走普通的 HTTP 处理流程,而是构造一个特殊的“101 Switching Protocols”响应报文,通知客户端,接下来就不用 HTTP 了,全改用 WebSocket 协议通信。(有点像 TLS 的“Change Cipher Spec”)
WebSocket 的握手响应报文也是有特殊格式的,要用字段“Sec-WebSocket-Accept”验证客户端请求报文,同样也是为了防止误连接。
具体的做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的 UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,再计算 SHA-1 摘要。
1encode_base64(
2 sha1(
3 Sec-WebSocket-Key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' ))
客户端收到响应报文,就可以用同样的算法,比对值是否相等,如果相等,就说明返回的报文确实是刚才握手时连接的服务器,认证成功。
握手完成,后续传输的数据就不再是 HTTP 报文,而是 WebSocket 格式的二进制帧了。
小结
浏览器是一个“沙盒”环境,有很多的限制,不允许建立 TCP 连接收发数据,而有了 WebSocket,我们就可以在浏览器里与服务器直接建立“TCP 连接”,获得更多的自由。
- HTTP 的“请求 - 应答”模式不适合开发“实时通信”应用,效率低,难以实现动态页面,所以出现了 WebSocket;
- WebSocket 是一个“全双工”的通信协议,相当于对 TCP 做了一层“薄薄的包装”,让它运行在浏览器环境里;
- WebSocket 使用兼容 HTTP 的 URI 来发现服务,但定义了新的协议名“ws”和“wss”,端口号也沿用了 80 和 443;
- WebSocket 使用二进制帧,结构比较简单,特殊的地方是有个“掩码”操作,客户端发数据必须掩码,服务器则不用;
- WebSocket 利用 HTTP 协议实现连接握手,发送 GET 请求要求“协议升级”,握手过程中有个非常简单的认证机制,目的是防止误连接。
http性能优化
HTTP 服务器
衡量服务器性能的主要指标有三个:吞吐量(requests per second)、并发数(concurrency)和响应时间(time per request)。
吞吐量就是我们常说的 RPS,每秒的请求次数,也有叫 TPS、QPS,它是服务器最基本的性能指标,RPS 越高就说明服务器的性能越好。
并发数反映的是服务器的负载能力,也就是服务器能够同时支持的客户端数量,当然也是越多越好,能够服务更多的用户。
响应时间反映的是服务器的处理能力,也就是快慢程度,响应时间越短,单位时间内服务器就能够给越多的用户提供服务,提高吞吐量和并发数。
1top # 查看 CPU 和内存占用情况
2vmstat 2 # 每 2 秒检查一次系统状态
3sar -n DEV 2 # 看所有网卡的流量,定时 2 秒检查
HTTP 客户端
客户端是信息的消费者,一切数据都要通过网络从服务器获取,所以它最基本的性能指标就是“延迟”(latency)。
首先,我们必须谨记有一个“不可逾越”的障碍——光速,因为地理距离而导致的延迟是无法克服的,访问数千公里外的网站显然会有更大的延迟。
其次,第二个因素是带宽,它又包括接入互联网时的电缆、WiFi、4G 和运营商内部网络、运营商之间网络的各种带宽,每一处都有可能成为数据传输的瓶颈,降低传输速度,增加延迟。
第三个因素是 DNS 查询,如果域名在本地没有缓存,就必须向 DNS 系统发起查询,引发一连串的网络通信成本,而在获取 IP 地址之前客户端只能等待,无法访问网站,
第四个因素是 TCP 握手,你应该对它比较熟悉了吧,必须要经过 SYN、SYN/ACK、ACK 三个包之后才能建立连接,它带来的延迟由光速和带宽共同决定。
建立 TCP 连接之后,就是正常的数据收发了,后面还有解析 HTML、执行 JavaScript、排版渲染等等,这些也会耗费一些时间。不过它们已经不属于 HTTP 了.
因为有“队头阻塞”,浏览器对每个域名最多开 6 个并发连接(HTTP/1.1),当页面里链接很多的时候就必须排队等待(Queued、Queueing),这里它就等待了 1.62 秒,然后才被浏览器正式处理;
浏览器要预先分配资源,调度连接,花费了 11.56 毫秒(Stalled);
连接前必须要解析域名,这里因为有本地缓存,所以只消耗了 0.41 毫秒(DNS Lookup);
与网站服务器建立连接的成本很高,总共花费了 270.87 毫秒,其中有 134.89 毫秒用于 TLS 握手,那么 TCP 握手的时间就是 135.98 毫秒(Initial connection、SSL);
实际发送数据非常快,只用了 0.11 毫秒(Request sent);
之后就是等待服务器的响应,专有名词叫 TTFB(Time To First Byte),也就是“首字节响应时间”,里面包括了服务器的处理时间和网络传输时间,花了 124.2 毫秒;
接收数据也是非常快的,用了 3.58 毫秒(Content Dowload)。
HTTP 传输链路
“第一公里”“中间一公里”和“最后一公里”
“第一公里”是指网站的出口,也就是服务器接入互联网的传输线路,它的带宽直接决定了网站对外的服务能力,也就是吞吐量等指标。显然,优化性能应该在这“第一公里”加大投入,尽量购买大带宽,接入更多的运营商网络。
“中间一公里”就是由许多小网络组成的实际的互联网,其实它远不止“一公里”,而是非常非常庞大和复杂的网络,地理距离、网络互通都严重影响了传输速度。好在这里面有一个 HTTP 的“好帮手”——CDN,它可以帮助网站跨越“千山万水”,让这段距离看起来真的就好像只有“一公里”。
“最后一公里”是用户访问互联网的入口,对于固网用户就是光纤、网线,对于移动用户就是 WiFi、基站。以前它是客户端性能的主要瓶颈,延迟大带宽小,但随着近几年 4G 和高速宽带的普及,“最后一公里”的情况已经好了很多,不再是制约性能的主要因素了。
“第零公里”, 就是网站内部的 Web 服务系统。它其实也是一个小型的网络(当然也可能会非常大),中间的数据处理、传输会导致延迟,增加服务器的响应时间,也是一个不可忽视的优化点。