我正在研究 HTTP 代理和反向代理如何处理缓慢的客户端问题。这个想法是上游服务器只有有限的槽位供客户端使用,如果客户端接收数据很慢,则会长时间消耗槽位。反向代理可用于缓冲响应,提前释放上游的插槽,然后缓慢地将响应转发到客户端。
例如,nginx 建议通过分配(默认)最多 8 个缓冲区(每个缓冲区 8k)来启用上游响应缓冲。如果这些缓冲区已满,它可以开始在磁盘上缓冲(但我禁用了此功能,我的磁盘足够繁忙)。
看:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering
然而,我做了多次检查,似乎内核分配了一个相当大的 RCVBUF(接收缓冲区),大约 1-4MB。如果上游发送 2MB 的响应,而最终客户端没有读取任何内容,则代理缓冲区将很快被填满,并且将使用内核缓冲区。
由于代理将缓冲比内核更少的数据,因此我不知道它如何帮助处理缓慢的客户端。当内核为我们提供足够的功能时,在代理中显式实现/启用缓冲功能有什么好处?
编辑:在第一次回复之后,我想提供一些有关我测试的内容的详细信息。
- 客户端程序连接到反向代理,等待几秒钟并开始读取。
- 反向代理仅在用户空间内存中缓冲最多 8kB,在 read() 之后,它将记录套接字接收缓冲区的大小。
- 上游提供 2MB 的 HTTP 响应(加上标头),记录accept() 和 close() 之间所花费的时间。
测试时,我可以看到服务器永远不会等待 write(),甚至在慢速客户端执行第一次 read() 之前调用 close()。此外,套接字接收缓冲区的大小将增长并超过 2MB:来自服务器的整个响应将被缓冲。
我在与客户端和代理相同的主机上使用上游服务器运行测试,并且在远程主机上使用上游服务器,观察到的行为是相同的。
另外,我知道内核在内存压力下可能会使用较小的缓冲区,但这也会影响反向代理(因此可能无法在用户空间中缓冲响应)。
答案1
我做了多次检查,似乎内核分配了一个相当大的 RCVBUF(接收缓冲区),大约 1-4MB。
不是默认情况下它不会。尺寸为每个插座; HTTP 关系可能涉及多个套接字。据我所知,没有系统最大值,除非有(相当高的)最大套接字数。从man 7 socket
:
SO_RCVBUF
设置或获取最大套接字接收缓冲区(以字节为单位)。当使用setsockopt(2)设置该值时,内核会将该值加倍(以便为簿记开销留出空间),并且该加倍的值由getsockopt(2)返回。 默认值由 /proc/sys/net/core/rmem_default 文件设置, 最大允许值由 /proc/sys/net/core/rmem_max 文件设置。此选项的最小值(双倍)值为 256。
对我来说,这是:
> cat /proc/sys/net/core/rmem_default
212992
208 KB。然而,它实际上因协议而异。从man 7 tcp
:
tcp_rmem(自 Linux 2.4 起)
这是一个由 3 个整数组成的向量:[min, default, max]。 TCP 使用这些参数来调节接收缓冲区大小。 TCP 根据系统中的可用内存,在这些值的范围内从下面列出的默认值动态调整接收缓冲区的大小。
分钟: 每个 TCP 套接字使用的接收缓冲区的最小大小。默认值是系统页面大小。 (在 Linux 2.4 上,默认值为 4K,在低内存系统中降低为 PAGE_SIZE 字节。)该值用于确保在内存压力模式下,低于此大小的分配仍然会成功。这不用于限制在套接字上使用 SO_RCVBUF 声明的接收缓冲区的大小。
默认: TCP 套接字接收缓冲区的默认大小。该值会覆盖为所有协议定义的通用全局 net.core.rmem_default 中的初始默认缓冲区大小。默认值为 87380 字节。 (在 Linux 2.4 上,在低内存系统中该值将降低至 43689。)如果需要更大的接收缓冲区大小,则应增加该值(以影响所有套接字)。要使用大型 TCP 窗口,必须启用 net.ipv4.tcp_window_scaling(默认)。
最大限度: 每个 TCP 套接字使用的接收缓冲区的最大大小。该值不会覆盖全局net.core.rmem_max。这不用于限制在套接字上使用 SO_RCVBUF 声明的接收缓冲区的大小。默认值使用以下公式计算
max(87380, min(4MB, tcp_mem[1]*PAGE_SIZE/128))
(在Linux 2.4上,默认值为87380*2字节,在低内存系统中降低到87380)。
该值报告于/proc/sys/net/ipv4/tcp_rmem
:
> cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 6291456
这可以通过一些创建单个 TCP 套接字的 C 代码来确认:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdio.h>
int main (int argc, const char *argv[]) {
int rcvbufsz;
socklen_t buflen = sizeof(rcvbufsz);
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket() failed");
return 1;
}
if (getsockopt (
fd,
SOL_SOCKET,
SO_RCVBUF,
&rcvbufsz,
&buflen
) == -1) {
perror("getsockopt() failed");
return 1;
}
printf("SO_RCVBUF = %d\n", rcvbufsz);
return 0;
}
编译并运行报告SO_RCVBUF = 87380
,与 中的数字相匹配/proc
。然而,nginx 可以自由地向上调整这个值,但不超过/proc/sys/net/core/rmem_max
,这可能又是 208 kB。
关于 TCP 如何“根据系统中可用的内存从默认值动态调整接收缓冲区的大小”(来自 参考资料man 7 tcp
)的内容也值得重申。
现在谈谈你问题的实质......
由于代理将缓冲比内核更少的数据,因此我不知道它如何帮助处理缓慢的客户端。当内核为我们提供足够的功能时,在代理中显式实现/启用缓冲功能有什么好处?
请记住,上面讨论的缓冲区不是用户空间缓冲区。尽管它是读取数据的来源,但应用程序通常不会直接对其执行任何操作。所以nginx自己的buffer中的数据不同时在内核缓冲区中。正在从中读出它。读取会清空缓冲区。所以这实际上是增加缓冲数据量 8 * 8 = 64 kB。