在epoll模型出现之前,java使用的模型有java bio模型和linux select模型。
模型如下:
当client和server传输数据时,需要client和server之间建立长连接,然后用socket.write向TCP/IP缓冲区中写入数据,client端只有当write将所有的数据写入TCP/IP缓冲区后才会返回。在这种模型下如果因为网络原因,数据传输速度慢,write就要等到缓冲区中的数据传输过去之后才能继续向缓冲区中写入数据,因此client端的返回也会变得缓慢(通俗的说就是阻塞)。
Linux select模型如下:
在Linux select模型下,服务端会先阻塞自己,然后监听100个客户端连接(假设有100个),当有连接状态发生变化时,server会唤醒自己然后遍历100个连接,找出其中发生改变的一个或者多个连接然后执行read操作。
这种模型相较于java bio的优点是:当server被唤醒时,一定是有连接的状态发生了改变,意味着缓冲区中有数据可以读或者有空间可以写,因此唤醒后不用等待。缺点也很明显,假设有1000个连接被监听,那每次唤醒都要遍历1000次,这样是十分耗时的。
epoll模型如下:
同样假设有100个链接被监听,不同的是epoll模型下会给每个连接设置回调函数,当连接发生变化时,会直接将自己唤醒,并执行回调函数。这样就不用服务端自己去遍历所有的被监听的连接了,节省了时间。
模型如下:
当我们启动Nginx时,会先启动一个master进程,该进程根据配置文件生成一定数量的子进程worker,由于是子进程,master和各个子进程之间共享内存。此后master只接收管理员的信号和监听网络接口,当网络端口中有连接请求时,根据epoll模型,master会唤醒自己并执行回调函数,将建立连接的操作交给worker执行,各个worker回去争抢一个accept锁,由抢到该锁的worker执行建立连接的操作。同时worker也是基于epoll模型,当有连接发生变化时会唤醒自己并执行read或者write操作。
由于worker是master的子进程,因此master可以享有worker的内存空间。当worker进程死亡时,master会获取worker进程的全部数据然后重新生成一个worker进程并回复请求。
Nginx的无缝重启操作也是基于这个模型,重启Nginx时master不重启,只重启worker进程。
协程是线程的内存模型,协程切换的开销非常小,因为协程切换时不用切换cpu,只用切换内存。
Nginx的worker是单线程工作的,worker上有多个连接,当连接发生阻塞时,worker只需要切换协程,而协程的切换开销比线程小得多,因此即使worker是单线程它也能高效的处理多个连接。