proxy_buffering off
经过实测,在压测环境修改了这个值以后,以及调小了 proxy_buffer_size 的值以后,内存稳定在了 20G 左右,没有再飙升过,内存占用截图如下所示。
后面可以开启 proxy_buffering,调整 proxy_buffers 的大小可以在内存消耗和性能方面取得更好的平衡。
在测试环境重复刚才的测试,结果如下所示。
可以看到这次内存值增长了 64M 左右。为什么是增长 64M 呢?来看看 proxy_buffering 的 Nginx 文档(nginx.org/en/docs/htt…
When buffering is enabled, nginx receives a response from the proxied server as soon as possible, saving it into the buffers set by the proxy_buffer_size and proxy_buffers directives. If the whole response does not fit into memory, a part of it can be saved to a temporary file on the disk. Writing to temporary files is controlled by the proxy_max_temp_file_size and proxy_temp_file_write_size directives.
When buffering is disabled, the response is passed to a client synchronously, immediately as it is received. nginx will not try to read the whole response from the proxied server. The maximum size of the data that nginx can receive from the server at a time is set by the proxy_buffer_size directive.
可以看到,当 proxy_buffering 处于 on 状态时,Nginx 会尽可能多的将后端服务器返回的内容接收并存储到自己的缓冲区中,这个缓冲区的最大大小是 proxy_buffer_size * proxy_buffers 的内存。
如果后端返回的消息很大,这些内存都放不下,会被放入到磁盘文件中。临时文件由 proxy_max_temp_file_size 和 proxy_temp_file_write_size 这两个指令决定的,这里不展开。
当 proxy_buffering 处于 off 状态时,Nginx 不会尽可能的多的从代理 server 中读数据,而是一次最多读 proxy_buffer_size 大小的数据发送给客户端。
Nginx 的 buffering 机制设计的初衷确实是为了解决收发两端速度不一致问题的,没有 buffering 的情况下,数据会直接从后端服务转发到客户端,如果客户端的接收速度足够快,buffering 完全可以关掉。但是这个初衷在海量连接的情况下,资源的消耗需要同时考虑进来,如果有人故意伪造比较慢的客户端,可以使用很小的代价消耗服务器上很大的资源。
其实这是一个非阻塞编程中的典型问题,接收数据不会阻塞发送数据,发送数据不会阻塞接收数据。如果 Nginx 的两端收发数据速度不对等,缓冲区设置得又过大,就会出问题了。
Nginx 源码分析
读取后端的响应写入本地缓冲区的源码在 src/event/ngx_event_pipe.c
中的 ngx_event_pipe_read_upstream 方法中。这个方法最终会调用 ngx_create_temp_buf 创建内存缓冲区。创建的次数和每次缓冲区的大小由
p->bufs.num(缓冲区个数) 和 p->bufs.size(每个缓冲区的大小)决定,这两个值就是我们在配置文件中指定的 proxy_buffers 的参数值。这部分源码如下所示。
static ngx_int_t
ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
for ( ;; ) {
if (p->free_raw_bufs) {
// ...
} else if (p->allocated < p->bufs.num) { // p->allocated 目前已分配的缓冲区个数,p->bufs.num 缓冲区个数最大大小
b = ngx_create_temp_buf(p->pool, p->bufs.size); // 创建大小为 p->bufs.size 的缓冲区
if (b == NULL) {
return NGX_ABORT;
p->allocated++;
Nginx 源码调试的界面如下所示。
还有过程中一些辅助的判断方法,比如通过 strace、systemtap 工具跟踪内存的分配、释放过程,这里没有展开,这些工具是分析黑盒程序的神器。
除此之外,在这次压测过程中还发现了 worker_connections 参数设置不合理导致 Nginx 启动完就占了 14G 内存等问题,这些问题在没有海量连接的情况下是比较难发现的。
最后,底层原理是必备技能,调参是门艺术。上面说的内容可能都是错的,看看排查思路就好。