[转]一次完整的HTTP接口请求过程及针对优化
原文: https://www.cnblogs.com/linguoguo/p/10600719.html
——————————————————————————————
客户端发起http请求,基本的经历过程如下:
域名解析 -> TCP三次握手 -> 建立TCP连接后发起HTTP请求 -> Nginx反向代理 -> 应用层 -> 服务层 -> 缓存/数据库
一、域名解析
首先Chrome浏览器会解析 www.linux178.com 这个域名(准确的叫法应该是主机名)对应的IP地址。怎么解析到对应的IP地址?
① Chrome浏览器 会首先搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存),看自身的缓存中是否有www.linux178.com 对应的条目,而且没有过期,如果有且没有过期则解析到此结束。
注:我们怎么查看Chrome自身的缓存?可以使用 chrome://net-internals/#dns 来进行查看
② 如果浏览器自身的缓存里面没有找到对应的条目,那么Chrome会搜索操作系统自身的DNS缓存,如果找到且没有过期则停止搜索解析到此结束.
注:怎么查看操作系统自身的DNS缓存,以Windows系统为例,可以在命令行下使用 ipconfig /displaydns 来进行查看
③ 如果在Windows系统的DNS缓存也没有找到,那么尝试读取hosts文件(位于C:\Windows\System32\drivers\etc),看看这里面有没有该域名对应的IP地址,如果有则解析成功。
④ 如果在hosts文件中也没有找到对应的条目,浏览器就会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器(一般是电信运营商提供的,也可以使用像Google提供的DNS服务器)发起域名解析请求(通过的是UDP协议向DNS的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址),运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。如果没有找到对应的条目,则有运营商的DNS代我们的浏览器发起迭代DNS解析请求,它首先是会找根域的DNS的IP地址(这个DNS服务器都内置13台根域的DNS的IP地址),找打根域的DNS地址,就会向其发起请求(请问www.linux178.com这个域名的IP地址是多少啊?),根域发现这是一个顶级域com域的一个域名,于是就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去,于是运营商的DNS就得到了com域的IP地址,又向com域的IP地址发起了请求(请问www.linux178.com这个域名的IP地址是多少?),com域这台服务器告诉运营商的DNS我不知道www.linux178.com这个域名的IP地址,但是我知道linux178.com这个域的DNS地址,你去找它去,于是运营商的DNS又向linux178.com这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.linux178.com这个域名的IP地址是多少?),这个时候linux178.com域的DNS服务器一查,诶,果真在我这里,于是就把找到的结果发送给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了www.linux178.com这个域名对应的IP地址,并返回给Windows系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.linux178.com 对应的IP地址,该进行一步的动作了。
注:一般情况下是不会进行以下步骤的
如果经过以上的4个步骤,还没有解析成功,那么会进行如下步骤(以下是针对Windows操作系统):
⑤ 操作系统就会查找NetBIOS name Cache(NetBIOS名称缓存,就存在客户端电脑中的),那这个缓存有什么东西呢?凡是最近一段时间内和我成功通讯的计算机的计算机名和Ip地址,就都会存在这个缓存里面。什么情况下该步能解析成功呢?就是该名称正好是几分钟前和我成功通信过,那么这一步就可以成功解析。
⑥ 如果第⑤步也没有成功,那会查询WINS 服务器(是NETBIOS名称和IP地址对应的服务器)
⑦ 如果第⑥步也没有查询成功,那么客户端就要进行广播查找
⑧ 如果第⑦步也没有成功,那么客户端就读取LMHOSTS文件(和HOSTS文件同一个目录下,写法也一样)
如果第八步还没有解析成功,那么就宣告这次解析失败,那就无法跟目标计算机进行通信。只要这八步中有一步可以解析成功,那就可以成功和目标计算机进行通信。
建议:
(1)使用HTTPDNS之类的技术方案解决:HTTPDNS说白了就是用HTTP协议进行域名解析,替代现有的基于UDP的DNS协议,避免域名解析失败,域名劫持等问题
(2)客户端和服务器之间保持长连接,使用socket通信
(3)本地维护一个ip列表,直接使用ip进行请求,而非用域名,并定期去更新这个列表
二、TCP三次握手
建议:无
三、发起HTTP请求
建议:
(1)并行连接:通过多条TCP连接发起并发的HTTP请求
(2)持久连接:keep-alive,长连接,重用TCP连接,以消除连接和关闭的时延,以事务个数和时间来决定是否关闭连接
(3)管道化连接:通过共享TCP连接发起并发的HTTP请求
(4)使用Restful风格的API
(5)缩短参数长度、数量
(6)压缩报文头或请求内容的相关信息
(7)合并多个HTTP请求,避免不必要的浪费
四、Nginx反向代理
Nginx可以做反向代理服务器,在真正的服务层前面设置网关,用来接收接口请求,然后根据策略分发给后面的web服务器。这一层做好冗余:有两台nginx,一台对线上提供服务,另一台冗余以保证高可用,常见的实践是keepalived存活探测,相同virtual IP提供服务。
负载均衡调度策略:
轮询(RR):按一次循环的方式将请求调度到不同的服务器上。轮询算法假设所有的服务器处理请求的能力都一样,调度器会将所有的请求平均分配给每个真实服务器
加权轮询(WRR):LVS会考虑每台服务器的性能,并给每台服务器添加一个权值,如果服务器A的权值为1,服务器B的权值为2,则调度器调度到服务器B的请求会是A的两倍
最小连接(LC):把请求调度到连接数量最小的服务器上
加权最小连接(WLC):给每个服务器一个权值,调度器会尽可能保持服务器连接数量与权值之间的平衡
基于局部性最少的连接(lblc):根据请求的目标IP寻找最近该目标IP地址所使用的服务器,如果这台服务器依然可用,并且有能力处理该请求,调度器会尽量选择相同的服务器,否则会继续选择其他可行的服务器
带复制的基于局部性最少连接(lblcr):记录的不是一个目标IP与一台服务器之间的连接记录,它会维护一个目标IP到一组服务器之间的映射关系,防止单点服务器负载过高
目标地址散列调度(DH):通过散列函数将目标IP与服务器建立映射关系,出现服务器不可用或负载过高的情况下,发往该目标IP的请求会固定发给该服务器
源地址散列调度(SH):根据源地址散列算法进行静态分配给固定的服务器资源
五、应用层
这一层涉及到路由协议,即请求的URL对应到哪个controller的代码去进行处理,通常有框架会去做路由协议处理,这时就会涉及到参数过滤、添加header或body、格式化返回结果等。
这一层的高可用通过集群实现,假设反向代理层是nginx,nginx.conf里能够配置多个web后端,并且nginx能够探测到多个后端的存活性。
自动故障转移:当web-server挂了的时候,nginx能够探测到,会自动的进行故障转移,将流量自动迁移到其他的web-server,整个过程由nginx自动完成,对调用方是透明的。
应用逻辑处理:
读:先读缓存,缓存要有一定时间,尽量使缓存保持高命中率;缓存没有则读数据库,读完根据实际需要去保存到缓存中;缓存可以压缩保存,减少空间
写:能用异步代替同步的,使用异步(从业务角度来看);能用同步的,尽量不用异步(从技术角度来看)。
假如处理逻辑复杂,而且时间长,再不影响业务感知的情况下用异步代替同步,将数据写入消息队列,再由脚本去读取执行,这样在高并发情况下提高速度,减少崩溃。后期数据不一致可进行脚本或人工修补。
假如处理短小,而且涉及事务操作,那可以使用同步,减少不一致等错误。
六、服务层
这一层通常是微服务使用到,相关的读写处理逻辑如上。同时为上一层提供连接池,减少资源、时间开销。
七、缓存、数据库
这里也提供连接池。
缓存层、数据库层的话需要主从同步,然后双主同步,一台对线上提供服务,另一台冗余以保证高可用。
mysql并发数 < redis并发数 < 消息队列并发数 < 服务器的TCP连接数
TCP连接数:理论无上限:2的32次方(ip数)×2的16次方(port数),但是在unix/linux下限制连接数的主要因素是内存和允许的文件描述符个数(每个tcp连接都要占用一定内存,每个socket就是一个文件描述符),另外1024以下的端口通常为保留端口。
对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万是没问题的。