Web服务基础介绍

                  Web服务基础介绍

                                                作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

 

一.正常情况下的单次web服务访问流程

博主推荐阅读:
  https://www.cnblogs.com/yinzhengjie/p/12014076.html

 

二.互联网发展历程回顾

1993年3月2日,中国科学院高能物理研究所租用AT&T公司的国际卫星信道建立的接入美国SLAC国家实验室的64K专线正式开通,成为我国连入Internet的第一根专线。

1995年马云开始创业并推出了一个web网站<<中国黄页>>,1999年创建阿里巴巴 www.alibabagroup.com, 2003年5月10日创立淘宝网,2004年12月,马云创立第三方网上支付平台支付宝(蚂蚁金服旗下,共有蚂蚁金服支付宝、余额宝、招财宝、蚂蚁聚宝、网商银行、蚂蚁花呗、芝麻信用等子业务板块。)。

2012年1月11日淘宝商城正式更名为“天猫”。 2014年9月19日里巴巴集团于纽约证券交易所正式挂牌上市。 2018年福布斯统计马云财富346亿美元。

2009年开始举办双十一购物狂欢节,以下是历年交易成交额:
  2009年双十一:5000万元;
  2010年双十一:9.36亿元;
  2011年双十一:33.6亿元;
  2012年双十一:191亿元;
  2013年双十一:350亿元;
  2014年双十一:571亿元;
  2015年双十一:912.17亿元;
  2016年双十一:1207亿元元;
  2017年双十一:1682.69亿元;
  2018年双十一:2135亿元;
  2019年双十一:2684亿元;

 

三.web服务介绍

  Netcraft公司于1994年底在英国成立,多年来一直致力于互联网市场以及在线安全方面的咨询服务,其中在国际上最具影响力的当属其针对网站服务器,域名解析/主机提供商,以及SSL市场所做的客观严谨的分析研究。

  Netcraft官网网站:https:
//news.netcraft.com/

 

四.Apache-早期的web服务端

Apache起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发,目前经历了两大版本分别是1.X和2.X,其可以通过编译安装实现特定的功能。httpd 2.2.x版本在2018年1月1日已经停止维护,目前使用httpd 2.4.x版本应该是主流的。

早期SVN+Apache还是火了一段时间的,后来Gitlab的出现基本上就后来居上了。 官方网站:http:
//www.apache.org

1>.Apache prefork模型

prefock模块概述:
  预派生模式,有一个主控制进程,然后生成多个子进程,使用select模型,默认最大并发1024(如果想要修改这个值则需要重新编译Linux内核),每个子进程有一个独立的线程响应用户请求,相对比较占用内存,但是比较稳定,可以设置最大和最小进程数,是最古老的一种模式,也是最稳定的模式,适用于访问量不是很大的场景。

优点:
  稳定

缺点:
  慢,占用资源,1024个进程不适用于高并发场景

 

2>.Apache woker模型

worker模型概述:
  一种多进程和多线程混合的模型,有一个控制进程,启动多个子进程,每个子进程里面包含固定的线程,使用线程程来处理请求,当线程不够使用的时候会再启动一个新的子进程,然后在进程里面再启动线程处理请求,由于其使用了线程处理请求,因此可以承受更高的并发。

优点:
  相比prefork占用的内存较少,可以同时处理更多的请求

缺点:
  使用keepalive的长连接方式,如一段时间某个用户打开网站后没有执行任何操作,某个线程会一直被占据,即使没有传输数据,也就是我们平常所说的空连接(event模型解决了该问题),也需要一直等待到超时才会被释放。如果过多的线程,被这样占据,也会导致在高并发场景下的无服务线程可用。(该问题在prefork模式下,同样会发生)

 

3>.Apache event模型

event模型概述:
  Apache中最新的模式,2012年发布的apache 2.4.X系列正式支持event模型,属于事件驱动模型(epoll),每个进程响应多个请求,在现在版本里的已经是稳定可用的模式。它和worker模式很像,最大的区别在于,它解决了keepalive场景下,长期被占用的线程的资源浪费问题(某些线程因为被keepalive,空挂在哪里等待,中间几乎没有请求过来,甚至等到超时)。event MPM中,会有一个专门的线程来管理这些keepalive类型的监听线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放。这样增强了高并发场景下的请求处理能力。 

优点:
  单线程响应多请求,占据更少的内存,高并发下表现更优秀,会有一个专门的线程来管理keep-alive类型的监听线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放。 

缺点:
  没有线程安全控制,指的是没有办法获取到监监听线程的状态,即如果某个子进程的监听线程挂掉了该子进程就不可用了(因为监听线程负责给该子进程的其它进程分配具体的请求任务),所以在apache里有一个配置项叫"MaxRequestsPerChild"(https://www.cnblogs.com/yinzhengjie/p/12001316.html),它可以规定了每个子进程处理多少个(比如50w)请求后自动消亡(资源回收),重新生成新的子进程来响应服务,这种方式在一定程度上就是为了解决监听线程不可用的问题。

 

五.Nginx-高性能的web服务端

Ninx是由1994年毕业于俄罗斯国立莫斯科鲍曼科技大学的同学为俄罗斯rambler.ru公司开发的,开发工作最早从2002年开始,第一次公开发布时间是2004年10月4日,版本号是0.1.0,官网地址 www.nginx.org

Nginx历经十几年的迭代更新(https:
//nginx.org/en/CHANGES), 目前功能已经非常完善且运行稳定,另外Nginx的版本分为开发版、稳定版和过期版,nginx以功能丰富著称,它即可以作为http服务器,也可以作为反向代理服务器或者邮件服务器,能够快速的响应静态网页的请求,支持FastCGI/SSL/Virtual Host/URLRwrite/Gzip/HTTP Basic Auth/http或者TCP的负载均衡(1.9版本以上且开启stream模块)等功能,并且支持第三方的功能扩展。
为什么使用Nginx: 天猫 淘宝 小米
163 京东新浪等一线互联网公司都在用Nginx或者进行二次开发
基于Nginx的访问流程如下:

  

六.用户访问体验统计

互联网存在用户速度体验的1-3-10原则,如下所示:
  1秒:
    最优
  1-3秒:
    较优
  3~10秒:
    比较慢
  10秒以上
    用户无法接受

用户放弃一个产品的代价很低,只是换一个URL而已。

全球最大搜索引擎 Google:慢500ms
=20%将放弃访问。

全球最大的电商零售网站 亚马逊:慢100ms=1%将放弃交易

 

七.性能影响

有很多研究都表明,性能对用户的行为有很大的影响: 
  79%的用户表示不太可能再次打开一个缓慢的网站
  47%的用户期望网页能在2秒钟以内加载 
  40%的用户表示如果加载时间超过三秒钟,就会放弃这个网站 
  页面加载时间延迟一秒可能导致转换损失7%,页面浏览量减少11% 
  8秒定律:用户访问一个网站时,如果等待网页打开的时间超过8秒,会有超过30%的用户放弃等待

1>.影响用户体验的几个因素

据说马云在刚开始创业在给客户演示时,打开一个国外网站花了两个多小时。他那个时候20世纪末网络带宽很小,很可能才64k左右的带宽访问网页速度较慢也是情理之中。

对于运维工程师来说,影响用户体验的几个因素仅供参考:
  客户端可能存在的因素:     客户端硬件配置:
      客户端不仅仅是打开网页卡,可能打开QQ,微信,淘宝等网站或应用都很卡,那就跟咱们运维没有关系了,不可能谁访问慢咱们运维给它换个新电脑吧~           客户端网络速率:
      客户端所在公司人口较多,但总出口带宽较小,从而导致客户端访问出口会出现丢包的情况。 也有可能公司网络带宽充足,但公司内外存在攻击,从而导致该公司无法正常访问web。     客户端与服务端距离:
      这个一般情况下一般是网络运维工程师去处理了,尤其是公网上跨运营商通信的业务,存在链路切换,运行商之间会配置静态路由,广域网的事情公司内部运维工程师很难处理的。

  服务端可能存在的因素:     服务端网络速率:
      服务端出口带宽较小,从而导致出口带宽被打满的时候无法正常提供服务,这种情况建议扩展带宽,但要先排除是否有攻击,这得咱们运维工程师结合之前的监控系统来做准确的判断。     服务端硬件配置:
      CPU,固态磁盘(或者1.5w转速的磁盘),硬件采购上建议大家不要犯这种低级错误,进来配置往高的推荐,若公司领导不采纳你的建议选择配置较低的家用电脑硬件配置来提供公司业务,这样出了问题可以直接把甩锅了,不然你就真的成为背锅侠喽。     服务端架构设计:
      这个会设计到业务请求流程,一般不是咱们运维工程师一个人的事情,因为软件设计不是运维干的,而是开发人员写的程序,需要和开发人员一起写上,是不是软件的架构师存在问题?千万别和开发刚起来哈,大家也都怪不容易的,有问题好好商量着来。     服务端应用程序工作模式:
      使用的web软件类型是httpd还是nginx?这些软件的优化配置你做了吗?这个和架构设计是有一定关联的。     服务端并发数量:
      这个并发设置是Linux系统调优(比如最大文件打开数量),内核调优,网络调优,数据库调优相关等常见参数默认值均需要修改,我这里就不啰嗦了,想必每个运维工程师对自己的业务都比较熟悉,每种业务可能调优的参数不太一样。     服务端响应文件大小及数量:
      尤其是比较大的响应文件,特别需要是否有用户恶意下载,比如你们公司官网有一个10k左右的图片文件,如果有人而已下载该图片,比如多个肉鸡并发10w下载量,瞬间就能打满接近1G的流量。     服务端I
/O压力:
      5900转/s(早期dell笔记本可能会用,现已经淘汰这种软件使用了,速度太慢)
      7200转/s(一般是家用笔记本硬盘使用)
      10000转/s(一般是用于存储服务使用)
      15000转/s(可以使用在web服务器上)
      固态硬盘(如果公司有钱的话,当然买固态是存储的最好的选择哟~)

2>.应用程序工作模式

httpd MPM(Multi-Processing Module,多进程处理模块)模式:
  prefork:进程模型,两级结构,主进程master负责生成子进程,每个子进程负责响应一个请求
  worker:线程模型,三级结构,主进程master负责生成子进程,每个子进程负责生成多个线程,每个线程响应一个请求
  event:线程模型,三级结构,主进程master负责生成子进程,每个子进程生成多个线程,每个线程响应一个请求,但是增加了一个监听线程,用于解决在设置了keep-alived场景下线程的空等待问题。

Nginx(Master
+Worker)模式:   主进程:
    接收请求并分配给指定的工作进程处理。   工作进程:
    分配子线程直接处理客户的请求
线程验证方式:   
cat /proc/PID/status   pstree -p PID
[root@node108.yinzhengjie.org.cn ~]# ss -ntl
State      Recv-Q Send-Q                                         Local Address:Port                                                        Peer Address:Port              
LISTEN     0      128                                                        *:22                                                                     *:*                  
LISTEN     0      80                                                        :::3306                                                                  :::*                  
LISTEN     0      128                                                       :::22                                                                    :::*                  
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# ps -ef | grep mysqld
root      3099     1  0 07:48 ?        00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data/mysql --pid-file=/data/mysql/node108.yinzhengjie.org.cn.pid
mysql     3391  3099  0 07:48 ?        00:00:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=/usr/local/mysql/lib/plugin --user
=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/data/mysql/node108.yinzhengjie.org.cn.pid --socket=/tmp/mysql.sock --port=3306
root      3720  3535  0 07:49 pts/0    00:00:00 grep --color=auto mysqld
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# cat /proc/3391/status | grep Threads
Threads:    32
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# cat /proc/3391/status | grep Threads          #查看线程方式1
[root@node108.yinzhengjie.org.cn ~]# ss -ntl
State      Recv-Q Send-Q                                         Local Address:Port                                                        Peer Address:Port              
LISTEN     0      128                                                        *:22                                                                     *:*                  
LISTEN     0      80                                                        :::3306                                                                  :::*                  
LISTEN     0      128                                                       :::22                                                                    :::*                  
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# ps -ef | grep mysqld
root      3099     1  0 07:48 ?        00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data/mysql --pid-file=/data/mysql/node108.yinzhengjie.org.cn.pid
mysql     3391  3099  0 07:48 ?        00:00:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=/usr/local/mysql/lib/plugin --user
=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/data/mysql/node108.yinzhengjie.org.cn.pid --socket=/tmp/mysql.sock --port=3306
root      3720  3535  0 07:49 pts/0    00:00:00 grep --color=auto mysqld
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# pstree -p 3391
mysqld(3391)─┬─{mysqld}(3455)
             ├─{mysqld}(3508)
             ├─{mysqld}(3513)
             ├─{mysqld}(3514)
             ├─{mysqld}(3515)
             ├─{mysqld}(3516)
             ├─{mysqld}(3517)
             ├─{mysqld}(3518)
             ├─{mysqld}(3519)
             ├─{mysqld}(3520)
             ├─{mysqld}(3521)
             ├─{mysqld}(3522)
             ├─{mysqld}(3523)
             ├─{mysqld}(3544)
             ├─{mysqld}(3545)
             ├─{mysqld}(3546)
             ├─{mysqld}(3547)
             ├─{mysqld}(3548)
             ├─{mysqld}(3549)
             ├─{mysqld}(3550)
             ├─{mysqld}(3551)
             ├─{mysqld}(3552)
             ├─{mysqld}(3553)
             ├─{mysqld}(3562)
             ├─{mysqld}(3563)
             ├─{mysqld}(3564)
             ├─{mysqld}(3565)
             ├─{mysqld}(3567)
             ├─{mysqld}(3569)
             ├─{mysqld}(3584)
             └─{mysqld}(3695)
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# pstree -p 3391                       #查看线程方式2,进程是用"()"表示,线程是用"{}"表示

3>.服务端I/O

[root@node108.yinzhengjie.org.cn ~]# top                        #注意,load average表示应用程序在1,5,15分钟之内调用了某个文件,而该文件还没有被返回的统计数量,如果这个值越大代表你的IO响应越慢,无论是的操作系统是24core还是48core,只要该数字大于5,那么你的web服务器其实就已经很慢了。
top - 08:03:04 up 14 min,  1 user,  load average: 0.00, 0.03, 0.04
Tasks: 100 total,   2 running,  98 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3880620 total,  3494716 free,   219372 used,   166532 buff/cache
KiB Swap:  4063228 total,  4063228 free,        0 used.  3449580 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                 
    1 root      20   0  125428   3776   2552 S  0.0  0.1   0:00.62 systemd                                                                                                 
    2 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kthreadd                                                                                                
    3 root      20   0       0      0      0 S  0.0  0.0   0:00.06 ksoftirqd/0                                                                                             
    5 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kworker/0:0H                                                                                            
    6 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kworker/u2:0                                                                                            
    7 root      rt   0       0      0      0 S  0.0  0.0   0:00.00 migration/0                                                                                             
    8 root      20   0       0      0      0 S  0.0  0.0   0:00.00 rcu_bh                                                                                                  
    9 root      20   0       0      0      0 R  0.0  0.0   0:00.31 rcu_sched                                                                                               
   10 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 lru-add-drain                                                                                           
   11 root      rt   0       0      0      0 S  0.0  0.0   0:00.00 watchdog/0                                                                                              
   13 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kdevtmpfs                                                                                               
   14 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 netns                                                                                                   
   15 root      20   0       0      0      0 S  0.0  0.0   0:00.00 khungtaskd                                                                                              
   16 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 writeback                                                                                               
   17 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kintegrityd                                                                                             
   18 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 bioset                                                                                                  
   19 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 bioset                                                                                                  
   20 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 bioset                                                                                                  
   21 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kblockd                                                                                                 
   22 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 md                                                                                                      
   23 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 edac-poller                                                                                             
   24 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 watchdogd                                                                                               
   25 root      20   0       0      0      0 S  0.0  0.0   0:00.67 kworker/0:1                                                                                             
   30 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kswapd0                                                                                                 
   31 root      25   5       0      0      0 S  0.0  0.0   0:00.00 ksmd                                                                                                    
   33 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 crypto                                                                                                  
   41 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kthrotld                                                                                                
   42 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kworker/u2:1                                                                                            
[root@node108.yinzhengjie.org.cn ~]# 
    
[root@node108.yinzhengjie.org.cn ~]# top                               #注意load average表示应用程序在1,5,15分钟之内调用了某个文件,而该文件还没有被返回的统计数量(我们也可以称之为I/O等待队列值),如果这个值越大代表你的I/O响应(I/O响应包括本地文件磁盘I/O和网络传输I/O)越慢,无论是的操作系统是24core还是48core,只要该数字大于5,那么你的web服务器其实就已经很慢了。生产环境中我们一般会监控该值的,一旦超过这个值就会报警哟~
I/O:
  在计算机中指Input/Output,分为磁盘IO和网络IO。
  网络通信就是网络协议栈到用户空间进程的IO就是网络IO。
  磁盘I/O是进程向内核发起系统调用,请求磁盘上的某个资源比如是文件或者是图片,然后内核通过相应的驱动程序将目标图片加载到内核的内存空间,加载完成之后把数据从内核内存再复制给进程内存,如果是比较大的数据也需要等待时间。

IOPS:
  (Input/Output Per Second)即每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一。IOPS是指单位时间内系统能处理的I/O请求数量,一般以每秒处理的I/O请求数量为单位,I/O请求通常为读或写数据操作请求。

一次完整的I/O:
  是用户空间的进程数据与内核空间的内核数据的报文的完整交换,但是由于内核空间与用户空间是严格隔离的,所以其数据交换过程中不能由用户空间的进程直接调用内核空间的内存数据,而是需要经历一次从内核空间中的内存数据copy到用户空间的进程内存当中,所以简单说I/O就是把数据从内核空间中的内存数据复制到用户空间中进程的内存当中。

每次IO,都要经由两个阶段:
  第一步:将数据从文件先加载至内核内存空间(缓冲区),等待数据准备完成,时间较长
  第二步:将数据从内核缓冲区复制到用户空间的进程的内存中,时间较短 

机械硬盘15000转/s和固态磁盘对比:
  机械磁盘iops一般在100左右,在固态盘上一般动不动就上万。
  固态盘没有磁头和内部盘片,它和内存一样直接使用颗粒存储,颗粒速度远大于盘片速度,因此性能非常强。15000转/s的机械硬盘顺序写入磁盘大概在180M/s左右,而固态硬盘大概在500M/s左右,如果是PCIE的接口固态硬盘可以达到1G/s左右哟。

 

八.系统I/O模型

同步/异步:
  关注的是消息通信机制,即在等待一件事情的处理结果时,被调用者是否提供完成通知。 
  同步(synchronous):
    调用者等待被调用者返回消息后才能继续执行,如果被调用者不提供消息返回则为同步,同步需要调用者主动询问事情是否处理完成。
    如进程发出请求调用后,等内核返回响应以后才继续下一个请求,即如果内核一直不返回数据,那么进程就一直等。
  异步(asynchronous):
    被调用者通过状态、通知或回调机制主动通知调用者被调用者的运行状态。
    如进程发出请求调用后,不等内核返回响应,接着处理下一个请求,Nginx是异步的。

阻塞
/非阻塞:   
  关注调用者在等待结果返回之前所处的状态   阻塞(blocking):     指IO操作需要彻底完成后才返回到用户空间,调用结果返回之前,调用者被挂起,干不了别的事情。   非阻塞(nonblocking):     指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成,最终的调用结果返回之前,调用者不会被挂起,可以去做别的事情。

 

九.网络I/O模型

1>.同步阻塞型IO模型(blocking IO)

同步阻塞IO模型概述:
  阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞,用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作,用户需要等待read将数据读取到cache后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。
  程序向内核发送IO请求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回,则进程将一直等待并不再接受新的请求,并由进程轮训查看IO是否完成,完成后进程将IO结果返回给Client,在IO没有返回期间进程不能接受其他客户的请求,而且是有进程自己去查看IO是否完成,这种方式简单,但是比较慢,用的比较少。
优点:
  程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用CPU资源

缺点:
  每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大,apache的preforck使用的是这种模式。

2>.同步非阻塞型I/O模型(nonblocking IO)

同步非阻塞概述:
    用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。即 “轮询”机制存在两个问题:如果有大量文件描述符都要等,那么就得一个一个的read。这会带来大量的Context Switch(read是系统调用,每调用一次就得在用户态和核心态切换一次)。轮询的时间不好把握。这里是要猜多久之后数据才能到。等待时间设的太长,程序响应延迟就过大;设的太短,就会造成过于频繁的重试,干耗CPU而已,是比较浪费CPU的方式,一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
    程序向内核发送请IO求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回IO结果,进程将不再等待,而且继续处理其他请求,但是仍然需要进程隔一段时间就要查看内核IO是否完成。

 

3>.IO多路复用型(IO multiplexing)

IO多路复用型(IO multiplexing)概述:
  IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。
  select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
  Apache prefork是此模式的select,work是poll模式。

4>.信号驱动式IO(signal-driven IO)

信号驱动IO:
    signal-driven I/O 用户进程可以通过sigaction系统调用注册一个信号处理程序,然后主程序可以继续向下执行,当有IO操作准备就绪时,由内核通知触发一个SIGIO信号处理程序执行,然后将用户进程所需要的数据从内核空间拷贝到用户空间 此模型的优势在于等待数据报到达期间进程不被阻塞。用户主程序可以继续执行,只要等待来自信号处理函数的通知。 
    优点:
    线程并没有在等待数据时被阻塞,内核直接返回调用接收信号,不影响进程继续处理其他请求因此可以提高资源的利用率 缺点:
    信号 I
/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知 异步阻塞: 程序进程向内核发送IO调用后,不用等待内核响应,可以继续接受其他请求,内核收到进程请求后进行的IO如果不能立即返回,就由内核等待结果,直到IO完成后内核再通知进程,apache event是此模式。

5>.异步(非阻塞) IO(asynchronous IO)

异步非阻塞:
    程序进程向内核发送IO调用后,不用等待内核响应,可以继续接受其他请求,内核调用的IO如果不能立即返回,内核会继续处理其他事物,直到IO完成后将结果通知给内核,内核在将IO完成的结果返回给进程,期间进程可以接受新的请求,内核也可以处理新的事物,因此相互不影响,可以实现较大的同时并实现较高的IO复用,因此异步非阻塞使用最多的一种通信方式。
    相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。 Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent(mysql在用)、libev、libuv。
    
异步过程如下图所示:

6>.网络IO模型对比

  这五种网络I/O模型中,越往后,阻塞越少,理论上效率也是最优前四种属于同步I/O,因为其中真正的I/O操作(recvfrom)将阻塞进程/线程,只有异步I/O模型才与POSIX定义的异步I/O相配。

7>.实现方式

[root@node108.yinzhengjie.org.cn ~]# ulimit -n               #这是我生产环境中建议设置的值
1048576
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# ulimit -n 1100000      #我们无法将文件打开数设置超过110万,如果业务的确需要超过110万的连接推荐使用lvs做负载均衡器。
-bash: ulimit: open files: cannot modify limit: Operation not permitted
[root@node108.yinzhengjie.org.cn ~]# 
[root@node108.yinzhengjie.org.cn ~]# 


温馨提示:
     有网友说ulimit设置的值到65535就够用了,估计很多运维小伙伴都笑而不语。
    
    为什么有人说设置文件打开数为65535就可以了呢?我猜测这个可能跟tcp或者upd报文头部有关,因为cpu或者udp支持16位存放相应端口号的,因此接收IP地址数量应该是最大65535。
    说实话生产环境中我们使用65535压根就不够用,一般情况下负载均衡器能够有10-20万连接数都很正常,因此我们建议将该值设置在100万以上,但我们上面演示 了设置成110万是无法设置的,这一点我们尤其需要注意哟~
    但如果你们公司业务的确连接数超过了100万,甚至更高,推荐使用lvs负载均衡,因为他们的工作原理是不同的,nginx是工作在7层的,而lvs是工作在4层的。
[root@node108.yinzhengjie.org.cn ~]# ulimit -n 1100000               #我们无法将文件打开数设置超过110万,如果业务的确需要超过110万的连接推荐使用lvs做负载均衡器。
  Nginx支持在多种不同的操作系统实现不同的事件驱动模型,但是其在不同的操作系统甚至是不同的系统版本上面的实现方式不尽相同,主要有以下实现方式:
    (1)select:
      select库是在linux和windows平台都基本支持的事件驱动模型库,并且在接口的定义也基本相同,只是部分参数的含义略有差异,最大并发限制1024,是最早期的事件驱动模型。
    (2)poll:
      在Linux 的基本驱动模型,windows不支持此驱动模型,是select的升级版,取消了最大的并发限制,在编译nginx的时候可以使用--with-poll_module和--without-poll_module这两个指定是否编译select库。
    (3)epoll:
      epoll是库是Nginx服务器支持的最高性能的事件驱动库之一,是公认的非常优秀的事件驱动模型,它和select和poll有很大的区别,epoll是poll的升级版,但是与poll的效率有很大的区别.epoll的处理方式是创建一个待处理的事件列表,然后把这个列表发给内核,返回的时候在去轮训检查这个表,以判断事件是否发生,epoll支持一个进程打开的最大事件描述符的上限是系统可以打开的文件的最大数,同时epoll库的IO效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行操作。
    (4)rtsig:
      不是一个常用事件驱动,最大队列1024,不是很常用
    (5)kqueue:
      用于支持BSD系列平台的高校事件驱动模型,主要用在FreeBSD 4.1及以上版本、OpenBSD 2.0级以上版本,NetBSD级以上版本及Mac OS X 平台上,该模型也是poll库的变种,因此和epoll没有本质上的区别,都是通过避免轮训操作提供效率。
    (6)/dev/poll:
      用于支持unix衍生平台的高效事件驱动模型,主要在Solaris 平台、HP/UX,该模型是sun公司在开发Solaris系列平台的时候提出的用于完成事件驱动机制的方案,它使用了虚拟的/dev/poll设备,开发人员将要见识的文件描述符加入这个设备,然后通过ioctl()调用来获取事件通知,因此运行在以上系列平台的时候请使用/dev/poll事件驱动机制。
    (7)eventport:
      该方案也是sun公司在开发Solaris的时候提出的事件驱动库,只是Solaris 10以上的版本,该驱动库看防止内核崩溃等情况的发生。
    (8)Iocp:
      Windows系统上的实现方式,对应第5种(异步I/O)模型。

8>.常用模型汇总

9>.常用模型对比

水平触发--单次通知
边缘触发--多次通知

Select:
  POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理
缺点
  单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义FD_SETSIZE,再重新编译内核实现,但是这样也会造成效率的降低
  单个进程可监视的fd数量被限制,默认是1024,修改此值需要重新编译内核对socket是线性扫描,即采用轮询的方法,效率较低   
select 采取了内存拷贝方法来实现内核将 FD 消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大 poll:   本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态
  其没有最大连接数的限制,原因是它是基于链表来
  存储的大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义
  poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd
  select是边缘触发即只通知一次 epoll:   在Linux
2.6内核中提出的select和poll的增强版本   支持水平触发LT和边缘触发ET,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次   使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知   优点:     没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口),具体查看/proc/sys/fs/file-max,此值和系统内存大小相关哟~   效率提升:
    非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即   epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关   内存拷贝,利用mmap(Memory Mapping)加速与内核空间的消息传递;即epoll使用mmap减少复制开销

 

10>.MMAP介绍(Memory Mapping内存拷贝,加速与内核空间的消息传递)

    mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问。

    传统方式copy数据如下图所示:
    从磁盘拷贝到内核空间中,如果有多个进程去访问就会直接把数据分别拷贝到多个进程对应的用户空间里。有点像硬链接。

  mmap方式copy数据如下所示:
    从磁盘将数据拷贝到内核空间中,如果有多个进程访问的话不会在像传统方式一样进行拷贝到对应的用户空间,而是各个用户空间直接指向了内核空间的存储位置,即并没有拷贝过程。有点像软连接。

 

posted @ 2019-12-12 00:24  尹正杰  阅读(822)  评论(0编辑  收藏  举报