ホームページ  >  に質問  >  本文

linux - IO阻塞非阻塞、同步异步有什么区别?

IO流程是怎样的?nginx、apache是基于哪种IO,为什么?

PHPzPHPz2742日前806

全員に返信(1)返信します

  • 天蓬老师

    天蓬老师2017-04-17 13:36:25

    可以用 sudo strace -p PID 查看编号的PID的进程的系统调用.
    nginx master: rt_sigsuspend
    nginx worker: epoll_wait
    php-fpm master: epoll_wait (events.mechanism = epoll)
    php-fpm worker: accept poll
    可见Nginx工作进程进行了epoll_wait系统调用,epoll是Linux内核提供的异步网络IO编程接口.

    http://liuxun.org/blog/nginx-gong-zuo-jin-cheng-mo-xing/
    Nginx并没有像PHP-FPM那样采用master进程来分发连接,这个工作由操作系统内核机制完成,
    所以可能会导致惊群现象,也就是当listen_fd有新的accept()请求过来,操作系统会唤醒所有子进程.
    Nginx通过全局互斥锁来避免惊群(accept_mutex on),每个工作进程在epoll_wait()之前先去申请锁,申请到则继续处理,获取不到则等待,
    并设置了一个负载均衡的算法(当某一个工作进程的任务量达到总设置量的7/8时,则不会再尝试去申请锁)来均衡各个进程的任务量.
    http://nginx.org/en/docs/ngx_core_module.html#accept_mutex

    Nginx解决惊群的新方法:使用内核提供的Socket ReusePort功能
    NGINX 1.9.1 支持socket分片:
    http://nglua.com/docs/sharding.html
    http://nginx.com/blog/socket-sharding-nginx-release-1-9-1/
    NGINX1.9.1支持socket的SO_REUSEPORT选项,这个选项在许多操作系统的新版本有效,包括DragonFly BSD和Linux(3.9+内核).
    这个选项允许多个socket监听同一个IP地址和端口的组合.内核负载均衡这些进来的sockets连接,将这些socket有效的分片.
    当SO_REUSEPORT选项没开启时,连接进来时监听socket默认会通知某个进程.
    如果accept_mutex off这个指令,此时会唤醒所有的工作进程,它们将为了得到它产生竞争,这就是所谓的惊群现象.
    如果使用epoll且不用锁(accept_mutex off),当监听端口有读操作时,是会产生惊群现象的.
    启用SO_REUSEPORT选项后,每个进程将有个独立的监听socket.内核决定哪个是有效的socket(进程)得到这个连接.
    这样做降低了延迟并提高了工作进程的性能,它也意味着工作进程在准备处理它们前被赋予了新的连接.
    开启SO_REUSEPORT支持,只需将新的参数reuseport加到listen指令的后面:
    listen 80 reuseport;
    包含这个reuseport参数后将禁用这个监听socket的accept_mutex,因为锁变得多余了.
    基准测试:NGINX 1.9.1启用reuseport完美解决惊群后,每秒处理的请求数提升了2到3倍,同时降低了延迟和stdev指标.
    附:淘宝的Tengine据说很早就加入了socket分片功能.

    上面说的是网络IO的异步,下面说磁盘IO的异步.

    在高性能的服务器编程中,I/O模型理所当然的是重中之重,需要谨慎选型.
    对于网络套接字,我们可以采用epoll的方式来轮询,尽管epoll也有一些缺陷,但总体来说还是很高效的,尤其来大量套接字的场景下.
    但对于Regular File来说,是不能够用的.采用poll/epoll,即O_NOBLOCK方式对于传统文件句柄是无效的.
    也就是说我们的open,read,mkdir之类的Regular File操作必定会导致阻塞.
    在多线程,多进程模型中,可以选择以同步阻塞的方式来进行IO操作,任务调度由操作系统来保证公平性.

    NGINX从1.7.11试验性引入线程池,特定场景性能提升9倍.
    https://www.nginx.com/blog/thread-pools-boost-performance-9x/
    测试服务器有2个Intel Xeon E5645处理器(共计:12核,24超线程)和10-Gbps的网络接口.
    磁盘子系统是由4块西部数据WD1003FBYX磁盘组成的RAID10阵列.
    操作系统是Ubuntu Server 14.04.1 LTS.

    thread_pool default threads=32 max_queue=65536;
    aio threads=default;
    这里定义了一个名为default,包含32个线程,任务队列最多支持65536个请求的线程池.
    如果任务队列过载,NGINX将输出如下错误日志并拒绝请求:
    thread pool "NAME" queue overflow: N tasks waiting

    有了这个线程池,NGINX有可能没有任何性能损失地卸载任何长期阻塞的操作.
    许多流行的库仍然没有提供异步非阻塞接口,此前,这使得它们无法与NGINX兼容.
    我们可以花大量的时间和资源,去开发我们自己的无阻塞原型库,但这么做始终都是值得的吗?
    现在,有了线程池,我们可以相对容易地使用这些库,而不会影响这些模块的性能.
    FreeBSD已经有足够好的异步接口来读取文件,这时候不需要使用线程池.
    Linux缺乏这样的机制,所以对于一些不适合缓存在虚拟内存的文件(大文件),可以卸载读操作到AIO线程池,避免阻塞工作进程.
    我们如果可以改进卸载读操作到线程池,将会非常有意义.
    我们只需要知道所需的文件数据是否在内存中,只有不在内存中时,读操作才应该卸载到一个单独的线程中.

    Linux内核提供的异步文件操作接口AIO,需要DirectIO,无法利用内存的Page Cache,
    这种奇怪的实现可能是Oracle/IBM专门为数据库应用设计的,MySQL就用到了AIO.
    AIO+DirectIO绕过了虚拟文件系统VFS高速缓存固然让大型数据库系统如Oracle,MySQL(InnoDB)非常高兴,
    因为Oracle,MySQL都有自己的缓存系统,所以不需要操作系统的Page Cache缓存,DirectIO能避免数据就被缓存两次而浪费内存.
    也就是说,如果你想让自己的程序通过AIO实现异步文件IO,那么你最好建立自己的内存缓存系统,而不是依赖内核.

    最后,Linux上Apache 2.4系列默认的event MPM,是一个多进程,每个工作进程包含多个线程的epoll事件驱动的MPM.
    Apache 2.2系列的prefork MPM是纯粹的多进程架构,没有引入多线程,也没有使用内核的epoll特性,估计主要是为了方便移植.
    IBM AIX上面的IBM HTTP Server貌似就是基于Apache改造的.

    返事
    0
  • キャンセル返事