ネットワークI/Oモデル
人が多すぎると問題が発生します。ウェブが最初に登場したとき、ウェブにアクセスする人はほとんどいませんでした。近年、ネットワークアプリケーションの規模は徐々に拡大しており、それに応じてアプリケーションのアーキテクチャも変化する必要があります。 C10k の問題により、エンジニアはサービスのパフォーマンスとアプリケーションの同時実行機能について考える必要があります。
ネットワーク アプリケーションは、ネットワーク I/O とデータ計算という 2 つの主要なタイプの問題のみを処理する必要があります。後者と比較すると、ネットワーク I/O の遅延は、後者よりもアプリケーションに大きなパフォーマンスのボトルネックをもたらします。ネットワーク I/O のモデルは大まかに次のとおりです:
ネットワーク I/O の本質はソケットの読み取りであり、Linux システムではソケットはストリームとして抽象化され、I/O は対流操作として理解できます。この操作は 2 つの段階に分かれています:
データの準備ができるのを待っています。
カーネルからプロセスにデータをコピーします。
ソケットストリーミングのみの場合、
最初のステップでは通常、データ パケットがネットワークに到着し、カーネル内のバッファにコピーされるのを待機します。
2 番目のステップは、カーネル バッファからアプリケーション プロセス バッファにデータをコピーすることです。
I/Oモデル:
これらのモデルを理解するために、簡単な比喩を使用してみましょう。ネットワーク IO は釣りに似ています。魚が餌を受け取るのを待つことは、魚が餌を受け取ったら、カーネルがデータをコピーする段階です。釣りをされる方はお申込み手続きとなります。
I/Oのブロック
ブロッキング I/O は最も人気のある I/O モデルです。それは人々の最も一般的な思考ロジックに準拠しています。ブロッキングとは、プロセスが「休止」しており、CPU が他のプロセスの処理で忙しいことを意味します。ネットワーク I/O 中に、プロセスは、recvform システム コールを開始します。その後、プロセスはブロックされ、データの準備ができるまで何も行われません。データはカーネルからユーザー プロセスにコピーされます。最後に、プロセスはデータを処理し、データを待機する 2 段階のデータ処理により、プロセス全体がブロックされます。他のネットワーク I/O を処理できません。おおよそ以下のようになります:
これは、私たちが釣りに行くとき、竿を投げた後、魚が餌を取るまで岸で待つのと同じです。それから再びロッドを投げ、次の魚が餌を受け取るのを待ちます。待っている間は何もせず、おそらくランダムに考えます。
ブロックIOの特徴は、IO実行の両方の段階でブロックされることです
ノンブロッキングI/O
ネットワーク I/O 中に、ノンブロッキング I/O は、データの準備ができているかどうかを確認するために、recvform システム呼び出しも行います。「ノンブロッキングでは、大きなブロック時間を N 個の小さなブロックに分割します。 " ブロッキングしているため、プロセスは常に CPU によって「ひいきにされる」機会があります。"
つまり、ノンブロッキング型のrecvformシステムコールが呼び出された後、プロセスはブロックされず、カーネルはすぐにプロセスに戻ります。データの準備ができていない場合は、この時点でエラーが返されます。プロセスが戻った後、他の処理を行ってから、recvform システム コールを開始できます。上記のプロセスを繰り返し、recvform システムコールを周期的に実行します。このプロセスは、多くの場合、ポーリングと呼ばれます。ポーリングでは、データの準備ができるまでカーネル データをチェックし、その後、データ処理のためにデータをプロセスにコピーします。データをコピーするプロセス全体中、プロセスは依然としてブロックされていることに注意してください。
釣り方で分類してみましょう。竿を水中に投げるときに、魚がウキに動きがあるかどうかを確認します。魚が餌を取らない場合は、数匹掘るなどの別のことをします。もっとミミズ。それから彼はすぐに戻ってきて、魚がウキに噛み付いているかどうかを確認しました。この往復のチェックと魚が掛かるまで放置してから処理します。
ノンブロッキング IO の特徴は、ユーザー プロセスがデータの準備ができているかどうかをカーネルに常に積極的に問い合わせる必要があることです。
I/Oの多重化
ノンブロッキング呼び出しのため、ポーリングがプロセスの大部分を占め、ポーリングが多くの CPU 時間を消費していることがわかります。前の 2 つのモードを組み合わせます。ポーリングがプロセスのユーザーモードではなく、誰かが助けてくれれば良いのですが。多重化はまさにこの問題に対処します。
多重化には、select または Paul という 2 つの特別なシステム コールがあります。選択呼び出しはカーネル レベルで行われます。選択ポーリングと非ブロッキング ポーリングの違いは、前者は複数のソケットのいずれかのデータが準備できたら、読み取り可能に戻ることができることです。プロセスは、カーネルからユーザー プロセスにデータをコピーするために、recvform システム コールを実行できます。もちろん、このプロセスはブロックされます。多重化には 2 種類のブロックがあり、選択またはポーリングが呼び出された後、プロセスがブロックされます。最初のブロックとの違いは、この時点での選択は、すべてのソケット データが到着するまで待機せずに処理することです。処理するデータの一部が到着したときにユーザーを呼び出します。データが到着したことはどのようにしてわかりますか?監視はカーネルに引き渡され、カーネルがデータ到着の処理を担当します。 「ノンブロッキング」とも解釈できます。
多重化、つまり複数のソケットをポーリングする場合。釣りをするとき、私たちは同時に複数の釣り竿を投げることができる助手を雇い、どの竿にも魚がかかるとすぐに竿を引きました。彼は私たちの釣りを手伝うことだけを担当しており、私たちが魚に対処するのを手伝ってくれるわけではないので、私たちは彼が竿を閉じるのを一緒に待たなければなりません。もう一度魚を扱いましょう。多重化は複数の I/O を処理できるため、複数の I/O 間の順序が不明確になり、当然、異なる番号をターゲットにすることもできます。
多重化の特徴は、プロセスがメカニズムを通じて IO ファイル記述子を同時に待機できることです。カーネルはこれらのファイル記述子 (ソケット記述子) を監視し、それらのいずれかが読み取り準備状態に入り、選択、ポーリング、 epoll 関数は返すことができます。監視方法としては、select、poll、epoll の 3 つの方法に分けられます。
前述の 3 つのモードを理解します。ユーザー プロセスは、データの到着を待機するときに、さまざまな方法で処理します。最初のプロセスはブロックされる場合があります。 、ブロックされる場合とブロックされない場合があります。その時点で、2 番目のプロセスがブロックされました。 I/O プロセス全体から見ると、順次実行されるため、同期モデル (非同期) に分類されます。すべてのプロセスがカーネルをアクティブにチェックします。
非同期I/O
同期 I/O と比較して、非同期 I/O は連続的に実行されません。ユーザー プロセスが aio_read システム コールを実行すると、カーネル データの準備ができているかどうかに関係なく、カーネル データがユーザー プロセスに直接返され、ユーザー状態プロセスは他の作業を行うことができます。ソケット データの準備ができると、カーネルはデータをプロセスに直接コピーし、カーネルからプロセスに通知を送信します。どちらの I/O フェーズでも、プロセスはノンブロッキングです。
今回は釣りの専門家を雇いました。彼は釣りをするだけでなく、魚が掛かった後に私たちにテキストメッセージを送って、魚の準備ができたことを知らせてくれます。私たちはロッドをキャストすることを彼に委任し、彼が私たちにテキストメッセージを送信するまで他のことをするために走り去ります。水揚げされた魚を処理するために戻ってきます。
同期と非同期の違い
上記のモデルの議論を通じて、ブロッキングと非ブロッキング、同期と非同期を区別する必要があります。これらは実際には 2 つの概念セットです。前者のグループは区別しやすいですが、後者のグループは前者と混同されやすいことがよくあります。私の意見では、いわゆる同期は I/O プロセス全体に含まれます。特に、データをコピーするプロセスがプロセスをブロックし、アプリケーションのプロセス状態がカーネル状態をチェックします。非同期とは、I/O プロセス全体のユーザー プロセスがノンブロッキングであることを意味し、データがコピーされると、カーネルはユーザー プロセスに通知を送信します。
同期モデルの場合、最初の段階の主要な処理方法が異なります。非同期モデルでは、両方のフェーズが異なります。ここでは信号駆動モードを無視します。これらの用語は依然として混乱を招きます。非同期は間違いなくノンブロッキングであり、非同期ノンブロッキングという用語は不必要に感じられるため、同期モデルのみがブロッキングとノンブロッキングを考慮します。
モデルを選択してください
同期モデルでは、多重化された I/O を使用することでサーバーのパフォーマンスを向上させることができます。
多重化モデルの中で、選択モデルとポーリング モデルがより一般的に使用されます。これらは両方とも、オペレーティング システムによって提供されるシステム インターフェイスです。もちろん、Python の select モジュールは、より高度なカプセル化を提供します。選択とポーリングの基本的な原則は似ています。待望のモデルが登場、今回の記事はモデル選びに焦点を当てます。
1.原則を選択してください
ネットワーク通信は、Unix システムによってファイル (通常はデバイス ドライバーによって提供されるデバイス) の読み取りと書き込みとして抽象化され、ドライバーは独自のデータが利用可能かどうかを知ることができます。ブロック操作をサポートするデバイス ドライバーは通常、上位層 (ユーザー層) で必要なブロック操作または非ブロック操作をサポートするための読み取り/書き込み待機キューなど、独自の待機キューのセットを実装します。デバイスのファイル リソースが利用可能な場合 (読み取り可能または書き込み可能)、プロセスに通知されます。そうでない場合、プロセスはスリープ状態になり、データが利用可能になるとプロセスが起動されます。
これらのデバイスのファイル記述子は配列に配置され、select が呼び出されたときに配列が走査され、ファイル記述子が読み取り可能な場合は、変更されたファイル記述子が返されます。トラバーサルが完了したときに、使用可能なデバイス ファイル記述子がまだない場合、選択により、リソースが使用可能になるのを待ってウェイクアップするまでユーザー プロセスがスリープ状態になり、以前に監視されていたアレイがトラバースされます。各トラバースは線形です。
2.エコーサーバーを選択
select にはシステム コールとオペレーティング システム関連の知識が含まれるため、その原理を文字通り理解するのは比較的退屈です。コードでデモンストレーションすること以上に優れたものはありません。 Python の select モジュールを使用すると、次のようなエコー サーバーを簡単に作成できます:
上記のコードを実行し、curl を使用して http://localhost:5000 にアクセスすると、コマンド ラインから返された要求された HTTP リクエスト情報を確認できます。
上記のコードの原理を以下で詳しく分析します。
上記のコードは、socket を使用して TCP ソケットを初期化し、ホスト アドレスとポートをバインドし、リッスンするようにサーバーを設定します。
監視のために選択する必要があるリストは、監視する必要があるオブジェクト (システムによって監視されるファイル記述子に相当) がここで定義されます。これはソケットソケットとユーザー入力をリッスンします。
その後、コードはサーバーのワイヤレス ループを実行します。
select 関数を呼び出し、受信リスト入力のループを開始します。 CURL サーバーがない場合、この時点では TCP クライアント接続が確立されないため、リスト内のオブジェクトはすべてデータ リソースであり、使用できません。したがって、ブロックを選択しても戻りません。
クライアントがcurl http://localhost:5000に入ると、ソケット通信が開始され、入力内の最初のオブジェクトサーバーが使用不可から使用可能に変わります。したがって、select 関数呼び出しは戻り、このときの読み取り可能にはソケット オブジェクトがあります (ファイル記述子は読み取り可能です)。
select が返された後、次に読み取り可能なファイル オブジェクトを走査します。この時点では、ソケット接続は 1 つだけあり、TCP スリーウェイ ハンドシェイク接続を確立してから、その接続オブジェクトを In に追加します。入力監視リストは、接続上でデータ IO 操作があるかどうかを監視することを意味します。
現時点では読み取り可能な利用可能なオブジェクトが 1 つだけであるため、トラバースは終了します。メインループに戻り、select を再度呼び出します。この時点で呼び出されると、確立する必要のある新しい接続があるかどうかを調べて監視するだけでなく、新しく追加された接続も監視します。 curl データが到着すると、select は readable に戻り、このとき for ループが実行されます。新しいソケットがない場合は、次のコードが実行されます:
ソケット接続を介してrecv関数を呼び出し、クライアントによって送信されたデータを取得します。データ送信が完了すると、接続は監視対象の入力リストから削除されます。その後、接続を閉じます。
ネットワーク インタラクション プロセス全体は次のようになります。もちろん、コマンド ラインでのユーザーの入力が中断された場合、入力リストで監視されている sys.stdin によって select が返され、最終的に次のコードが実行されます。
リーリー入力によって監視されるオブジェクトには常にデータがあり、次に select が呼び出されるとき、readable が返されます。for ループが終了するまで、次の select が実行されます。 。
主に、ソケット接続の確立が IO であり、接続されたデータの到着も IO であることに注意してください。
3.セレクトの欠点
select は使うのがとても楽しいですが、クロスプラットフォーム機能もあります。しかし、select にはまだいくつかの問題があります。
Select は監視対象のファイル記述子を走査する必要があり、この記述子の配列には最大制限があります。ファイル記述子の数が増加するにつれて、ユーザー モードとカーネルのアドレス空間のコピーによって発生するオーバーヘッドも直線的に増加します。監視対象のファイル記述子が長期間非アクティブだった場合でも、select は引き続き線形にスキャンします。
これらの問題を解決するために、オペレーティング システムはポーリング ソリューションを提供しますが、ポーリング モデルは select とほぼ同じですが、いくつかの制限が変更されているだけです。現在、Linux で最も先進的な方法は epoll モデルです。
nginxやnodejsなどの多くの高性能ソフトウェアはepollに基づいて非同期です。