1755843895
2025-08-22 03:51:00
世紀の変わり目に、私たちは大容量のWebサーバーのより大きなニーズを得るようになりました。たとえば、ありました C10Kの問題 紙。
当時、リクエストごとに行われた作業を減らすために行われたことの種類は、Webサーバーを事前に輸送していました。これは、高価なプロセス作成なしでリクエストを処理できることを意味します。
はい、すべての要求に新しいプロセスを作成することは、以前は完全に正常なものでした。
物事は良くなりました。人々はスレッドを作成する方法を学び、物事をより軽量にする。その後、彼らは使用に切り替えました poll()/select()、プロセス/スレッドの作成だけでなく、コンテキスト全体が切り替わるために。
コメントを覚えています クロス 海賊湾とそれを動力としたWebサーバーの両方の作成者であるAnakataから、「私はBorgのselect()の線に沿って、抵抗は無駄です」と、スケーラブルなWebサーバーの書き方を理解していない人をock笑します。
しかし select()/poll() また、スケーリングしません。 1万件の接続がある場合、それはリクエスト処理ループの反復ごとにカーネルに送信する必要がある1万整数の配列です。
入力 epoll (kqueue 他のオペレーティングシステムでは、ここでLinuxに焦点を当てています)。今はそれが良いです。メインループは今です:
set_up_epoll()
while True:
new, read, write = epoll()
epoll_add_connections(new)
for con in read:
process(con.read())
if con.read_all_we_need:
epoll_remove_read_op(con)
for con in write:
con.write_buffer()
if con.buffer_empty:
epoll_remove_write_op(con)
すべてのsyscallsはかなり安いです。 epoll() デルタでのみ取引しているため、何千ものアクティブな接続を再販売する必要はありません。
しかし、それらにはコストがないわけではありません。ここまで来ると、syscallのコストは実際には残りの総コストの重要な部分です。
私たちはここでの改善を無視します sendfile() そして splice()、そして代わりにジャンプします…
io_uring
私たちがやりたいことすべてのためにsyscallを実行し、カーネルにこれを行うように命じている代わりに、io_ingは列に注文を書き続け、カーネルにそのキューを非同期に消費させます。
たとえば、置くことができます accept() キューに。カーネルはそれを拾い上げ、着信接続を待ち、それが到着すると完了キューに「完了」が入ります。
Webサーバーは、完了キューを確認できます。そこに完成がある場合、それに基づいて行動できます。
このようにして、Webサーバーは、以前は「高価な」syscallであったあらゆる種類の操作を、単にメモリに書き込むことができます。それでおしまい。そして、メモリの別の部分の結果を読み取ります。それでおしまい。
忙しいループを避けるために、カーネルとWebサーバーの両方は、キューを少しだけチェックするのに忙しいループ(構成可能ですが、ミリ秒を考える)で、新しいものがない場合は、Queueに何かが追加されるまで「眠りにつく」ためにsyscallを行います。
同様に、カーネル側では、新しいものがない場合はカーネルが忙しいループを停止し、再び忙しいルーピングを開始するためにsyscallが必要です。
これは最適化するのが難しいように聞こえますが、そうではありません。最終的に、Webサーバーはキューに物を置くだけで、カーネルが実際にBusyloopingを停止した場合にのみそのsyscallを行うライブラリ関数を呼び出します。
これは、忙しいWebサーバーがすべてのクエリを1回(セットアップ後)にsyscallを実行する必要があることなしですべてのクエリを提供できることを意味します。キューが追加され続ける限り、 strace 表示します 何もない。
コアごとに1つのスレッド
今日のCPUには多くのコアがあるため、理想的には、コアごとに1つのスレッドを実行し、そのコアに結合し、読み取りワイトのデータ構造を共有しないことです。
のために numa ハードウェアでは、スレッドがローカルNUMAノードのメモリのみにのみアクセスすることを確認する必要があります。 このNetflixの話 numaと大量のHTTP配信に関する興味深いものがいくつかあります。
要求の負荷は、スレッド(したがってコア)間で完全にバランスが取れていませんが、将来の投稿のトピックである必要があると思います。
メモリの割り当て
ただし、カーネルサーバーとWebサーバー側の両方に、メモリの割り当てがあります。ユーザースペースでのメモリの割り当てには、最終的にはsyscallsが必要です。
Webサーバー側の場合、すべての接続に対して固定チャンクを事前に割り当ててから、その接続に関するすべてをそこに存在させることができます。そうすれば、新しい接続はsyscallsを必要とせず、メモリは断片化されず、メモリが不足するリスクはありません。
カーネル側では、各接続には、着信および発信バイトのためにバッファが必要になります。これはソケットオプションを介して多少制御可能かもしれませんが、将来の投稿の主題である必要があります。
ラムが逃げないようにしてください。悪いことが起こる傾向があります。
KTLS
KTLS アプリケーションが暗号化/復号化の仕事をカーネルに引き渡すことができるLinuxカーネルの機能です。アプリケーションは引き続きTLSの握手を実行する必要がありますが、その後、KTLSを有効にして、すべてプレーンテキストで送信されているふりをすることができます。
あなたはこれが実際には何もスピードアップしないと言うかもしれません、それはただ動きます どこ
暗号化が行われました。しかし、利益があります:
- これはそれを意味します
sendfile()使用することができ、ユーザースペースとカーネルスペースの間に大量のデータをコピーする必要があります。 - ネットワークカードにハードウェアのサポートがある場合、暗号操作は実際にCPUからネットワークカードにオフロードされ、CPUがより良いことをすることができます。
DecruptorLessファイル
もう1つの最適化は、ユーザースペースとカーネルスペースの間を行き来するファイル記述子を渡すことを避けることです。ファイル記述子とIO_の間のマッピングには、明らかに頭上にあります。
だから来る DecruptorLessファイル 経由
register_files。
これで、ユーザースペースが表示するファイル記述子番号は、単なる整数です。彼らは現れません /proc/pid/fd、およびio_uringでのみ使用できます。彼らはまだキャップされています ulimit ただし、ファイル記述子の制限。
小麦
これらの技術をより良く学ぶために、私は構築しました これらすべてを組み込むWebサーバー。
名前が付けられています tarweb 単一のTARファイルのコンテンツを提供するWebサーバーだからです。
錆、io_uring、およびktls。正確に最も一般的な組み合わせではありません。 IO_URINGとKTLSが一緒にうまくプレイしていないことがわかりました。 KTLSを有効にするには3つが必要です
setsockopt() 通話、およびIO_IRINGはサポートしていません setsockopt (それらが合流するまで 私のPR、つまり)。
そして ktls クレート、の一部 rustls、同期を呼び出すことのみを可能にします
setsockopt()、必要な構造をエクスポートしないために私が新しいio_uringに渡すのに setsockopt。 別のPRが送信されました。
したがって、これらの2つのPRが合併していると、うまく機能しています。
Tarwebは完璧とはほど遠いです。コードには多くの作業が必要であり、TLSライブラリ(Rustls)が握手中にメモリの割り当てを行わないという保証はありません。ただし、リクエストごとに1つのsyscallなしでHTTPSにサービスを提供します。そして、それはかなりクールです。
ベンチマーク
私はまだベンチマークをしていません。最初にコードをクリーンアップしたいです。
IO-uring and Cafety
IO_の同期サイコールよりも複雑にすることの1つは、完了キューに表示されて操作がマークされるまで、バッファーがメモリにとどまる必要があることです。
たとえば、提出するとき write 操作、これらのバイトのメモリ位置を扱いたり上書きしたりしてはなりません。
io-uring クレートはこれであまり役に立ちません。 APIは、コンパイル時に借入チェッカーがあなたを保護することを許可していません。また、ランタイムチェックを行っているとは思いません。
私はC ++に戻っているように感じます。そこでは、間違いが足全体を吹き飛ばす可能性があります。それは私がセグフォーを見たことがないという奇跡です。
誰かが作るべきです safer-ring クレートなど、の力を使用しています
ピン留め 錆の通常の「コンパイルされれば正しい」とrustの通常を達成するために、借入または何か。
#ゼロSyscall #HTTPSサーバーのIO_IRINGKTLSおよびRUST