プロキシが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)を使用して設定されている場合、カーネルは値を2倍に(長くオーバーヘッドするためのスペースを確保するために)増加し、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 (Linux 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。

関連情報