乐趣区

HTTP、PHP-FPM、与握手协议

HTTP 请求的流程梳理

用户输入 url 如 http:www.baidu.com 到浏览器,浏览器如 chrom 需要将其解析为 ip 地址才知道需要到哪里去访问哪个服务器。浏览器解析 DNS 步骤如下

搜索浏览器自身的 dns 缓存,这个缓存缓存时间短,缓存数目有限。
搜索操作系统的 dns 缓存
读取 host 文件的 dns 映射(一般做本地开发映射都是修改这个文件来达到拦截浏览器请求到本地服务器的目的,从而使本地可以成功映射服务器地址)
先本地网卡配置里的 dns 服务器发起域名解析请求,这里好像还有一套运营商的处理流程就不在展开了。
下面好像还有一些流程,由于基本不会执行到这一步,一般所以 dns 运营商的 dns 服务器都会搞定的。
解析失败,以上任何一步成功都会返回一个成功的 ip 地址

浏览器以一个随机的端口享这个 ip 地址的特定端口 (默认 80) 发起著名的 TCP3 次握手。关于一个 http 请求是如何到达 nginx 服务的流程大致如下:
握手完成后的浏览器和服务器就可以愉快地发送 http 请求了,具体在 nginx 上流程如下:

PHP-FPM 在服务端出来请求中扮演了什么角色

PHP、nginx 与 CGI 协议
对于一个 PHP 的 web 程序来说,web 服务器(如:nginx)要想与它通信需要通过 CGI 协议。当一个 web 请求触达 web 服务器时,web 服务器会创建一个 CGI 进程,CGI 进程将 web 的请求按照固定的格式进行解析,然后写入标准输入 (STDIN) 和环境变量中,然后 PHP 启动的 CGI 解析器会从标准输入 (STDIN) 和环境变量中读取 http 请求的数据,所以 $_SERVER 才会有数据,然后做出相应的逻辑处理,然后将处理结果放入标准输出(STDOUT),CGI 进程从 STDOUT 中读取响应数据然后传输给浏览器,这样服务端就完成了一次 http 请求。
上面是 CGI 的实现流程,但是使用 CGI 协议的服务器在用户每次访问服务器的时候都需要 fork/ 销毁 CGI 进程,必然照成多余的系统开销,所以 FASTCGI 就是为了解决这个问题的。

什么是 FastCGI 协议
FastCGI 会创建一个常驻的 master 进程和多个 worker 进程,master 进程负责管理和为 worker 进程反派任务,worker 进程负责内部嵌入了 CGI 解析器用于解释 php 代码。
PHP-FPM 是一个 FastCGI 进程管理器,在 LNMP 体系中就是由它来实现 FastCGI 协议的。同样,它也会创建一个常驻的 master 进程和多个 worker 进程,master 进程负责监听端口和接收来自 nginx 的请求,指派任务给 worker 进程。worker 进程的负责解释 php 代码。PHP-FPM 可以通过配置预先启动一定数量的 worker 进程,这样当 http 请求触达时就可以更快速的响应。

Nginx 关于 FastCGI 的配置
nginx 与 PHP-FPM 之间的通信一般通过其 ngx_http_fastcgi_module 模块来实现。其中 fastcgi_pass 用于设置 fastcgi 服务器的 IP 地址;fastcgi_param 设置传入 fastcgi 服务器的参数。这个模块出现的配置问题一般集中在这一块。
相对于并发状态下出现的问题,一般也都集中在 fastcgi 服务器上,具体表现为 fastcgi 服务器为了应对大量的 http 请求必须不停的 fork 新的 worker 进程,这时就需要考虑服务器可支持的最大链接数和最大打开文件数(可通过 ulimit - n 查看)以及 php-fpm 配置里的最低开启 worker 数和最高开启 worker 数的限制。高性能服务器可以在这个方向上调优。

HTTP 协议三次握手四次挥手的细节

协议过程中客服端与服务端的状态图

TCP 的标志位说明

标志位
英文
说明

SYN
synchronous
建立联机

ACK
acknowledgement
确认

PSH
push
传送

FIN
finish
结束

RST
reset
重置

URG
urgent
紧急

Sequence numbe

顺序号码

Acknowledge number

确认号码

TCP 状态说明

状态
说明

LISTEN
侦听状态

SYN_SEND
发送连接请求 [SYN=J] 后等待匹配连接请求

SYN_RECEIVED
收到连接请求 [SYN=J] 后发送连接确认包 [SYN=k,ack=J+1] 后等待收到确认包 [Ack=k+1] 状态

ESTABLISHED
打开连接后,可以开始传输数据

FIN_WAIT_1
发起连接中断请求 [FIN=M] 后等待远程 TCP 确认时 [Ack=M+1] 状态

FIN_WAIT_2
收到远程中断确认 [Ack=M+1] 后,等待远程中断请求[FIN=N]

CLOSE_WAIT
收到连接中断请求 [FIN=M] 后未发送出中断确认包 [Ack=M=1] 状态

TIME_WAIT
发送确认远程中断请求 [Ack=N+1] 包后,进入等待状态, 用以保证被重新分配的 socket 不会受到之前残留的延迟重发报文影响的机制

大量 TIME_WAIT 的原因「常见性能异常」
在四次挥手断开连接中, 发起 socket 主动关闭的一方 socket 将进入 TIME_WAIT 状态,TIME_WAIT 状态将持续 2 个 MSL(Max Segment Lifetime),TIME_WAIT 状态下的 socket 不能被回收使用.
具体现象是对于一个处理大量短连接的服务器, 如果是由服务器主动关闭客户端的连接, 将导致服务器端存在大量的处于 TIME_WAIT 状态的 socket, 甚至比处于 Established 状态下的 socket 多的多, 严重影响服务器的处理能力, 甚至耗尽可用的 socket, 停止服务.
TIME_WAIT 是 TCP 协议用以保证被重新分配的 socket 不会受到之前残留的延迟重发报文影响的机制, 是必要的逻辑保证。一般产生的原因是系统没有主动关闭连接, 如 mysql 连接资源没有关闭

关于网络链路中追踪异常用到的运维命令
(以下显示的 IP 和端口均为假数据)

Linux 中查看 socket 的状态
cat /proc/net/sockstat

参数
说明

sockets:used
已使用的所有协议套接字总量

TCP:inuse
正在使用(正在侦听)的 TCP 套接字数量。其值≤ netstat –lnt
grep ^tcp
wc –l

TCP:orphan
无主(不属于任何进程)的 TCP 连接数(无用、待销毁的 TCP socket 数)

TCP:tw
等待关闭的 TCP 连接数。其值等于 netstat –ant
grep TIME_WAIT
wc –l

TCP:alloc
已分配(已建立、已申请到 sk_buff)的 TCP 套接字数量。其值等于 netstat –ant
grep ^tcp
wc –l

TCP:mem
套接字缓冲区使用量

UDP:inuse
正在使用的 UDP 套接字数量

FRAG
使用的 IP 段数量

查看当前 tcp 链接情况
netstat -na | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’

参数
说明

LISTEN
正在监听状态

CLOSE_WAIT
对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成 CLOSE_WAIT 此时我方要调用 close()来使得连接正确关闭

ESTABLISHED
建立连接,正在通信

TIME_WAIT
我方主动调用 close()断开连接,收到对方确认后状态变为 TIME_WAIT

tcp 工具抓取网络请求包
tcpdump -n port 3306

mysql 主动断开链接
11:38:45.693382 IP 172.18.0.3.3306 > 172.18.0.5.38822: Flags [F.], seq 123, ack 144, win 227, options [nop,nop,TS val 3000355 ecr 2997359], length 0 # MySQL 发送 fin 包给我 11:38:45.740958 IP 172.18.0.5.38822 > 172.18.0.3.3306: Flags [.], ack 124, win 229, options [nop,nop,TS val 3000360 ecr 3000355], length 0 # 我回复 ack 给它 11:38:45.740960 IP 172.18.0.3.3306 > 172.18.0.5.38822: Flags [F.], ack 125, win 231, options [nop,nop,TS val 3000360 ecr 3000355], length 0 # MySQL 发送 fin 包给客户端 11:38:45.740965 IP 172.18.0.5.38822 > 172.18.0.3.3306: Flags [.], ack 125, win 229, options [nop,nop,TS val 3000360 ecr 3000355], length 0 # 客户端回复 ack 给我 ……
src > dst: flags data-seqno ack window urgent options
# 发生了 3 次握手
11:38:15.679863 IP 172.18.0.5.38822 > 172.18.0.3.3306: Flags [S], seq 4065722321, win 29200, options [mss 1460,sackOK,TS val 2997352 ecr 0,nop,wscale 7], length 0
11:38:15.679923 IP 172.18.0.3.3306 > 172.18.0.5.38822: Flags [S.], seq 780487619, ack 4065722322, win 28960, options [mss 1460,sackOK,TS val 2997352 ecr 2997352,nop,wscale 7], length 0
11:38:15.679936 IP 172.18.0.5.38822 > 172.18.0.3.3306: Flags [.], ack 1, win 229, options [nop,nop,TS val 2997352 ecr 2997352], length 0

参数
说明

src > dst
表明从源地址到目的地址

flags
是 TCP 包中的标志信息,S 是 SYN 标志, F(FIN), P(PUSH) , R(RST) “.”(没有标记)

data-seqno
是数据包中的数据的顺序号

ack
是下次期望的顺序号

window
是接收缓存的窗口大小

urgent
表明数据包中是否有紧急指针

options
是选项

退出移动版