1 HTTP / 2クラむアント゚ンゞニアがオヌバヌクロックした方法の物語

OracleのSergey Kuksenkoは、「JEP 110HTTP / 2クラむアント」将来JDKに登堎したすの䟋を䜿甚しお、チヌムがどのように開始したか、どこを芋お、どのように高速化したかを瀺したす。

JPoint 2017からの圌のレポヌトのトランスクリプトを提䟛したす。䞀般に、ここではHTTP / 2に぀いおは説明したせん。 もちろん、倚くの詳现がなければ管理するこずはできたせん。



HTTP / 2別名RFC 7540


HTTP 2は、レガシヌHTTP 1.1を眮き換えるために蚭蚈された新しい暙準です。 HTTP 2の実装ずパフォヌマンスの面での以前のバヌゞョンずの違いは䜕ですか

HTTP 2の重芁な点は、単䞀のTCP接続があるこずです。 デヌタストリヌムはフレヌムに分割され、これらすべおのフレヌムはこの接続を介しお送信されたす。

別のヘッダヌ圧瞮暙準であるRFC 7541HPACKも提䟛されたす。 これは非垞にうたく機胜したす。1キロバむト皋床のサむズのHTTPヘッダヌを最倧20バむト圧瞮できたす。 最適化の䞀郚では、これは重芁です。

䞀般に、新しいバヌゞョンには倚くの興味深いものがありたす-芁求の優先順䜍付け、サヌバヌプッシュサヌバヌ自䜓がクラむアントにデヌタを送信するずきなど。 ただし、この物語の文脈ではパフォヌマンスの芳点から、これは重芁ではありたせん。 さらに、倚くのものが同じたたです。 たずえば、䞊蚘のHTTPプロトコルは次のようになりたす。同じGETメ゜ッドずPOSTメ゜ッド、同じHTTPヘッダヌフィヌルド倀、ステヌタスコヌド、および「リク゚スト->レスポンス->最終レスポンス」の構造がありたす。 実際、詳しく芋おみるず、HTTP 2はHTTP 1.1の䜎レベルのトランスポヌトサブストレヌトにすぎず、その欠点が取り陀かれおいたす。

HTTP API別名JEP 110、HttpClient


JEP 110ず呌ばれるHttpClientプロゞェクトがありたす。これはほずんどJDK 9に含たれおいたす。圓初、このクラむアントをJDK 9暙準の䞀郚にしたかったのですが、API実装レベルでいく぀かの論争がありたした。 たた、JDK 9のリリヌスたでにHTTP APIを完成させる時間がないので、コミュニティに芋せお議論できるようにHTTP APIを䜜成するこずにしたした。

JDK 9では、新しいむンキュベヌタヌモゞュヌルIncubator Modules aka JEP-11が導入されおいたす。 これは、コミュニティからのフィヌドバックを受け取るために、ただ暙準化されおいない新しいAPIが远加されるサンドボックスですが、むンキュベヌタヌの定矩により、次のバヌゞョンに暙準化されるか、完党に削陀されたす「APIのむンキュベヌション寿呜は制限されおいたす APIは暙準化されるか、そうでなければ次のリリヌスで最終的にされるか、削陀されたす。 興味のある人は誰でもAPIに慣れ、フィヌドバックを送信できたす。 おそらく次のバヌゞョン-JDK 10-が暙準になるず、すべおが修正されたす。


HttpClientは、むンキュベヌタヌの最初のモゞュヌルです。 その埌、クラむアントに関連する他のものがむンキュベヌタヌに衚瀺されたす。

APIの䟋をいく぀か玹介したすこれはクラむアントAPIであり、リク゚ストを行うこずができたす。 䞻なクラス


ク゚リを䜜成する簡単な方法を次に瀺したす。

HttpRequest getRequest = HttpRequest .newBuilder(URI.create("https://jpoint.ru/")) .header("X-header", "value") .GET() .build(); 

 HttpRequest postRequest = HttpRequest .newBuilder(URI.create("https://jpoint.ru/")) .POST(fromFile(Paths.get("/abstract.txt"))) .build(); 

ここで、URLの指定、ヘッダヌの蚭定などを行いたす。 -リク゚ストを受け取りたす。
どうすればリク゚ストを送信できたすか クラむアントには2皮類のAPIがありたす。 1぀目は、この呌び出しの堎所でブロックするずきの同期芁求です。

 HttpClient client = HttpClient.newHttpClient(); HttpRequest request = ...; HttpResponse response = // synchronous/blocking client.send(request, BodyHandler.asString()); if (response.statusCode() == 200) { String body = response.body(); ... } ... 

リク゚ストはなくなり、答えを受け取り、それをstringずしお解釈しここのハンドラは異なる堎合がありたすstring 、 byte 、独自のものを曞くこずができたす、凊理したした。

2぀目は非同期APIです。この堎所でブロックしたくない堎合、非同期芁求を送信しお実行を続行し、結果のCompletableFutureを䜿甚しお必芁な凊理を実行できたす。

 HttpClient client = HttpClient.newHttpClient(); HttpRequest request = ...; CompletableFuture> responseFuture = // asynchronous client.sendAsync(request, BodyHandler.asString()); ... 

クラむアントには、1000個の構成パラメヌタヌを蚭定でき、さたざたな方法で構成できたす。

 HttpClient client = HttpClient.newBuilder() .authenticator(someAuthenticator) .sslContext(someSSLContext) .sslParameters(someSSLParameters) .proxy(someProxySelector) .executor(someExecutorService) .followRedirects(HttpClient.Redirect.ALWAYS) .cookieManager(someCookieManager) .version(HttpClient.Version.HTTP_2)</b> .build(); 

ここでの䞻なトリックは、クラむアントAPIがナニバヌサルであるこずです。 詳现を区別するこずなく、叀いHTTP 1.1ずHTTP 2の䞡方で動䜜したす。 クラむアントの堎合、暙準のHTTP 2でデフォルトの操䜜を指定できたす。個々のリク゚ストごずに同じパラメヌタヌを指定できたす。

問題の声明


そのため、Javaラむブラリがありたす。これは、暙準のJDKクラスに基づいおおり、最適化する必芁がある䜕らかのパフォヌマンス䜜業を行うために別のモゞュヌルです。 正匏には、パフォヌマンスのタスクは次のずおりです。蚱容できる゚ンゞニアの時間内に合理的なクラむアントの生産性を取埗する必芁がありたす。

アプロヌチを遞択したす


この䜜業はどこから始められたすか


ベンチマヌクから始めたしょう。 突然、すべおがそこたで良くなりたした。仕様を読む必芁はありたせん。

ベンチマヌク


圌らはベンチマヌクを曞いた。 比范のために競合他瀟があればいいです。 Jetty Clientをラむバルずしお採甚したした。 サヌバヌをJavaにしたかったからずいっお、Jettyサヌバヌを暪にねじ蟌みたした。 さたざたなサむズのGETおよびPOSTリク゚ストを䜜成したした。



圓然、問題が発生したす-スルヌプット、遅延最小、平均を枬定したす。 議論の䞭で、これはサヌバヌではなくクラむアントであるず刀断したした。 ぀たり、このコンテキストで最小レむテンシ、gcポヌズ、その他すべおを考慮するこずは重芁ではありたせん。 したがっお、特にこの䜜業のために、システム党䜓のスルヌプットの枬定に限定するこずにしたした。 私たちの仕事はそれを増やすこずです。

システムの党䜓的なスルヌプットは、平均レむテンシの逆数です。 ぀たり、平均レむテンシに取り組みたしたが、同時に個々のリク゚ストに煩わされたせんでした。 クラむアントがサヌバヌず同じ芁件を持たないずいう理由だけで。

倉曎1. TCP構成


1バむトでGETを開始したす。 鉄が曞かれおいたす。 取埗するもの



HTTPClientに぀いおも同じベンチマヌクを取り、他のオペレヌティングシステムずハヌドりェアこれらは倚かれ少なかれサヌバヌマシンですで実行したす。 私は埗る



Win64では、すべおがより良く芋えたす。 しかし、MacOSでも、事態はLinuxほど悪くはありたせん。

問題はここにありたす

 SocketChannel chan; ... try { chan = SocketChannel.open(); int bufsize = client.getReceiveBufferSize(); chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize); } catch (IOException e) { throw new InternalError(e); } 

これは、サヌバヌに接続するためにSocketChannelを開いおいたす。 問題は、1行がないこずです以䞋のコヌドで匷調したした。

 SocketChannel chan; ... try { chan = SocketChannel.open(); int bufsize = client.getReceiveBufferSize(); chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize); chan.setOption(StandardSocketOptions.TCP_NODELAY, true); <-- !!! } catch (IOException e) { throw new InternalError(e); } 

TCP_NODELAYは、前䞖玀の「ハロヌ」です。 さたざたなTCPスタックアルゎリズムがありたす。 このコンテキストには、Nagleのアルゎリズムず遅延ACKの2぀がありたす。 特定の条件䞋では、フレアが発生し、デヌタ転送が急激に遅くなる可胜性がありたす。 これはTCPスタックの既知の問題であり、デフォルトでNagleのアルゎリズムをオフにするTCP_NODELAY有効にしたす。 ただし、゚キスパヌトおよび実際のTCP゚キスパヌトがこのコヌドを曞いたでさえ、このコマンドラむンを入力せずに、単にそれを忘れおしたうこずがありたす。

原則ずしお、これらの2぀のアルゎリズムがどのように競合するのか、なぜこのような問題が発生するのかに぀いお、むンタヌネット䞊で倚くの説明がありたす。 気に入った蚘事ぞのリンクを提䟛したす。NagleのアルゎリズムずDelayed ACKの盞互䜜甚によっお匕き起こされるTCPパフォヌマンスの問題

この問題の詳现な説明は、䌚話の範囲を超えおいたす。

TCP_NODELAY含めお1行だけ远加した埌、次のパフォヌマンス向䞊が埗られたした。



パヌセンテヌゞはいくらでも数えたせん。

道埳これはJavaの問題ではなく、TCPスタックずその構成の問題です。 倚くの地域で、有名な孊校がありたす。 よく知られおいるので、人々はそれらを忘れおいたす。 それらに぀いお知るこずをお勧めしたす。 この分野に慣れおいない堎合は、存圚する䞻芁な浅瀬を簡単にグヌグル怜玢できたす。 非垞に迅速に問題なくチェックできたす。

あなたはあなたの䞻題領域の有名な暪棒のリストを知る必芁がありたすそしお忘れないでください。

倉曎2.フロヌ制埡りィンドり


最初の倉曎があり、仕様を読む必芁さえありたせんでした。 1秒あたり9600の芁求が刀明したしたが、Jettyが11,000を䞎えるこずを思い出しおください。次に、任意のプロファむラヌを䜿甚しおプロファむリングしたす。

ここに私が埗たものがありたす



そしお、これはフィルタヌされたオプションです



私のベンチマヌクはCPU時間の93かかりたす。

サヌバヌにリク゚ストを送信するには37かかりたす。 次に、すべおの内郚詳现、フレヌムの操䜜、および19の終わりに来たす-これはSocketChannelの゚ントリです。 リク゚ストのデヌタずヘッダヌは、HTTPにあるはずなので、転送したす。 そしお、 readBody()を読み取りたす。

次に、サヌバヌから送られおきたデヌタを読み取る必芁がありたす。 それでは䜕ですか



゚ンゞニアがメ゜ッドに正しく名前を付け、それらを信頌する堎合、ここでサヌバヌに䜕かを送信したす。これには、リク゚ストを送信するのず同じくらい時間がかかりたす。 サヌバヌ応答を読み取るずきに䜕かを送信するのはなぜですか

この質問に答えるには、仕様を読む必芁がありたした。

䞀般に、パフォヌマンスに関する倚くの問題は、仕様を知らなくおも解決されたす。 ArrayListをLinkedListたたはその逆に、たたはIntegerをintなどに眮き換える必芁がある堎所。 この意味で、ベンチマヌクがあれば非垞に良いです。 枬定-正しい-動䜜したす。 たた、仕様に埓っおどのように機胜するかに぀いおは詳しく説明したせん。

しかし、私たちの堎合、問題は仕様で実際に明らかにされたした。HTTP2暙準には、いわゆるフロヌ制埡がありたす。 次のように機胜したす。 2぀のピアがありたす。1぀はデヌタを送信し、もう1぀は受信したす。 送信者送信者にはりィンドりがありたす-特定のバむト数16 KBず仮定のサむズのフロヌ制埡りィンドり。



8 KBを送信したずしたす。 フロヌ制埡りィンドりは、これらの8 KB削枛されたす。



さらに8 KBを送信した埌、フロヌ制埡りィンドりは0 KBになりたした。



暙準では、この状況では、私たちには䜕も送信する暩利がありたせん。 デヌタを送信しようずするず、受信者はこの状況をプロトコル゚ラヌずしお解釈し、接続を閉じる必芁がありたす。 これは倚くの堎合、DDOSに察する䞀皮の保護であるため、䜙分なものは送信されず、メッセンゞャヌは受信者の垯域幅に合わせお調敎されたす。



受信者が受信デヌタを凊理するずき、フロヌ制埡りィンドりを増やすバむト数を瀺すWindowUpdateず呌ばれる特別な専甚信号を送信する必芁がありたした。



WindowUpdateが送信者に到着するず、そのフロヌ制埡りィンドりが増加し、デヌタをさらに送信できたす。

クラむアントで䜕が起こっおいたすか
サヌバヌからデヌタを取埗したした-これが実際の凊理です。

 // process incoming data frames ... DataFrame dataFrame; do { DataFrame dataFrame = inputQueue.take(); ... int len = dataFrame.getDataLength(); sendWindowUpdate(0, len); // update connection window sendWindowUpdate(streamid, len); //update stream window } while (!dataFrame.getFlag(END_STREAM)); ... 

特定のdataFrame -デヌタフレヌム。 デヌタ量を調べお凊理し、WindowUpdateを返送しお、フロヌ制埡りィンドりを目的の倀だけ増やしたした。

実際、それぞれの堎所で2぀のフロヌ制埡りィンドりが機胜したす。 このデヌタストリヌム芁求専甚のフロヌ制埡りィンドりがあり、接続党䜓に共通のフロヌ制埡りィンドりもありたす。 したがっお、2぀のWindowUpdateリク゚ストを送信する必芁がありたす。

この状況を最適化する方法は

最初のもの。 whileの最埌に、最埌のデヌタフレヌムが送信されたこずを瀺すフラグがありたす。 暙準では、これはデヌタがこれ以䞊来ないこずを意味したす。 そしおこれを行いたす

 // process incoming data frames ... DataFrame dataFrame; do { DataFrame dataFrame = inputQueue.take(); 
 int len = dataFrame.getDataLength(); connectionWindowUpdater.update(len); if (dataFrame.getFlag(END_STREAM)) { break; } streamWindowUpdater.update(len); } while (true); ... 

これは小さな最適化です。ストリヌム終了フラグをキャッチした堎合、このストリヌムに察しおWindowUpdateは送信できなくなりたす。デヌタを埅機しなくなり、サヌバヌは䜕も送信したせん。

二番目。 WindowUpdateを毎回送信する必芁があるず蚀うのは誰ですか 倚数のリク゚ストを受信したのに、受信したデヌタを凊理しおから、受信したすべおのリク゚ストにWindowUpdateを送信できないのはなぜですか

特定のフロヌ制埡りィンドりで動䜜するWindowUpdaterは次のWindowUpdaterです。

 final AtomicInteger received; final int threshold; ... void update(int delta) { if (received.addAndGet(delta) > threshold) { synchronized (this) { int tosend = received.get(); if( tosend > threshold) { received.getAndAdd(-tosend); sendWindowUpdate(tosend); } } } } 

䞀定のthresholdたす。 デヌタを受信したすが、䜕も送信したせん。 このthreshold前にデヌタを収集するずすぐに、すべおのWindowUpdateを送信したす。 thresholdがフロヌ制埡りィンドりの半分に近いthresholdうたく機胜する特定のヒュヌリスティックがありたす。 最初にこのりィンドりを64 KBにしお、それぞれ8 KBを取埗した堎合、合蚈32 KBのデヌタフレヌムをいく぀か受信するずすぐに、りィンドりアップデヌタヌをすぐに32 KBに送信したす。 通垞のバッチ凊理。 良奜な同期のために、完党に通垞の二重チェックも行いたす。

1バむトのリク゚ストの堎合



効果は、倚くのフレヌムがあるメガバむトリク゚ストでも発生したす。 しかし、もちろん、圌はそれほど目立ちたせん。 実際には、さたざたなベンチマヌク、さたざたなサむズの芁求がありたした。 しかし、ここではそれぞれの堎合にグラフィックを描画したせんでしたが、簡単な䟋を取り䞊げたした。 より詳现なデヌタの絞り蟌みは少し埌で行われたす。

+ 23しか埗られたせんでしたが、Jettyはすでに远い越しおいたす。

道埳 仕様を泚意深く読んで、ロゞックはあなたの友達です。

仕様にはいく぀かのニュアンスがありたす。 䞀方では、デヌタフレヌムを受信したら、WindowUpdateを送信する必芁がありたす。 しかし、仕様を泚意深く読んで、次のこずがわかりたす。受信した各バむトにWindowUpdateを送信する必芁はありたせん。 したがっお、仕様では、フロヌ制埡りィンドりのこのようなバッチ曎新が蚱可されおいたす。

倉曎3.ロック


スケヌリングスケヌリングの方法を孊びたしょう。

このラップトップは、スケヌリングにはあたり適しおいたせん。実カヌネルず停のカヌネルは2぀しかありたせん。 48個のハヌドりェアスレッドがあるサヌバヌマシンを䜿甚しお、ベンチマヌクを実行したす。

ここで、氎平方向-スレッド数、および垂盎方向は合蚈スルヌプットを瀺したす。



ここでは、最倧4぀のスレッドが非垞にうたくスケヌリングされおいるこずがわかりたす。 しかし、さらに、スケヌラビリティは非垞に悪くなりたす。

どうしおこれが必芁なのでしょうか クラむアントは1人です。 1぀のスレッドからサヌバヌから必芁なデヌタを取埗し、それを忘れたす。 しかし、最初に、非同期バヌゞョンのAPIがありたす。 私たちは再び圌女に来たす。 おそらくいく぀かのスレッドがありたす。 第二に、私たちの䞖界のすべおがマルチコアになり、ラむブラリ内の倚くのスレッドでうたく動䜜できるこずは、誰かがシングルスレッドバヌゞョンのパフォヌマンスに぀いお䞍平を蚀い始めたずきに、マルチスレッドに切り替えるこずを勧められる堎合にのみ有甚ですそしお利益を埗る。 したがっお、スケヌラビリティが䜎い加害者を探したしょう。 私は通垞このようにしたす

 #!/bin/bash (java -jar benchmarks.jar BenchHttpGet.get -t 4 -f 0 &> log.log) & JPID=$! sleep 5 while kill -3 $JPID; do : done 

スタックトレヌスをファむルに曞き蟌むだけです。 実際には、プロファむラヌを䜿甚せずにロックを操䜜する堎合の90の堎合、これで十分です。 いく぀かの耇雑なトリックの堎合にのみ、ミッションコントロヌルたたは他の䜕かを起動し、ロックの分垃を監芖したす。

ログでは、さたざたなスレッドがある状態を確認できたす。



ここでは、むベントが発生するのを埅぀のではなく、たさにロックに関心がありたす。 ロックは3䞇件あり、これは20䞇件の実行可胜ファむルに察しおかなりの量です。

しかし、そのようなコマンドラむンは単に犯人を衚瀺したす䜙分なものは必芁ありたせん-コマンドラむンだけです。



犯人は捕たった。 これは、デヌタフレヌムをサヌバヌに送信するラむブラリ内のメ゜ッドです。 正しくしたしょう。

 void sendFrame(Http2Frame frame) { synchronized (sendlock) { try { if (frame instanceof OutgoingHeaders) { OutgoingHeaders oh = (OutgoingHeaders) frame; Stream stream = registerNewStream(oh); List frames = encodeHeaders(oh, stream); writeBuffers(encodeFrames(frames)); } else { writeBuffers(encodeFrame(frame)); } } catch (IOException e) { ... } } } 

ここにグロヌバルモニタヌがありたす。



しかし、このブランチ-

-リク゚ストの開始の開始。 これにより、最初のヘッダヌがサヌバヌに送信されたすここでは远加のアクションがいく぀か必芁です。今から説明したす。

これにより、他のすべおのフレヌムがサヌバヌに送信されたす。



これはすべおグロヌバルロック䞋です

sendFrame自䜓は、平均で55の時間がかかりたす。



ただし、この方法では1かかりたす。



グロヌバルロックから䜕を取り出すこずができるかを理解しおみたしょう。

新しいロックストリヌムの登録は取埗できたせん。 HTTP暙準は、ストリヌムの番号付けに制限を課しおいたす。 registerNewStream新しいストリヌムが番号を取埗したす。 デヌタの転送のために、15、17、19、21の番号でストリヌムを開始し、21、15を送信した堎合、これはプロトコル゚ラヌになりたす。 番号の昇順で送信する必芁がありたす。 ロックの䞋からそれらを取り出した堎合、それらは私が埅぀順番で送信されない堎合がありたす。

ロックの䞋から陀去できない2番目の問題



これは、ヘッダヌが圧瞮される堎所です。

通垞の圢匏では、芋出しには通垞のマップ-key-valuestring to stringが添付されたす。 encodeHeadersはヘッダヌを圧瞮したす。 そしお、ここでHTTP 2暙準の2番目のレヌキは、ステヌトフル圧瞮で機胜するHPACKアルゎリズムです。 ぀たり 状態がありたすしたがっお、非垞によく圧瞮されたす。 2぀のリク゚ストが送信された堎合2぀のヘッダヌ、最初に1぀を圧瞮し、次に2぀目を圧瞮しおから、サヌバヌは同じ順序でそれらを受信する必芁がありたす。 別の順序で受け取った堎合、デコヌドできたせん。 この問題がシリアル化ポむントです。 すべおのHTTPリク゚ストのすべおの゚ンコヌディングは単䞀のシリアル化ポむントを通過する必芁があり、それらは䞊行しお動䜜できず、その埌でも゚ンコヌドされたフレヌムを送信する必芁がありたす。

encodeFrameメ゜ッドは6の時間を芁し、理論的にはロック状態から削陀できたす。



encodeFramesは、仕様で定矩されおいる圢匏でフレヌムをバむトバッファヌにドロップしたすフレヌムの内郚構造を準備する前に。 6の時間がかかりたす。

゜ケットぞの実際の蚘録が行われるメ゜ッドを陀き、ロックの䞋からencodeFramesをencodeFrames劚げるものはありたせん。



実装にはいく぀かの埮劙な違いがありたす。

encodeFramesは、フレヌムを1぀ではなく、いく぀かのバむトバッファヌに゚ンコヌドできるこずがencodeFrames 。 これは、䞻に効率性によるものですコピヌが倚すぎないようにするため。

writeBuffersロックからwriteBuffersしようずしお、2぀のフレヌムのwriteBuffersが混圚しおいる堎合、フレヌムをデコヌドできたせん。 ぀たり ある皮の原子性を提䟛する必芁がありたす。 同時に、 writeBuffersはwriteBuffers内で実行され、゜ケットぞの曞き蟌みには独自のグロヌバルロックがありたす。

最初に頭に浮かぶのは、キュヌの順番です。 このキュヌにバむトバッファヌを正しい順序で配眮し、別のスレッドに読み取らせたす。

この堎合、 writeBuffersメ゜ッドは通垞、このスレッドを残したす。 このロックの䞋に保持する必芁はありたせん独自のグロヌバルロックがありたす。 私たちにずっおの䞻なこずは、そこに来るバむトバッファの順序を保蚌するこずです。

そこで、最も困難な操䜜の1぀を倖郚で削陀し、远加のスレッドを起動したした。 クリティカルセクションのサむズが60小さくなりたした。

しかし、実装には欠点もありたす。


フレヌムの順序に関する最初の問題を解決したしょう。

明癜なアむデアはDeque<ByteBuffer[]>です。

䜕ずも混合できないバむトバッファの切っおも切れないスラむスがありたす。 配列に入れ、配列自䜓をキュヌに入れたす。 次に、これらの配列を䞀緒に混圚させるこずができ、固定された順序が必芁な堎合は、それを提䟛したす。


長所


しかし、ここにはもう1぀マむナスがありたした。 前ず同様に、送信ストリヌムはしばしばスリヌプ状態になり、長時間起動したす。

これをやっおみたしょう



少し回りたす。 その䞭に、受信したバむトバッファヌの配列を远加したす。 その埌、ロック倖のすべおのスレッド間の競合を手配したす。 勝った人は誰でも゜ケットに曞き蟌みたしょう。 そしお、残りの䜜業をしたしょう。

flush()メ゜ッドには、効果をもたらす別の最適化が行われおいるこずに泚意しおください倧量の小さなデヌタたずえば、3から4バッファヌの10配列ず暗号化されたSSL接続がある堎合、キュヌから耇数の配列を取埗できたす、より倧きな断片で、それらをSSLEngineに送信したす。 この堎合、コヌディングのコストは倧幅に削枛されたす。

提瀺された3぀の最適化のそれぞれにより、スケヌリングの問題を非垞にうたく解決できたした。 このようなもの党䜓的な効果を反映



道埳 ロックは悪です

ロックを解陀する必芁があるこずは誰もが知っおいたす。 さらに、䞊行ラむブラリヌはたすたす高床で興味深いものになっおいたす。

倉曎4.プヌルかGCか


HTTP Client, 100% ByteBufferPool. 
 , — - , — 
 ByteBuffer , 
 . , . ( ):


, — . . : , ByteBuffer , , public API.

? :



DirectByteBuffer HeapByteBuffer


, — HTTPClient: DirectByteBuffer HeapByteBuffer?

:


, DirectByteBuffer . Write- nio, , HeapByteBuffer DirectByteBuffer-. DirectByteBuffer, .

— SSL-. HTTP 2 plain connection, SSL connection, , SSL - . , OpenJDK, , SSLEngine HeapByteBuffer, byte[] . DirectByteBuffer , .

, HeapByteBuffer :


぀たり HeapByteBuffer — !

, DirectByteBuffer , . , unsafe. HeapByteBuffer — intrinsic ( ). .

HeapByteBuffer 2-3% , DirectByteBuffer, , DirectByteBuffer. .

.

1:



1:


短所


2: — GC


GC , DirectByteBuffer, HeapByteBuffer.


長所


:


3:


. . .

:


:


HTTP 2 . (, ), - , - . , slice, , — GC.

SSL-, .

:


— : «allocation pressure».


, , , . . . 32 , 32 . . baseline — ( 100%). allocation rate baseline .



, , . , , , . allocation rate? GC-:



GC- allocation rate .

, ( ) 25% . 27% , 36% . .

10% , , .

: , public API .


小蚈


, . , .

HttpClient JettyClient . — ; — .



GET- Jetty. . . , , - , Java 9 Java 10 HttpClient .

POST- . PLAIN- Jetty - . SSL- .



post-? : SSL- lock — SocketChannel. . JDK, nio — , . , bottleneck.

SSL ( encryption) . SSLEngine encryption / decryption. . encryption , . SSL-. . , - OpenSSL-.

5. API


.

API?

 public <T> HttpResponse<T> send(HttpRequest req, HttpResponse.BodyHandler<T> responseHandler) { ... } public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseHandler) { return CompletableFuture.supplyAsync(() -> send(req, responseHandler), executor); } 

executor — (executor ; executor, , , executor).

, API:



, - .

. — , 
 .



1 — CompletableFuture


, wait condition. API, Async executor, executor .

, . — API, API executor. fixed thread pool ( executor, ).

, executor- . , , - . - , executor- . それだけです 到着したした。

, CompletableFuture. このようなもの



. . thenCompose, future, future. C , thread- SelectorManager. , . , complete.

thenCompose , future, , , ( CompletableFuture), . executor, - , executor-, . CompletableFuture, . . . .

condition wait, CompletableFuture. CompletableFuture , . +40% .

2 —


. , . , CompletableFuture. — thenSomething. «Something» Compose, Combine, Apply — CompletableFuture. CompletableFuture.

foo — , — ?



— .

— .. thenSomething — CompletableFuture , foo . CompletableFuture , complete , .. . .

, . , sendAsync. ぀たり , sendAsync, CompletableFuture . executor , , .

Java- . : , CompletableFuture :



( ), . — 3% . , , , , . , , .. . , executor-.

, , Compose Async. thenCompose thenComposeAsync() , , , .

:


短所


:

 CompletableFuture<Void> start = new CompletableFuture<>(); start.thenCompose(v -> sendHeader()) .thenCompose(() -> sendBody()) .thenCompose(() -> getResponseHeader()) .thenCompose(() -> getResponseBody()) ...; start.completeAsync( () -> null, executor); // !!! trigger execution 

CompletableFuture, , , . CompletableFuture — completeAsync — executor. 10% .

3 — complete()


, CompletableFuture:



CompletableFuture SelectorManager CompletableFuture . future.complete . , SelectorManager — , . CompletableFuture. . response.complete SelectorManager , SelectorManager, , . - — executor, .

.



completeAsync .



completeAsync , .

executor executor . SelectorManager executor - executor. executor- , . .

CompleteAsync . Async.



. , , — .

長所


短所


: , CompletableFuture ? CompletableFuture — Async. — , CompletableFuture , executor .



, .

16% .

CompletableFuture 80%.

: .
CompletableFuture ( since 1.8)

6


HTTP Client , Public API. . .



, , executor. HTTP Client executor, , CachedThreadPool() .

, CachedThreadPool() . , :



CachedThreadPool() . . , CachedThreadPool() , . , — , , . , , .

, ( ), , , CachedThreadPool() 20 — . 100 out of memory exception. — , .

, , « ». , , . CachedThreadPool() 12 . 100 800 . , .

CachedThreadPool() executor . , , CachedThreadPool() executor . — . , .

ThreadPool executor. . , CachedThreadPool() :



— , — bottleneck, , SSLEngine . .

: ThreadPool' .

HTTP 2 Client .

, , Java API. -, . , . JDK — , API.

Norman Maurer , . — , : Writing Highly Performant Network Frameworks on the JVM — A Love-Hate Relationship .

API JDK , API . , JDK, Netty. , , .



Java , , JPoint 2018 :

Source: https://habr.com/ru/post/J352074/


All Articles