taobao / nginx-book

Nginx开发从入门到精通
6.91k stars 2.03k forks source link

nginx是怎样处理静态文件的 #148

Closed Alexoner closed 10 years ago

Alexoner commented 10 years ago

nginx处理每一个请求是先epoll确定状态为ready的socket,那么,如果说用户请求一个很大的文件,比如有1G多的的MP4视频,那么服务器会有很长的时间给这个socket传输数据。但是这时候nginx并没有阻塞,如果没有用线程的话,请问这是怎么实现的呢?还望大牛们不佞赐教,指点一二。

chobits commented 10 years ago

@Alexoner 多线程和阻塞没有直接关系。 非阻塞是socket fd的一个特性,在linux上可以通过系统调用ioctl()或者fcntl()来设置这个特性。 nginx中对此做了封装,请参考函数ngx_nonblocking()

aCayF commented 10 years ago

@chobits @jinglong 代码看的不多,如果我的看法有什么错误之处,还望前辈能及时指出,thx 我的看法是这个socket确实是被用来传输这个1G多的MP4视频文件了,但是用于发送的socket缓冲区是有大小的,当缓冲区被写满时,下一次对socket缓冲区的写操作必将导致该socket阻塞住当前进程,所以由ngx_nonblocking()设置的socket会防止这个阻塞发生,立即返回NGX_AGIAN,然后nginx将该fd加入到epoll中(等待下次该fd上的可写事件到来),接着nginx调用其他先前从epoll中取出的fd,处理该fd上的事件,这样就实现了等同于多线程调度的效果

Alexoner commented 10 years ago

谢谢解答。我的意思是这样的,之前我的理解是:nginx服务器接受到请求之后,会给客户端发送数据,如果不用多线程的话,cpu就会用来传输数据。然后我没理解为什么这里还可以接受新的请求,因为数据没有传输完。后来我再看了会儿,发现有用到aio,所以我想nginx的传输数据是不会阻塞的,因为是用的异步I/O传输数据。也就是说nginx不阻塞就是由于epoll和异步I/O实现的。是这样的吗? @chobits

2013/10/28 Lice Pan notifications@github.com

@chobits https://github.com/chobits @jinglonghttps://github.com/jinglong代码看的不多,如果我的看法有什么错误之处,还望前辈能及时指出,thx

我的看法是这个socket确实是被用来传输这个1G多的MP4视频文件了,但是用于发送的socket缓冲区是有大小的,当缓冲区被写满时,下一次对socket缓冲区的写操作必将导致该socket阻塞住当前进程,所以由ngx_nonblocking()设置的socket会防止这个阻塞发生,立即返回NGX_AGIAN,然后nginx将该fd加入到epoll中(等待下次该fd上的可写事件到来),接着nginx调用其他先前从epoll中取出的fd,处理该fd上的事件,这样就实现了等同于多线程调度的效果

— Reply to this email directly or view it on GitHubhttps://github.com/taobao/nginx-book/issues/148#issuecomment-27207352 .

jinglong commented 10 years ago

Lice Pan说的是正确的。

在 2013年10月28日下午10:24,Alexoner notifications@github.com写道:

谢谢解答。我的意思是这样的,之前我的理解是:nginx服务器接受到请求之后,会给客户端发送数据,如果不用多线程的话,cpu就会用来传输数据。然后我没理解为什么这里还可以接受新的请求,因为数据没有传输完。后来我再看了会儿,发现有用到aio,所以我想nginx的传输数据是不会阻塞的,因为是用的异步I/O传输数据。也就是说nginx不阻塞就是由于epoll和异步I/O实现的。是这样的吗?

2013/10/28 Lice Pan notifications@github.com

@chobits https://github.com/chobits @jinglong< https://github.com/jinglong>代码看的不多,如果我的看法有什么错误之处,还望前辈能及时指出,thx

我的看法是这个socket确实是被用来传输这个1G多的MP4视频文件了,但是用于发送的socket缓冲区是有大小的,当缓冲区被写满时,下一次对socket缓冲区的写操作必将导致该socket阻塞住当前进程,所以由ngx_nonblocking()设置的socket会防止这个阻塞发生,立即返回NGX_AGIAN,然后nginx将该fd加入到epoll中(等待下次该fd上的可写事件到来),接着nginx调用其他先前从epoll中取出的fd,处理该fd上的事件,这样就实现了等同于多线程调度的效果

— Reply to this email directly or view it on GitHub< https://github.com/taobao/nginx-book/issues/148#issuecomment-27207352> .

— Reply to this email directly or view it on GitHubhttps://github.com/taobao/nginx-book/issues/148#issuecomment-27215415 .

Jinglong Software Engineer Server Platforms Team at Taobao

Alexoner commented 10 years ago

是这样啊,谢谢。

2013/10/29 pengqi notifications@github.com

Lice Pan说的是正确的。

在 2013年10月28日下午10:24,Alexoner notifications@github.com写道:

谢谢解答。我的意思是这样的,之前我的理解是:nginx服务器接受到请求之后,会给客户端发送数据,如果不用多线程的话,cpu就会用来传输数据。然后我没理解为什么这里还可以接受新的请求,因为数据没有传输完。后来我再看了会儿,发现有用到aio,所以我想nginx的传输数据是不会阻塞的,因为是用的异步I/O传输数据。也就是说nginx不阻塞就是由于epoll和异步I/O实现的。是这样的吗?

2013/10/28 Lice Pan notifications@github.com

@chobits https://github.com/chobits @jinglong< https://github.com/jinglong>代码看的不多,如果我的看法有什么错误之处,还望前辈能及时指出,thx

我的看法是这个socket确实是被用来传输这个1G多的MP4视频文件了,但是用于发送的socket缓冲区是有大小的,当缓冲区被写满时,下一次对socket缓冲区的写操作必将导致该socket阻塞住当前进程,所以由ngx_nonblocking()设置的socket会防止这个阻塞发生,立即返回NGX_AGIAN,然后nginx将该fd加入到epoll中(等待下次该fd上的可写事件到来),接着nginx调用其他先前从epoll中取出的fd,处理该fd上的事件,这样就实现了等同于多线程调度的效果

— Reply to this email directly or view it on GitHub< https://github.com/taobao/nginx-book/issues/148#issuecomment-27207352> .

— Reply to this email directly or view it on GitHub< https://github.com/taobao/nginx-book/issues/148#issuecomment-27215415> .

Jinglong Software Engineer Server Platforms Team at Taobao

— Reply to this email directly or view it on GitHubhttps://github.com/taobao/nginx-book/issues/148#issuecomment-27272422 .

aCayF commented 10 years ago

aio那部分代码没怎么看,我也不能发表什么意见:)

Alexoner commented 10 years ago

aio这部分网上有人说不是为socket设计的,unix network programming 中说道,“We are not certain, for example, if systems will support it for sockets.“,所以,应该是对于磁盘的I/O读写效率提升。具体在socket中的应用,我自己去写的试试看。

On Tue, Oct 29, 2013 at 10:17 AM, Lice Pan notifications@github.com wrote:

aio那部分代码没怎么看,我也不能发表什么意见:)

— Reply to this email directly or view it on GitHubhttps://github.com/taobao/nginx-book/issues/148#issuecomment-27273796 .

chobits commented 10 years ago

@aCayF 你说的是对的。(发送细节在nginx-src/os/unix/ngx_send.c函数ngx_unix_send()里面,有处理NGX_AGAIN的情况。)

@Alexoner AIO也是一种异步模型。 但是注意AIO和epoll等在nginx里面都是事件模型的实现,两者使用上只能选择其一。分别在 nginx-src/event/modules/ngx_aio_module.c 和 nginx-src/event/modules/ngx_epoll_module.c,另外对于不同系统,还有一些其他事件模型的实现,比如bsd就是kqueue,还有unix-like os通用的select等。

举一个简单的例子,在发送response(1G文件)时候,用户又向nginx发送一个请求。假如只启动一个nginx worker进程。nginx首先发送response,然后直到写缓冲区写满,发送函数(send/write)等立即返回-1 && errno=NGX_AGAIN, 此时nginx返回事件中心继续监听(epoll_wait)下一个事件。这时如果用户发送一个请求过来,nginx即可捕获这个读事件并且处理,处理完再返回事件中心监听事件。此时可能写缓冲区的数据已经被发送完了,内核会上报一个写事件,nginx就会捕捉到写事件,然后继续处理之前未发送完的文件。 这里如果_不设置nonblocking_的话,当写缓冲区写满的时候,发送函数(send/write)不会立即返回,而是被阻塞住,这时nginx只能等待网卡把数据发送出去释放写缓冲区。这里浪费的时间其实可以用来处理用户发送过来的请求。因为cpu和网卡可以各自独立工作。

Alexoner commented 10 years ago

懂了很多,谢谢大家。nginx里面有一个event这个大模块,aio,epoll等等都是底层的实现,通过诸如ngx_event_actions这样的变量及函数指针在编译的时候选择具体实现。epoll_wait得到的struct epoll_event结构里面的数据指向ngx_connetcion_t,这个数据结构里面有个成员sent,用于记录上次往socket里面写的数据量,便于下次继续写。ngx_unix_send这个函数就是需要传入ngx_connection_t这个结构。所有与×nix操作系统有关的底层文件操作的api都封装后传到了ngx_io这个全局变量。而event的底层实现是通过函数指针赋值给了ngx_event_actions.发现看源码没有cscope真是看不成。各种模块都通过函数指针和全局变量封装起来,在配指文件里选择具体实现和其他功能。现在正在自己写epoll实现的并发服务器,发现运行结果和send函数里传入的buf 大小有很大关系,当buf的长度小于TCP的buffer长度时,可能不会阻塞,这与send写入数据的速度和网卡速度有关。运行还发现其他很多奇怪的问题,估计得研究一段时间了。

2013/10/29 Xiaochen Wang notifications@github.com

@aCayF https://github.com/aCayF你说的是对的。(发送细节在nginx-src/os/unix/ngx_send.c函数ngx_unix_send()里面,有处理NGX_AGAIN的情况。)

@Alexoner https://github.com/Alexoner AIO也是一种异步模型。 但是注意AIO和epoll等在nginx里面都是事件模型的实现,两者使用上只能选择其一。分别在 nginx-src/event/modules/ngx_aio_module.c 和 nginx-src/event/modules/ngx_epoll_module.c,另外对于不同系统,还有一些其他事件模型的实现,比如bsd就是kqueue,还有unix-like os通用的select等。

举一个简单的例子,在发送response(1G文件)时候,用户又向nginx发送一个请求。假如只启动一个nginx worker进程。nginx首先发送response,然后直到写缓冲区写满,发送函数(send/write)等立即返回-1 && errno=NGX_AGAIN, 此时nginx返回事件中心继续监听(epoll_wait)下一个事件。这时如果用户发送一个请求过来,nginx即可捕获这个读事件并且处理,处理完再返回事件中心监听事件。此时可能写缓冲区的数据已经被发送完了,内核会上报一个写事件,nginx就会捕捉到写事件,然后继续处理之前未发送完的文件。 这里如果不设置nonblocking 的话,当写缓冲区写满的时候,发送函数(send/write)不会立即返回,而是被阻塞住,这时nginx只能等待网卡把数据发送出去释放写缓冲区。这里浪费的时间其实可以用来处理用户发送过来的请求。因为cpu和网卡可以各自独立工作。

— Reply to this email directly or view it on GitHubhttps://github.com/taobao/nginx-book/issues/148#issuecomment-27283733 .