【充电】《Nginx核心知识100讲》nginx内存池
极客专栏《Nginx核心知识100讲》36小节的笔记
内存池对性能的影响
如果你开发过Nginx第三方模块,不需要关心内存的释放。如果你配置一些比较罕见的nginx使用场景,你可能要修改nginx在请求和连接上初始分配的内存池大小。但,nginx官方说明通常不需要修改这些配置。究竟要不要改内存池的大小呢?
内存池究竟是怎么运转的?
之前有提到ngx_connections_s,每一个连接会使用到这么一个结构体。有个成员ngx_pool_t 对应的连接所使用的内存池。这个内存池可以通过connection_pool_size 去定义。

为什么需要内存池呢?

我们用一些工具可以发现nginx 产生的内存碎片是比较小的。这就是内存池的功劳。
内存池会把内存会提前分配好一批,而且当我们使用小块内存的时候,会用next指针(看图中浅绿色部分)一个个连接在一起。每次我们使用东西比较少的时候,第二次再分配内存会连接在一起使用。大大减少了内存碎片。
分配大块内存的时候还是会走到操作系统的allocate去分配大块的内存。对于nginx有什么好处呢?
nginx 主要在处理web请求,特别对于http请求,有两个非常明显的特点。
1.连接内存池
每当我们有一个tcp连接的时候,这个tcp连接可能会运行很多http请求(http keepalive请求)连接没有关闭,执行完一条请求之后还负责执行另外一条请求。有一些内存为连接分配一次就够了。比如:读取每一个请求的前1k字节,在连接内存池上分配一次就够了,只要连接不关闭,这1k的内存不需要释放,什么时候释放呢?连接关闭的时候再释放。
- 请求内存池
每一次请求开始分配,不知道分配多少,http请求,特别是http1.1而言,通常会分配4k大小的内存,因为url和header 需要分配那么多。没有内存池就要频繁的去分配。分配内存有代价。一次分配较多的内存就没有这样的问题。请求执行完之后,那么连接还可以复用,也可以把请求内存池。
这样所有nginx第三方模块开发者不必关注内存什么时候释放。只要关注是从连接内存池,还是请求内存池分配内存。比如,请求结束之后,连接人员想继续使用可以在连接内存池里面分配。
内存池配置说明
connection_pool_size
http://nginx.org/en/docs/http/ngx_http_core_module.html#connection_pool_size
Default: connection_pool_size 256|512
,跟操作系统位数有关。内存池配置512并不代表只能分配512。当分配超过预分配大小的时是可以继续分配的。512是预分配的大小,预分配之后可以减少分配内存的次数。
request_pool_size
http://nginx.org/en/docs/http/ngx_http_core_module.html#request_pool_size
每一个请求的内存池大小。Default: request_pool_size 4k;
。是connection的八倍。是因为对于连接而言保存的上下文信息比较少。它只需要帮助后面的请求读取一部分字节就可以了。对于请求而言,保存大量的上下文信息,比如:所有读取到的url跟headr一直保存下来。url通常还比较长。所以需要有4k。
官方文档上说它对性能影响比较小。在极端场景下,比如你的url很大,可以考虑把这个值调大些。相反,可以调小些(这样可以少消耗内存,可以做更大并发量的请求)。
内存池的意义
内存池对减少内存碎片,对第三方模块的快速开发是有很大意义的。可能会有一些第三方模块不当使用了内存池。本该在请求内存池分配内存,结果在连接内存里分配,可能会导致内存的延期释放,nginx内存无谓的增加。
留言问题
- 老师 请问 压测nginx 返回499比较多 是不是php程序与后台交互慢吗? 还是nginx配置有问题?
作者回复
后端慢的话,可以考虑nginx加缓存来缩短响应时间,或者压测客户端增大读超时时间。499是客户端读超时关连接造成的,加了proxy_ignore_client_abort on; 也不解决问题。推荐从超时时间或者优化响应速度入手。
- 我这边一个应用遇到一个问题。nginx反向代理一web服务器,access.log返回200了,但是客户端没收到响应,您建议我这边先抓包,access.log再增加remote_port。这是个流量很大的系统,而且是随机出现的问题。抓包不太现实,能否认为access.log的某个url请求返回状态码是200就认为nginx肯定是把响应出去了呢(或者说发给了我们前面的负载,而是负载到客户浏览器这段网络内数据丢了呢)。
作者回复
nginx打印access.log时,这条请求的response肯定已经发出去了,但只是nginx进程把write请求提交给linux kernal了,至于kernal有没有发到交换机,交换机有没有给到机房的路由器,有没有从广域网发到客户网络,等等,都是不可知的。
如果抓包不现实,那么就看端口吧,把remote_addr和remote_port打印到access日志中,然后对照浏览器上的src_port看。
3.内存池 原理跟 数据库连接池类似吧
作者回复
不太一样,nginx内存池与http协议相关度很大,当请求结束时、连接关闭时统一释放。
4.nginx有没有防DDOS的安全模块? 效果如何?
作者回复
进程防止DDOS攻击效果不行,大规模的攻击下还没到进程就已经崩了。如果只是应对小流量,可以考虑openresty+waf
5.request_pool_size是为每个请求分配的内存大小,请求内存池是预先分配了一定数量的request_pool_size,用完就丢到池里(类似java的连接池),重复使用,还是一个请求进来就向请求内存池申请分配一次,没有预先创建一定数量的request_pool_size?
作者回复
预先分配一部分,但没有释放,仅在请求结束后全部释放掉该内存池。