问题

说一下你知道的nginx和apache的区别

题库小秘书

发表于 2019-03-21 16:24:24

由于web服务器是一对多的关系,通常完成并行处理的方式有多进程、多线程、异步三种方式。

多进程:多进程就是每个进程对应一个连接来处理请求,进程独立响应自己的请求,一个进程挂了,并不会影响到其他的请求;而且设计简单,不会产生内存泄漏等问题,因此进程比较稳定。但是进程在创建的时候一般是fork机制,会存在内存复制的问题,另外在高并发的情况下,上下文切换将很频繁,这样将消耗很多的性能和时间。早期的apache使用的prework模型就多进程方式,但是apache会预先创建几个进程,等待用户的响应,请求完毕,进程也不会结束。因此性能上有优化很多。

多线程:每个线程响应一个请求,由于线程之间共享进程的数据,所以线程的开销较小,性能就会提高。由于线程管理需要程序自己申请和释放内存,所以当存在内存等问题时,可能会运行很长时间才会暴露问题,所以在一定程度上还不是很稳定。apache的worker模式就是这种方式

异步的方式:nginx的epoll,apache的event也支持,不多说了

Nginx的IO模型是基于事件驱动的,使得应用程序在多个IO句柄间快速切换,实现所谓的异步IO。事件驱动服务器,最适合做的就是IO密集型工作,如反向代理,它在客户端与WEB服务器之间起一个数据中转作用,纯粹是IO操作,自身并不涉及到复杂计算。反向代理用事件驱动来做,显然更好,一个工作进程就可以run了,没有进程、线程管理的开销,CPU、内存消耗都小。

Apache这类应用服务器,一般要跑具体的业务应用,如科学计算、图形图像等。它们很可能是CPU密集型的服务,事件驱动并不合适。例如一个计算耗时2秒,那么这2秒就是完全阻塞的,什么event都没用。想想MySQL如果改成事件驱动会怎么样,一个大型的join或sort就会阻塞住所有客户端。这个时候多进程或线程就体现出优势,每个进程各干各的事,互不阻塞和干扰。当然,现代CPU越来越快,单个计算阻塞的时间可能很小,但只要有阻塞,事件编程就毫无优势。所以进程、线程这类技术,并不会消失,而是与事件机制相辅相成,长期存在。

总的说来,事件驱动适合于IO密集型服务,多进程或线程适合于CPU密集型服务

其实也就是说nginx比较适合做前端代理,或者处理静态文件(尤其高并发情况下),而apache适合做后端的应用服务器,功能强大[php, rewrite…],稳定性高。

题库小秘书

发表于 2019-03-29 12:04:30

可以从进程模型的角度来讲一讲区别

假设有 100 万用户同时与一个进程保持着 TCP 连接,而每一时刻只有几十个或几百个 TCP 连接时活跃的(接收到 TCP
包),也就是说,在每一时刻,进程只需要处理这 100 万连接中的一小部分连接。

select 和 poll 的做法是:进程每次收集事件的连接(其实这 100 万连接中的大部分都是没有事件发生的)都把这 100 万连
接的套接字传给操作系统(这首先就是用户态内存到内核态内存的大量复制),而由操作系统内核寻找这些连接上有没有未
处理的事件,将会是巨大的资源浪费,因此 select 和 poll 最多只能处理几千个并发连接。

而 epoll 则是在 Linux 内核中申请了一个简易的文件系统,把原先的一个 select 或者 poll 调用分成了 3 个部分:

  • 调用 epoll_create 建立 1 个 epoll 对象(在 epoll 文件系统中给这个句柄分配资源);
  • 调用 epoll_ctl 向 epoll 对象中添加这 100 万个连接的套接字;
  • 调用 epoll_wait 收集发生事件的连接。
Linux 2.6.35内核对 epoll 的实现

当某一个进程调用 epoll_create 方法时,Linux 内核会创建一个 eventpoll 结构体:

struct eventpoll {
    ...
    /* 红黑树的根节点,这棵树中存储着所有添加到 epoll 中的事件,也就是这个 epoll 监控的事件 */
    struct rb_root rbr;
    
    /* 双向链表 rdllist 保存着将要通过 epoll_wait 返回给用户的、满足条件的事件 */
    struct list_head rdllist;
};

每一个 epoll 对象都有一个独立的 eventpoll 结构体,这个结构体会在内核空间中创造独立的内存,用于存储使用
epoll_ctl 方法向 epoll 对象中添加进来的事件。这些事件都会挂到 rbr 红黑树中,这样,重复添加的事件就可以通过红黑
树而高效地识别出来。

所有添加到 epoll 中的事件都会与设备(如网卡)驱动程序建立回调关系,即相应的事件发生时会调用 ep_poll_callback 回调
方法,它会把这样的事件放到上面的 rdllist 双向链表中。

在 epoll 中,对于每一个事件都会建立一个 epitem 结构体:

struct epitem {
    ...
    /* 红黑树节点 */
    struct rb_node rbn;
    
    /* 双向链表节点 */
    struct list_head rdllink;
    
    /* 事件句柄等信息 */
    struct epoll_filefd ffd;
    
    /* 指向所属的 eventpoll 对象 */
    struct eventpoll *ep;
    
    /* 期待的事件类型 */
    struct epoll_event event;
    ...
};

这里包含每一个事件对应着的信息。
当调用 epoll_wait 检查是否有事件的连接时,只是检查 eventpoll 对象中的 rdllist 双向链表是否有 epitem 元素,如果
rdllist 链表不为空,则把这里的事件复制到用户态内存中,同时将事件数量返回给用户。

具体可以参考一篇博客:

https://www.cnblogs.com/jimodetiantang/p/8952141.html