サーバー側は通常、高度な並行ビジネスアクセスをサポートする必要があり、どのように優れたサーバー側のネットワークIO作業スレッド/プロセスモデルを設計することは、高度な並行ビジネスアクセスの需要に重要な中核的な役割を果たしています。
この論文では、様々なシナリオにおける様々なネットワークIOスレッド/プロセスモデルを要約し、様々なモデルの長所と短所、およびそれらのパフォーマンス最適化方法を示します。これは、サーバーサイド開発者、ミドルウェア開発者、データベース開発者、およびその他の開発者が学ぶのに非常に適しています。
スレッディング・モデル i. シングルスレッド・ネットワークIO多重化モデル
説明
- すべてのネットワークIOイベントがepollイベントセットに登録されます。
- メインループはepoll_waitを使用して、カーネルステートによって収集されたepollイベントの情報を一度に取得し、各イベントに対応するコールバックをポーリングします。
- イベント登録、epoll_wait イベント取得、イベントコールバック実行はすべて単一スレッドで処理されます。
完全なリクエストの構成
完全なリクエスト処理プロセスは、以下の主な部分で構成されます:
ステップ1: epoll_waitを使ってネットワークIOイベントを一度に取得します。
ステップ2:データの読み取りとプロトコルの解析
ステップ3:解析に成功すると、ビジネスロジックが処理され、クライアントに応答します。
このウェブスレッディングモデルの欠陥
epollイベントのフェッチやイベント処理など、すべての作業は単一のスレッドで実行され、1つのリクエストのイベントコールバック処理がブロックされる限り、他のすべてのリクエストもブロックされます。例えば、redisのハッシュ構造にファイル数が多すぎる場合、1つのハッシュキーに数百万のファイル数が含まれていると仮定すると、ハッシュキーの有効期限が切れるとredis全体がブロックされます。
シングルスレッド作業モデルでは、CPUがボトルネックになり、QPSが高すぎるとCPU全体の負荷が100%に達し、レイテンシがジリジリになります。
典型的なケース
redis
Twitterキャッシュミドルウェア twemproxy
メインループワークフロー
while (1) {
//epoll_waitネットワークイベントを待つ、ネットワークイベントがあればリターンする、タイムアウトの範囲を指定する
size_t numevents= epoll_wait();
//epollによって得られたネットワークイベントを繰り返し処理し、対応するイベントコールバックを実行する。
for (j = 0; j < numevents; j++) {
if(イベントを読む){
//
readData()
//解析
parseData()
//読み取りイベント処理、データ読み取り後のビジネスロジック処理
requestDeal()
} else if(書き込みイベント){
//書き込みイベント処理、書き込みデータロジック処理
writeEentDeal()
} else {
//例外イベント処理
errorDeal()
}
}
}
注:後続のマルチスレッド/プロセス・モデルにおける各スレッド/プロセスのメイン・フローは、このwhile()フローと一致しています。
redisのソースコード解析と非同期ネットワークIO多重化ライトデモ
スレッディングモデル ii. 単一リスナー + 固定ワーカースレッド
スレッドモデル図を以下に示します:
説明
リスナー・スレッドは、すべてのクライアント・リンクを受け入れる責任を負います。
リスナースレッドは、新しいクライアントリンクを受信するたびに新しいfdを生成し、それをディストリビュータ経由で対応するワーカースレッドに送ります。
ワーカースレッドが対応する新しいリンクfdを取得した後、そのリンクに対するその後のすべてのネットワークIOの読み込みと書き込みは、そのスレッドによって処理されます。
32のリンクがあると仮定すると、各スレッドは、32のリンクが正常に確立された後、平均4つのリンクの読み取り、書き込み、メッセージ処理、およびビジネスロジック処理を処理します。
このウェブスレッディングモデルの欠陥
アクセプト処理用のリスナースレッドは1つしかないため、瞬時の高同時性シナリオではボトルネックになりやすくなります。
IOの多重化の方法を介してスレッドは、複数のリンクのFDデータの読み取りと書き込み、メッセージの解析とその後のビジネスロジックの処理に対処するために、このプロセスは、深刻なキューイング現象を持って、たとえば、内部処理時間の解析後に受信したメッセージのリンクが長すぎるし、要求の他のリンクは、キューイングをブロックします。
典型的なケース
スレッディングモデル iii. 固定ワーカースレッドモデル
プロトタイプモデルは以下の通り。
説明
Linux カーネル 3.9 は reuseport 機能のサポートを開始しました。この機能では、カーネルスタックが新しいリンクを取得するたびに、ユーザーランドのワーカースレッドへの配布のバランスを自動的に調整します。
このモデルは、モデルIにおけるリスナーの1点ボトルネック問題を解決します。同時にリスナーとして動作する複数のプロセス/スレッドは、クライアントからの新しいリンクを受け入れることができます。
このネットワークプロセス/スレッドモデルの欠陥
reuseportがサポートされた後、カーネルはロードバランシングを通して、異なる新しいリンクを複数のユーザランドのワーカープロセス/スレッドに分散し、各プロセス/スレッドはIO多重化を通して、複数のクライアントからの新しいリンクfdのデータ読み書き、メッセージ解析、解析後のビジネスロジック処理を処理します。それぞれのワーカープロセス/スレッドは、複数のリンクからのリクエストを同時に処理します。 リンクのメッセージが受信され、解析された後の内部処理時間が長すぎると、他のリンクからのリクエストはブロックされ、キューに並びます。
このモデルはリスナーの1点ボトルネック問題を解決しますが、ワーカースレッド内部の待ち行列問題は解決されません。
しかし、7層のフォワーディングエージェントであるNginxは、すべてインメモリ処理なので内部処理時間が短く、このモデルに適しています。
典型的なケース
nginx(nginxはプロセスを使用して、モデルの原理は同じです)、モデルはnginxプロキシなどの内部ビジネスロジックの単純なシナリオに適しています。
スレッドモデルIV:1リンク1スレッドモデル
スレッドモデル図を以下に示します:
説明
リスナー・スレッドは、すべてのクライアント・リンクを受け入れる責任を負います。
リスナー・スレッドは、新しいクライアント・リンクを受信するたびにスレッドを作成し、このスレッドは、そのリンクに対するデータの読み取りと書き込み、メッセージの解析、およびビジネス・ロジックの処理のみを担当します。
このウェブスレッドモデルには欠陥があります:
リンクは、スレッドを作成するには、100,000リンクの場合は、100,000スレッドが必要ですが、スレッドの数が多すぎる、システムの責任、メモリ消費量も多くなります。
リンクが閉じられると、スレッドも破棄される必要があり、スレッドの頻繁な生成と消費はシステム負荷をさらに増加させます。
典型的なケース
mysqlのデフォルトの方法、mongodbの同期スレッドモデルの設定、データベースサービスなど、より時間のかかるシナリオのリクエスト処理用
Apacheウェブサーバーは、モデルがApacheのパフォーマンスを制限し、nginxの利点は、より明白になります。
スレッドモデル V: 単一リスナー + 動的ワーカースレッド
スレッドモデル図を以下に示します:
説明
リスナー・スレッドは新しいリンク fd を受信し、処理のためにそれをスレッドプールに渡します。 その後のすべての読み取りと書き込み、メッセージ解析、およびリンクのビジネス処理は、スレッドプール内の複数のスレッドによって処理されます。
このモデルでは、1つのリクエストを複数のタスクに変換してグローバルキューに入れ、スレッドプールのスレッドがキューからタスクをフェッチして実行します。
同じリクエストアクセスが複数のタスクに分割され、1つのリクエストが複数のスレッドで処理されることがあります。
タスクが多すぎてシステムにストレスがかかると、スレッドプールのスレッド数が動的に増加します。
タスクが削減され、システムの圧力が低下すると、スレッドプールのスレッド数が動的に減少します。
作業スレッドの実行時間に関するいくつかの統計:
T1:完全なmongodbメッセージを受け取るために、基礎となるasioライブラリを呼び出す時間。
T2: メッセージ受信後のすべての処理
T3:スレッドがデータを待つ時間
単一のワーカースレッドが「アイドル」であると判断する方法
スレッドの総実行時間 = T1 + T2 + T3、ここでT3は無駄な待ち時間。T3は無駄な待機時間の割合が大きい場合、それはスレッドが比較的アイドルであることを意味します。有効時間の割合を決定するために作業スレッドの処理の各サイクルの後、指定されたしきい値よりも小さい場合、それは終了し、自分自身を破壊します。
スレッドプールのワーカースレッドが「ビジーすぎる」と判断する方法
制御スレッドは、スレッドプール内のワーカースレッドにかかる圧力を判断し、パフォーマンスを向上させるためにスレッドプール内に新しいワーカースレッドを作成するかどうかを決定するためだけに使用されます。
制御スレッドは、スレッドの圧力状態をチェックするために、スレッドプール内のスレッドを一定時間ごとに循環させる、実装原理は、スレッドプール内のスレッドの単純なリアルタイム記録は、次の2種類のカウントのために現在実行されている:スレッドの総数_threadsRunning、現在タスクタスクを実行しているスレッドの数_threadsInUse.もし_threadsRunning=_threadsRunning、それはすべての作業スレッドが現在タスクタスクを処理していることを意味します。threadsRunning=_threadsInUseの場合、すべてのワーカースレッドが現在タスク処理中であり、スレッドプールのスレッドが高負荷状態にあることを意味します。
モデル詳細なソースコードの実装プロセス詳細公開記事:MongoDBネットワーク伝送処理ソースコードの実装とパフォーマンスチューニング - 経験カーネル性能極端な設計
このウェブスレッドモデルには欠陥があります:
- スレッドプールはタスクを実行させるため、ロックの競合が発生し、これがシステムのボトルネックになります。
典型的なケース
mongodb dynamic adaptive thread model は、データベースサービスなど、リクエスト処理に時間がかかるシナリオのためのモデルです。
スレッドモデル VI. シングルリスナー + ダイナミックワーカースレッド - mongodb ネットワークスレッドモデル最適化の実践
スレッドモデル図を以下に示します:
説明
複数のキューにグローバルキューを分割し、それぞれのキューにハッシュハッシュに応じてキューにタスクは、作業スレッドは、タスクを取得するために、同じようにハッシュを介して対応するキューにタスクを取得するために、この方法では、ロック競争を減らすために、全体的なパフォーマンスを向上させながら。
典型的なケース
OPPOは、自社開発のmongodbカーネルマルチキュー適応スレッドモデルの最適化は、パフォーマンスが向上し、データベースサービスなど、より時間のかかるシナリオを要求処理に適用されます。
質問?なぜmysqlやmongodbなどのデータベースは、カーネルのreuseportの特殊なマルチスレッドによるacceptリクエストの同時処理を利用しないのでしょうか?
A: データベースサービスを含め、事実上すべてのサービスがこの機能を利用できます。しかし、データベースサービスのアクセスレイテンシは一般的にmsレベルであるため、reuseport機能を利用した場合、レイテンシは数十usの性能向上となりますが、データベースの内部処理のmsレベルのレイテンシと比較すると、数十usの性能向上は基本的に無視できます。
キャッシュやプロキシなどのミドルウェアは、内部処理時間自体が比較的小さく、また私たちのレベルでもあるので、この機能をフルに活用する必要があります。





