极客专栏《Nginx核心知识100讲》44小节、45小节的笔记
44 | Listen指令的用法
一个请求在进来之前,我们需要监听端口,以使得nginx跟客户端建立起一个TCP连接,建立端口的指令时listen,是放在server配置块下。通过监听的端口或者地址,可以决定有哪些匹配上TCP四元组的监听的地址的连接对应的server块它的相关指令取处理相应的请求。
Listen指令

listen 127.0.0.1 这样会默认使用80端口。
listen :8000 跟listen *:8000 效果是一样的。什么情况下*:8000
会不同呢?比如:对指定的地址给做一个全部匹配。
listen localhost:8000 bind 这是在老的linux系统上对*8000做绑定。其他端口需要显示做绑定。最新的linux系统不会有这样的问题了。
45 | 处理HTTP请求头部的流程
处理流程
在http模块开始处理用户请求之前,首先需要nginx的框架先对nginx建立好连接,接收用户发来的http的line,比如:方法、url等。然后在接收到所有的header,根据header信息决定选用哪些配置块,才能决定让http配置块处理哪些请求。先看下nginx的框架是怎样建立连接以及接受http请求的。

操作系统内核:三次握手建立连接。很多worker进程,每一个worker进程可能都监听了80跟443端口。操作系统根据它的负载均衡算法选中一个worker进程。
事件模块:epoll_wait 方法 返回到刚建立好的句柄。发来的ack产生一个读事件,根据读事件找到了原理是监听的80或者443端口,然后调用accept方法,分配连接内存池(内存池分为连接内存池跟请求内存池)。connect_pool_size是连接内存池的分配大小。也就是nginx为这个连接分配了512字节的内存池。这个内存池如何使用呢?
http模块:分配完内存建立好连接之后。所有的http模块
开始从事件模块
手中,接入请求的处理过程。http模块在启动时候就定义了一个回调方法ngx_http_init_connection。当accept一个新连接到时候这个方法就会被回调执行。新建立的读事件添加到epoll中(通过epoll_ctl函数),添加超时定时器,如果60秒中没有收到请求就超时了,也就是我们经常会看到的client_header_timeout:60s
。
上面三个流程走完,nginx的事件模块可能切到http模块处理了。
当用户把一个get或post请求发来的时候,发来DATA,TCP层(操作系统内核层)回一个ACK。同时时间模块的epoll_wait又拿到这个请求了。这个请求的回调方法ngx_http_wait_request_handler(它是在上面ngx_init_connection那步设置的)。收到请求后把内核中的DATA读到用户的内核态中 。要读到内核态中要分配内存。这段内存要分配多大呢?从哪里分配的呢?是从连接内存池分配的,连接内存池初始分配了512字节,这个时候从内存中再分配1k(client_header_buffer_size),因为内存是可以扩展的。这个1k可以修改,也不是越大越好,因为只要用户有一个字节发来,nginxwoker进程也会分配1k。这么一看分配很大并不合适。但如果发来的url或者header非常大,超过了1k呢?接下来看看超过1k有什么变化。
接收请求HTTP模块

接收URI
分配完1k之后,我们收到了小于等于1k的请求内容。处理请求跟处理连接是不一样的。处理连接只要把它收到nginx内存中。但处理请求时,需要做大量的上下文分析,分析协议,分析每一个header。这个时候需要分配一个请求内存池
。请求内存池默认分配4k(request_pool_size)。是connect_pool_size的8倍大小。为什么是8倍,因为请求的上下文涉及到业务。通常4k是一个比较合适的数值。如果分配的很小的话。需要请求内存池不断在的扩充。当分配内存的次数变多时,性能会下降。request_pool_size要不要改,要根据业务状况来。理解了后备这个内存池如何去用到就可以根据业务情况去决定是否修改它。
分配完内存池之后。会用一个状态机解析请求行
,也就是\r\n
之前的,方法名、url、协议。解析过程中可能发现有些url特别大,已经超过了刚刚所分配的1k的内存。这个时候分配更大的内存,这个更大的内存就是用来解决有些url太长了。分配的大内存有多大呢?最大是large_client_header_buffers
(也是一个经常遇到的一个配置)。4 8k
是什么意思呢?不是一下子分配32k,而是先分配一个8k,然后把刚刚那1k的内容拷贝到8k,剩下的7k再去接收url,然后发现url是否解完了通过状态机做这样的部分。如果8k都没有接收完,再分配第二个8k(即分配到了16k,最多一共分配32k)。
当完整的解析完请求行,然后就可以标识URI
。什么是标识URI?后面会介绍nginx有很多变量,这些变量并不是真的复制一份,它只是有一个C指针指向接收到的解析行。所以nginx性能才能如此强大。
接收header
http请求的header可能非常长,它含有cookie、host等这些字段。状态机解析header
。header很有可能超过1k,所以还是要分配大内存large_client_header_buffers
。接收URI跟接收header的这两个大内存是共用的。如果接收URI用掉了2个8k,那么在接收header这里顶多在用掉2个8k,这个4 8k
是针对所有的,所以叫做client_header。
接受完完整的header,就开始标识header
。标识header的过程中还会涉及到一些操作。比如:确定哪些server块开始处理这个请求。
当收到host 这样一个header时候,当我收到全部的header以后,标识完header以后。就可以移除超时定时器
了。所以,大家可以确定你需要什么时候修改这个60秒。也就是说我们收完header到底最长两次接收之间需要多大的时间。
接下来开始核心的过程——开始11个阶段的http请求处理
。
总结
以上的处理流程都是nginx框架完成的。nginx的http模块是多样化的。我们必须有一个流程按照一个主线串起来。方便我们理解与记忆。
留言问题
1.请教一下课件中的接收URI的部分为什么会涉及到请求行呢?我理解URI仅仅是一个字符串而已,请求行处理是右边接收header的逻辑
作者回复
一个标准的HTTP请求是这样的:GET /uri HTTP/1.1\r\nHost: xxx\r\n,这里第一个\r\n就是请求行。
2.视频中好像没有很具体的讲出连接内存池的作用,只是讲到从连接内存池中分配出了请求内存池,除此之外,还有其他的作用吗?
作者回复
所有与请求无关的内存都要在连接内存池里分配.例如第一次接收http请求行,此时既没有Host也没有URL,只能用连接内存池里分配默认1K的内存
网友评论