프록시가 Linux RCVBUF보다 적은 양의 데이터를 버퍼링하는 것이 유용합니까?

프록시가 Linux RCVBUF보다 적은 양의 데이터를 버퍼링하는 것이 유용합니까?

HTTP 프록시와 역방향 프록시가 느린 클라이언트를 처리하는 방법을 조사 중입니다. 업스트림 서버에는 클라이언트가 사용할 수 있는 슬롯 수가 제한되어 있고 클라이언트가 데이터 수신 속도가 느린 경우 슬롯이 오랫동안 소모된다는 아이디어입니다. 역방향 프록시를 사용하여 응답을 버퍼링하고 슬롯 업스트림을 일찍 해제한 다음 클라이언트에 응답을 천천히 전달할 수 있습니다.

예를 들어, nginx는 기본적으로 최대 8개의 버퍼(각각 8k)를 할당하여 업스트림 응답 버퍼링을 활성화할 것을 권장합니다. 이러한 버퍼가 가득 차면 디스크에서 버퍼링을 시작할 수 있습니다(그러나 이 기능을 비활성화했기 때문에 내 디스크는 충분히 사용 중입니다).

바라보다:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering

그러나 여러 번 확인한 결과 커널이 약 1-4MB 정도의 상당히 큰 RCVBUF(수신 버퍼)를 할당하는 것 같습니다. 업스트림이 2MB 응답을 보내고 최종 클라이언트가 아무것도 읽지 않으면 프록시 버퍼가 빠르게 채워지고 커널 버퍼가 사용됩니다.

프록시는 코어보다 적은 양의 데이터를 버퍼링하므로 느린 클라이언트에 어떻게 도움이 되는지 모르겠습니다. 커널이 충분한 기능을 제공할 때 프록시에서 버퍼링 기능을 명시적으로 구현/활성화하면 어떤 이점이 있습니까?

편집: 첫 번째 답변에 이어 제가 테스트한 내용에 대한 세부 정보를 제공하고 싶습니다.

  • 클라이언트 프로그램은 역방향 프록시에 연결하고 몇 초간 기다린 후 읽기를 시작합니다.
  • 역방향 프록시는 사용자 공간 메모리에서 최대 8kB까지만 버퍼링하며 read() 후에 소켓 수신 버퍼의 크기를 기록합니다.
  • 업스트림은 2MB HTTP 응답(헤더 포함)을 제공하여 accept()와 close() 사이에 소요된 시간을 기록합니다.

테스트할 때 느린 클라이언트가 첫 번째 read()를 수행하기 전에 서버가 결코 write()를 기다리지 않고 심지어 close()를 호출하는 것을 볼 수 있습니다. 또한 소켓 수신 버퍼 크기가 늘어나 2MB를 초과합니다. 즉, 서버의 전체 응답이 버퍼링됩니다.

클라이언트 및 프록시와 동일한 호스트, 그리고 업스트림 서버를 사용하는 원격 호스트에서 업스트림 서버를 사용하여 테스트를 실행했는데 관찰된 동작은 동일했습니다.

또한 커널은 메모리 부족으로 인해 더 작은 버퍼를 사용할 수 있지만 이는 역방향 프록시에도 영향을 미칩니다(따라서 응답이 사용자 공간에 버퍼링되지 않을 수 있음).

답변1

여러 번 확인한 결과 커널이 약 1-4MB 정도의 상당히 큰 RCVBUF(수신 버퍼)를 할당하는 것 같습니다.

기본적으로는 그렇지 않습니다. 차원은 소켓별로 결정됩니다. HTTP 관계에는 여러 소켓이 포함될 수 있습니다. 내가 아는 한, (상당히 높은) 최대 소켓 수가 없는 한 시스템 최대값은 없습니다. 에서 man 7 socket:

SO_RCVBUF

최대 소켓 수신 버퍼를 바이트 단위로 설정하거나 가져옵니다. 값이 setockopt(2)를 사용하여 설정되면 커널은 값을 두 배로 늘리고(장부 오버헤드를 위한 공간을 확보하기 위해) 두 배로 된 값은 getsockopt(2)에 의해 반환됩니다. 기본값은 /proc/sys/net/core/rmem_default 파일에 의해 설정됩니다. 허용되는 최대 값은 /proc/sys/net/core/rmem_max 파일에 의해 설정됩니다. 이 옵션의 최소(이중) 값은 256입니다.

나에게 이것은 다음과 같다:

> cat /proc/sys/net/core/rmem_default
212992

208KB. 그러나 실제로는 프로토콜마다 다릅니다. 에서 man 7 tcp:

tcp_rmem (리눅스 2.4부터)

이는 3개의 정수([최소, 기본값, 최대])로 구성된 벡터입니다. 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_max208kB를 초과할 수는 없습니다.

또한 TCP가 "시스템에서 사용 가능한 메모리를 기반으로 기본값에서 수신 버퍼의 크기를 동적으로 조정"하는 방법에 대한 man 7 tcp내용을 반복할 가치가 있습니다 (Resources 참조).

이제 질문의 본질을 살펴보겠습니다.

프록시는 코어보다 적은 양의 데이터를 버퍼링하므로 느린 클라이언트에 어떻게 도움이 되는지 모르겠습니다. 커널이 충분한 기능을 제공할 때 프록시에서 버퍼링 기능을 명시적으로 구현/활성화하면 어떤 이점이 있습니까?

위에서 설명한 버퍼는 사용자 공간 버퍼가 아니라는 점을 명심하세요. 이는 데이터를 읽는 소스이지만 일반적으로 애플리케이션은 이에 대해 직접 작업을 수행하지 않습니다. 따라서 nginx 자체 버퍼에 있는 데이터는동시에 커널 버퍼에 있지 않음. 그것에서 읽고 있습니다. 읽으면 버퍼가 지워집니다. 그래서 이것은 실제로증가하다버퍼링된 데이터 크기 8 * 8 = 64kB.

관련 정보